Škálování Gitu v OpenAI
Budujeme správu verzí pro rozsah práce agentů a zároveň investujeme do ekosystému Gitu.
Za poslední rok rostla velikost našeho monorepa i nároky na něj ohromujícím tempem. Bylo jasné, že musíme přepracovat náš systém správy verzí, aby zvládl rozsah souběžné práce, který umožňuje Codex.
Vytvořili jsme Pando, nový backend pro úložiště Gitu, aniž bychom přerušili vývoj nebo změnili způsob, jakým náš tým Git používá. Pando nyní zajišťuje veškerý vývoj v monorepu OpenAI. Pando jsme navrhli se dvěma ambiciózními cíli: za prvé horizontálně škálovatelné zápisy optimalizované pro souběžné operace push do mnoha větví. A za druhé maximální výkon čtení, aby klienti mohli stahovat data dostatečně rychle a plně využít dostupnou šířku pásma.
Těchto cílů jsme dosáhli při zachování kompatibility s protokolem i klientem Gitu, takže naši vývojáři i agenti mohli Pando bez problémů začlenit do stávajících pracovních postupů. V produkčním provozu Pando zkrátilo medián latence operace push téměř pětkrát. Nyní dodává téměř petabajt dat Gitu denně.
V tomto článku vysvětlíme, proč jsme vytvořili nový systém správy verzí a proč jsme se rozhodli zachovat kompatibilitu s Gitem. Porovnáme náš systém s běžnými přístupy ke škálování Gitu a popíšeme, proč Pando dokáže jedinečným způsobem naplnit nároky OpenAI na škálovatelnost. Probereme také další plány a to, jak budeme dál přispívat do Gitu, aby podporoval práci v rozsahu, který přinášejí agenti.
Codex změnil rozsah i tempo vývoje v OpenAI a umožnil vývojářům dělat mnohem více práce paralelně. To znamená více větví, operací push a slučování i více operací fetch a clone z vývojových prostředí, výzkumných úloh a CI.
Tyto nároky se násobí. Jeden vývojář má nyní několik změn připravených pro CI najednou a každý push může spustit několik úloh, které stahují tentýž commit.
Zhruba od konce loňského roku systémy zajišťující provoz našeho monorepa přestávaly stíhat. Potýkali jsme se s pomalými operacemi fetch a push i neúspěšným slučováním. Spolehlivost místy klesala pod 90 %. Narazili jsme na limit samotného Gitu, nebo způsobu, jakým jsme naše monorepo provozovali? Git vzešel z komunity, která si e-mailem vyměňovala patche jádra. Vývojáři už více než dvacet let přizpůsobují své nástroje Gitu podle toho, jak se mění způsob tvorby softwaru. Když jsme v našem monorepu začali narážet na potíže, záleželo nám na tom, abychom na tuto práci mohli navázat.
Viděli jsme cestu, jak zachovat zavedený pracovní postup a přitom přestavět backend tak, aby zvládal paralelní operace push do různých větví a poskytoval stejnou historii mnoha čtenářům současně. Tím, že jsme správu verzí dál stavěli na Gitu, mohla být vylepšení klienta užitečná i mimo OpenAI. Chtěli jsme tato vylepšení z naší kopie Gitu(otevře se v novém okně) přispět do hlavního projektu a sdílet je s širší komunitou.
Škálování systému správy verzí má dva zásadní rozměry: mnoho zapisujících klientů odesílajících změny do různých větví a mnoho čtenářů stahujících stejnou historii. Pando optimalizuje obojí.
Za prvé Pando koordinuje aktualizace každé větve nezávisle. Aktualizace nesouvisejících větví mohou probíhat paralelně a čtenář stahující větev dostane historii potřebnou pro danou revizi. Kompromisem je, že čtenář prohlížející několik větví může vidět jejich stavy z různých časových okamžiků.
Za druhé Pando při operacích fetch opakovaně využívá soubory pack (formát Gitu pro společnou kompresi více objektů), místo aby vytvářelo samostatný soubor pack pro každého klienta. Klient si sice může stáhnout některé objekty, které už má, ale server může stejná data poskytovat mnoha klientům s minimem výpočtů.
Díky těmto rozhodnutím můžeme škálovat zápisy napříč větvemi a obsluhovat čtení z mezipamětí. Abychom viděli, jak to do sebe zapadá, začněme tím, jak Git ukládá a přenáší historii.
Git musí udržovat synchronizované dvě části úložiště: objekty a reference.
Úložiště Gitu zaznamenává snímky souborů a historii, která je propojuje, jako graf objektů. Objekt může být blob, strom, commit nebo tag. Bloby uchovávají obsah jednotlivých souborů a stromy přiřazují názvy blobům nebo dalším stromům, čímž vytvářejí adresářovou strukturu. Commity odkazují na kořenový strom a nula či více rodičovských commitů, a tvoří tak historii úložiště.
Objekty jsou identifikovány hashem svého typu a obsahu. Objekty jsou také neměnné: zaznamenání změněného souboru vytvoří nový blob, zatímco nezměněné soubory a adresáře mohou znovu využít stávající objekty.
Větve a tagy jsou reference neboli „refs“, které odkazují na commity. Vytvořením commitu ve větvi vznikne nový commit a reference větve se aktualizuje tak, aby na něj odkazovala.
Objekty Gitu na sebe odkazují pomocí ID: commity na stromy a stromy na další stromy a bloby. Větev refs/heads/main odkazuje na commit C2, jehož rodičem je C1. C1 odkazuje na kořenový strom T1, v němž parser.py odkazuje na blob B1. C2 odkazuje na kořenový strom T2, v němž parser.py odkazuje na blob B2. V obou kořenových stromech odkazuje docs/ na stejný strom T3. Jeho položka README.md odkazuje na blob B3 a usage.md na blob B4.
Git ukládá objekty buď „volně“, nebo „zabalené“. Volné objekty jsou uložené v samostatných souborech identifikovaných ID objektu. Zabalené objekty jsou uložené společně v souboru pack, takže podobné objekty lze ukládat jako vzájemné rozdíly.
Git může bloby ukládat jako samostatné soubory nebo je zabalit společně a jeden blob zakódovat jako rozdíl vůči druhému.
Objekt je v Gitu dosažitelný z commitu, pokud jej Git dokáže najít sledováním odkazů z daného commitu přes jeho rodiče, stromy a bloby. Při stahování změn klient sdělí serveru, které commity chce a které už má. Server najde objekty dosažitelné z požadovaných commitů, vynechá ty, o nichž ví, že je klient už má, a zbytek odešle jako balíčkový soubor ve stejném formátu, jaký Git používá na disku. Úplné klonování začíná bez jakýchkoli objektů, takže potřebuje všechny objekty dosažitelné z klonované historie.
Odesílání změn přenáší objekty opačným směrem a žádá server o aktualizaci jedné nebo více referencí. Klient využívá sadu referencí oznámených serverem, aby neposílal objekty, které server už má. Odesílaný balíček proto může záviset na existujících objektech: samotné přijetí balíčku k rekonstrukci nové historie nestačí.
Replikovat úložiště Gitu znamená udržovat objekty i reference dostupné na více serverech. Než může replika poskytovat novou hodnotu reference, musí být schopna dodat objekty dosažitelné z této hodnoty. Služba také musí koordinovat soupeřící aktualizace referencí, tedy odesílání změn, které se snaží posunout stejnou větev ze stejného původního koncového commitu na různé nové commity. V systémech, které všem zápisům do úložiště vnucují jediné pořadí, mohou odesílání změn do nesouvisejících větví čekat jedna na druhou.
Při návrhu backendu Pando jsme vyhodnotili běžné přístupy ke škálování správy verzí pomocí Gitu: jeden koordinuje aktualizace referencí mezi replikami úložiště pomocí třífázového potvrzování transakcí (3PC, protokol pro koordinaci potvrzení mezi více uzly), druhý řadí aktualizace prostřednictvím sdíleného protokolu zápisu napřed (WAL, záznam změn uložený před jejich provedením) v úložišti blobů.
V modelovaném návrhu s třífázovým potvrzováním uchovává služba Gitu úplnou kopii úložiště na několika replikách. Když kterýkoli server přijme odeslanou změnu, příchozí balíčkový soubor se zkopíruje a paralelně odešle všem replikám. Aby se odeslaná změna zveřejnila, musí aktualizaci reference přijmout většina replik. Server, který tento proces řídí, je koordinátor. Proces vyžaduje několik kol komunikace mezi koordinátorem a každou další replikou.
Aby replika přijala aktualizaci reference, uzamkne svou kopii reference a ověří, že stále odkazuje na commit, který klient očekává. Koordinátor pak replikám, které aktualizaci přijaly, odešle zprávu o předběžném potvrzení. Jakmile většina přijetí této zprávy potvrdí, koordinátor jim dá pokyn k provedení aktualizace. Každá aktuální replika pak může ze své lokální kopie úložiště oznámit nový koncový commit větve.
Udržování úplných replik umožňuje obsluhovat čtení lokálně a zaručuje klientům konzistentní pohled na všechny reference bez ohledu na to, kterou repliku zvolí. Reference se proto aktualizují jedna po druhé a každé odeslání změny vyžaduje několik komunikačních cyklů pro koordinaci aktualizace reference mezi replikami.
Nutnost čekat na dosažení kvora způsobuje, že výkon disků replik a latence síťové komunikace přímo ovlivňují dobu potřebnou k odeslání změn. S rostoucím objemem zápisových operací musí repliky zvládat nejen příchozí balíčkové soubory, ale také provoz související se čtením dat.
V modelovaném návrhu založeném na protokolu předběžného zápisu používá služba Gitu sdílený manifest v úložišti blobů k určení pořadí aktualizací v rámci úložiště. Když server přijme odeslanou změnu, nahraje příchozí balíčkový soubor do úložiště blobů a připraví aktualizaci reference ve své lokální kopii úložiště. Pro zveřejnění odeslané změny přidá do sdíleného manifestu v úložišti blobů položku, která zaznamenává aktualizaci reference a odkazuje na balíčkový soubor. Server Gitu poté aktualizuje manifest pomocí operace compare-and-swap. Ta manifest buď atomicky nahradí nebo změnu odmítne. Pokud jej mezitím aktualizoval jiný zapisující klient, server přečte nově přidané položky, ověří, že jeho aktualizace reference je stále platná, a zkusí to znovu s již nahraným balíčkovým souborem.
Zapisující klienti mohou své balíčkové soubory nahrávat paralelně, ale i odesílání změn do nesouvisejících větví vyžadují aktualizaci stejného manifestu. Doba komunikačního cyklu úspěšné operace compare-and-swap omezuje, jak často může úložiště zveřejnit nový manifest. Úložiště blobů s nízkou latencí tento krok urychluje. Dávkové zpracování umožňuje jedním manifestem aktualizovat několik referencí najednou, ovšem za cenu řazení aktualizací do fronty a vyšší latence.
Úložiště blobů může přijímat balíčkové soubory paralelně, ale aktualizace sdíleného manifestu spolu stále soupeří. Samotné zvýšení kapacity úložiště blobů toto úzké hrdlo neodstraní. Každá replika musí balíčky z úložiště blobů nejprve přečíst a indexovat, než je může poskytovat lokálně. Pokud příchozí zápisy postupují rychleji než tato práce, repliky zaostávají a klienti déle čekají na možnost stáhnout nově odeslané commity.
Tím se dostáváme k backendu Pando. Pando je nová architektura pro obsluhu úložišť Gitu, jejímž cílem je škálovat na velký počet klientů odesílajících změny současně. Funguje takto:
Než jsme začali vyvíjet Pando, tušili jsme, že své požadavky dokážeme splnit, aniž bychom museli ustoupit od Gitu. Museli jsme ale přehodnotit některé věci, které většina systémů založených na Gitu považuje za nezbytné:
Konzistence celého úložiště: Poskytnutí konzistentního snímku všech referencí k určitému okamžiku vyžaduje koordinaci v rámci úložiště. V systému, který určuje společné pořadí všech aktualizací referencí, soupeří odesílání změn do nesouvisejících větví o jediný koordinační bod.
Jedinečné balíčkové soubory pro každého klienta: Pokud dva klienti s odlišnou lokální historií stahují stejnou větev, server může potřebovat pro každého vytvořit jiný balíček, aby neposílal objekty, které už mají. Vytváření těchto odpovědí spotřebovává procesorový čas a diskové I/O, což ztěžuje škálování na velký počet čtenářů.
Pando místo toho nabízí volnější model konzistence (kauzální konzistenci) pro škálování zápisů a opakovaně využívá balíčkové soubory pro škálování čtení.
Když klient odešle změnu do větve, Pando uloží příchozí soubor balíčku jako neměnný blob do úložiště blobů. Při každém posunu větve Pando zaznamená do úložiště metadat verzi reference, která obsahuje nový koncový commit, balíček, jenž jej dodal, a všechny předchozí verze referencí, na nichž balíček závisí. Úložiště blobů i úložiště metadat lze škálovat horizontálně.
Stažení commitu nebo reference z Pando vyžaduje přehrání sady verzí referencí (souborů balíčků) klientovi, čímž se obnoví stav klienta, který stahovanou verzi reference vytvořil. To zajišťuje slabší formu konzistence napříč celým úložištěm, ale zároveň serializovatelnost aktualizací jednotlivých referencí. Klienti se dostanou do konfliktu pouze při souběžném odeslání změn do stejné reference. Klient, který čte několik větví, nemůže předpokládat, že tato čtení zachycují jediný časový okamžik v rámci celého úložiště.
Při odeslání změn do hlavní větve se přidá přírůstkový balíček obsahující jeden nebo více commitů. Každé odeslání do vedlejší větve znovu zahrnuje její předchozí commity, takže klient s oznámenou verzí hlavní větve může vedlejší větev stáhnout v jediném balíčku.
Jde o formu kauzální konzistence. Při stahování reference čtenář získá historii zapisujícího klienta, který ji vytvořil. Během odeslání změn Pando řídí, na jaké společné historii se klient a server shodnou, a tím zajišťuje, že klienti vytvářejí snáze sdílené balíčkové soubory.
Pando proto při operacích Gitu neposkytuje stav všech referencí k jedinému okamžiku ani pořadí aktualizací referencí v rámci klonování nebo stahování. Představme si konkrétně, že se větve foo i bar aktualizují během vyjednávání úplného klonu. Během vyjednávání může jeden klient vidět foo ve verzi 1 a bar ve verzi 2, zatímco druhý vidí foo ve verzi 2 a bar ve verzi 1. V praxi uživatelé ani agenti na globálním pořadí aktualizací referencí nezávisejí; důležité je pro ně pořadí aktualizací jedné reference.
V této simulaci tří modelů konzistence nastavujeme 1 000 klientů tak, aby si každý vybral jinou větev a poté do ní opakovaně odesílal jedinečné commity. Měříme celkový uplynulý čas a latenci od odeslání změny do dostupnosti pro stažení. Druhá metrika je pro pracovní zátěže důležitá, protože měří dobu celého cyklu při spolupráci uživatelů a agentů nebo při používání dalších systémů závislých na správě verzí. Simulace modeluje hardware od běžných po prémiové virtuální stroje a systémy pro ukládání blobů. V této simulaci jsme u třífázového potvrzování transakcí zmírnili požadavek na konzistenci celého úložiště.
Simulating Git Server Implementations
Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.
Spusťte tuto simulaci, která porovnává Pando se dvěma dalšími přístupy. Vyberte infrastrukturu od běžného po prémiový hardware, počet klientů a další nastavení a podívejte se na srovnání.
Modelovaný systém s třífázovým potvrzováním vykazuje při zátěži výrazně vyšší latenci zápisu, protože jeho disky dosahují limitu. Systémy s protokolem zápisu napřed ze stejného důvodu vykazují výrazně vyšší latenci čtení, protože servery nestíhají číst žurnál. Jak při reálných testech na našem monorepozitáři, tak v této simulaci Pando podporuje mnoho tisíc klientů, kteří mohou souběžně odesílat změny, aniž by při zátěži klesal výkon.
Při zpracování odeslání změny Pando ověřuje příchozí soubor balíčku během jeho streamování do úložiště blobů, aniž by jej rozbalovalo do lokálního úložiště. Když klient zahájí stahování, Pando vybere uložené balíčky, které potřebuje, a klient si je stáhne. Žádný server nemusí aplikovat balíčkové soubory na lokální úložiště ani provádět náročné operace rozbalování a balení. Protože se servery Panda věnují především vyjednávání a výběru souborů pack, mohou běžet na běžném hardwaru.
Pando tak může škálovat až na limity klientů, sítě mezi klienty a Pandem a služby úložiště blobů. Při dostatečné kapacitě mohou klienti pracující na nezávislých větvích přenášet balíčky tak rychle, jak jim dovolí jejich vlastní připojení. Abychom touto rychlostí obsloužili více klientů, můžeme přidat servery Pando a současně škálovat úložiště metadat i blobů.
Pushing and Fetching a Branch in Pando
Pando stores the writer’s packfile in blob storage and records the ref version in metadata service. On fetch, Pando selects the packs the reader needs, and Express serves them.
Agent upraví soubory, vytvoří lokální commit a spustí uložení změny gitu. Tím vznikne soubor balíčků s těmito změnami pro aktualizaci větve. Pando balíček přijme a streamuje jej do úložiště blobů. Po ověření příchozí operace push dekódováním v jediném průchodu Pando použije transakci nad více klíči k zaznamenání verze reference do úložiště metadat a k atomické aktualizaci ukazatele reference. Pando tak zaručuje konzistenci typu „čtení vlastních zápisů“. Celá tato operace zápisu vyžaduje čtení a zápisů metadat.
Když větev stahuje jiný klient, Pando vybere sadu uložených balíčků podle historie, kterou klient už má. Tato operace vyžaduje čtení metadat, kde je nejkratší vzdálenost ve verzích referencí mezi tím, co klient má, a tím, co požaduje. Server vrátí URI balíčkových souborů a klient je stáhne prostřednictvím služby Express, která balíčkové soubory ukládá do regionálních mezipamětí.
Tento návrh stojí na třech hlavních službách architektury Pando: odesílání změn, stahování změn a kompakci.
Když klient odesílá změnu, běžný server Gitu se chová jako hráč pokeru, který vyloží všechny karty: odhalí klientovi každou větev, kterou má. Klient pak shromáždí commity, stromy a bloby a vytvoří soubor balíčku, který vynechá objekty, o nichž ví, že je server už má.
Oznámení více referencí může zmenšit objem dat odesílání změn. Může ale také ztížit opětovné využití balíčku, protože jinému klientovi mohou chybět objekty, které zapisující klient vynechal.
Pando volí jiný přístup. Místo odhalení všech karet oznámí klientovi jen vybranou sadu větví. Má to dva důvody:
Poskytování konzistentního pohledu na všechny reference je nákladné. Pokud existují miliony větví, může samotné oznámení referencí přesáhnout 100 MiB. Každý server kvůli tomu musí buď udržovat aktuální mapování, což přináší značnou režii koordinace, nebo provádět databázový dotaz, jehož náročnost roste s počtem referencí.
Když klient dostane jen podmnožinu referencí, vytvoří balíček závislý pouze na tom, co mu server výslovně dovolí vynechat. Sledování závislostí umožňuje Pandu znovu použít soubor pack vytvořený klientem při jednom odeslání změny jako odpověď na následné stažení změny.
Čím více referencí server Gitu oznamuje, tím více zdrojů mají klienti při odesílání změny k dispozici pro vynechání objektů, a mohou tak vytvářet menší balíčky. Aby však server mohl tyto balíčkové soubory znovu využít, musí být schopen ověřit, že stahující klienti mají všechny jejich závislosti. Jinak by v souboru balíčků mohly chybět objekty, které klient potřebuje.
Pokud naopak server Gitu neoznámí žádnou referenci, klient vytvoří soubor balíčků obsahující celou historii úložiště. Takový soubor balíčků může použít libovolný jiný klient, ovšem za značnou cenu: stažení jakékoli větve se fakticky rovná úplnému klonování.
Oznámení více referencí vede k menším balíčkovým souborům . Většiny zmenšení lze dosáhnout už oznámením jediné reference.
Když klient odešle novou vývojovou větev, Pando standardně oznámí jako základ pouze určenou hlavní větev. Velikost oznámení tak zůstává stejná, i když v úložišti přibývají větve. Má to ale své kompromisy. Pokud vývojář opakovaně odesílá změny do jedné větve nebo navrství jednu vývojovou větev na druhou, klient musí znovu odeslat vše mimo hlavní větev.
Další odeslání změn do větve jsou tak větší, obsahují potenciálně nadbytečné objekty a spotřebovávají více přenosové kapacity klienta. V našem monorepozitáři byl medián velikosti takto vytvořeného balíčkového souboru pod 20 KiB a 99. percentil pod 3 MiB. Pro nás šlo o rozumný kompromis, protože čtenář, který už má oznámenou historii hlavní větve, může danou větev stáhnout pomocí jediného balíčkového souboru.
Po výběru sady referencí, které klientovi oznámí, Pando přijme odeslání změny a aktualizuje své úložiště metadat. Aktualizaci metadat Pando rozděluje na synchronní a asynchronní část. Synchronní aktualizace metadat zajišťuje, že klienti mohou nově aktualizovaný commit okamžitě stáhnout. Zapíše se nová verze reference a ukazatel reference se přesměruje na tuto verzi. Uloží se přitom nový koncový commit, závislosti použité při vyjednávání balíčkového souboru a odkaz na odeslaný balíčkový soubor.
Ve stejné transakci se zároveň zaznamenávají asynchronní činnosti, například naplánování kompakce. U velmi velkých odeslání změn se synchronně zapisuje jen podmnožina commitů. Díky tomu potřebujeme jen konstantní počet čtení a zápisů do úložiště metadat bez ohledu na velikost odeslání změn.
Důležité je, že servery backendu Pando mohou přijímat odeslání změn do nesouvisejících referencí nezávisle. Jediným klíčem, o který operace soupeří, je ukazatel reference. Ten chráníme atomickou operací podobnou operaci compare-and-swap. Výkon zápisu můžeme škálovat přidáváním serverů Pando, škálovacích jednotek úložiště metadat nebo úložišť blobů.
Při stahování změn klient předá serveru dvě sady ID commitů, označované v protokolu jako have a want. Běžný server Gitu vypočítá množinový rozdíl mezi objekty dosažitelnými z požadovaných commitů a objekty dosažitelnými z commitů, které klient už má, zkomprimuje jej a výsledek odešle klientovi. Přestože open-source server Gitu používá řadu lokálních indexů k urychlení této operace, vytváření těchto balíčkových souborů může být výpočetně náročné a obecně se obtížně ukládají do mezipaměti.
Pando místo toho využívá mapování commitů zaznamenaná při odesílání změn. Podle klientových položek have a want vyhledá sadu verzí referencí, které tyto commity obsahují. Každá z těchto verzí referencí odkazuje na balíčkový soubor, který backend Pando přijal dříve. Server z toho určí, co klient potřebuje, a vybere balíčkové soubory pokrývající sadu potřebných objektů.
Planning fetches in Pando
Pando compares what a client has with what it wants, then selects the stored packfiles needed—including any missing branch dependencies.
Fetch 27 trunk packs.
Select a branch to include its trunk dependencies.
Pando mapuje seznamy dostupných a požadovaných commitů protokolu Git na posloupnost verzí referencí. Požadavek na vedlejší větev zahrnuje i historii hlavní větve až po bod, na němž vedlejší větev závisí.
Protokol Git umožňuje v odpovědi HTTP na klonování a stahování změn odeslat pouze jeden balíčkový soubor. Nedoceněná funkce protokolu Git packfile-uri však serveru umožňuje vrátit odkazy pro samostatné stažení dalších balíčků. Pando využívá výhradně tuto možnost a přímo v odpovědi přenáší prázdný balíčkový soubor. Na podporu této funkce přispěla OpenAI do hlavního projektu řadou vylepšení funkce packfile-uri.
Systémy OpenAI fungují v mnoha geografických regionech, a proto v každém z nich provozujeme clustery Pando s lokálními mezipaměťmi. Služba Express pro doručování balíčků zajišťuje mezipaměť našeho úložiště blobů a samostatná služba zajišťuje mezipaměť úložiště metadat. Tyto mezipaměti umožňují backendu Pando obsluhovat požadavky na ID commitu nebo speciální čtení reference výhradně ze stavu uloženého v mezipaměti daného clusteru. Požadavky na balíčkové soubory směrujeme na servery Express pomocí konzistentního hashování. Míra zásahů do mezipaměti přesahuje 99 %.
Tento systém mezipamětí poskytuje mimořádně vysoký výkon, který dokáže plně vytížit síťové spojení mezi klienty a backendem Pando. Ověřili jsme, že s těmito URI balíčkových souborů a lokálními mezipaměťmi dokážou klonování a stahování změn plně vytížit 10Gb/s síťové linky, a to pouze s běžnými virtuálními stroji cloudového poskytovatele.
U většiny serverů Gitu je pro udržení výkonu rostoucího úložiště nutné opakované zabalení a odstraňování nepotřebných objektů. Oba kroky uvolňují místo, ale jsou náročné na výpočty i I/O.
V rychle se měnícím úložišti, jako je monorepozitář OpenAI, není praktické posílat samostatné URI balíčkového souboru pro každé odeslání změny provedené od posledního stažení změny. Klient by musel každý den stáhnout desítky tisíc balíčkových souborů, aby aktualizoval svou hlavní větev.
Pando místo toho slučuje balíčky na pozadí, čímž tuto režii snižuje. Používá přitom návrh podobný kompakci v LSM stromech. Každé odeslání změny vytvoří balíčkový soubor, který Pando označuje jako „L0“ a který představuje nejmenší jednotku dat, již Pando dokáže přenést. Procesy na pozadí postupně sdružují tyto balíčkové soubory do skupin pokrývajících posloupnost verzí referencí. Velikost skupin roste od L0 geometricky. Náš monorepozitář dnes používá skupiny balíčkových souborů až do úrovně L4.
Tyto skupiny plní dvojí účel. Jakmile je skupina dostatečně zaplněná, převezme ji proces kompakce a vytvoří z ní jediný balíčkový soubor. Přeuspořádáním objektů uvnitř balíčkového souboru se zlepší kompresní poměr. Zároveň se zrychlí vyjednávání stažení změny, protože skupiny fungují jako přeskokový seznam nad verzemi referencí. Souběžným procházením indexů na jednotlivých úrovních může Pando vybrat sadu balíčků pro klienta v logaritmickém čase. Tyto skupiny balíčkových souborů, ať už znovu zabalené, nebo ne, umožňují efektivnější plánování stahování změn a snižují nároky na výkon i latenci backendu Pando.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
Kompakce na pozadí slučuje balíčkové soubory z mnoha verzí referencí, a umožňuje tak efektivnější stahování změn s menším počtem souborů.
Na rozdíl od mnoha serverů Gitu se balíčkové soubory uchovávají na všech úrovních a nezahazují se ani neodstraňují při úklidu nepotřebných dat. Balíčky ze všech úrovní průběžně slouží klientům podle položek have a want, které klienti vyžadují při stahování změn. U klientů používajících Pando jako API navíc načtení libovolného objektu z balíčkového souboru obvykle vyžaduje přečíst méně dat z L0 než z L4, protože je méně pravděpodobné, že bude objekt komprimovaný.
Kompakce v backendu Pando výrazně snižuje celkový objem dat přenášených klientům a zároveň umožňuje, aby náročnost plánování balíčků rostla sublineárně s tím, jak moc klient zaostává. To je zvlášť důležité pro velká a rychle se měnící úložiště, která se podle nás stanou novým standardem vývoje softwaru.
Pando jsme vytvořili s pomocí Codexu a od zahájení projektu k produkčnímu nasazení jsme se dostali za pouhých 10 týdnů. Jádro backendu Pando navrhl jediný vývojář, implementovaly jej týmy agentů a nasadila malá skupina vývojářů. Pando začalo obsluhovat produkční provoz v dubnu letošního roku a je jednou ze součástí širšího systému nástrojů pro správu verzí používaných v OpenAI.
Při ověřování návrhu backendu Pando jsme využili širokou škálu přístupů. Uplatnili jsme naše zásady pro budování bezpečnějších softwarových základů. Protože šlo o projekt na zelené louce, rozhodli jsme se Pando implementovat v Rustu a bez závislostí na starších systémech. Napsali jsme sadu testů shody, aby agenti mohli své změny ověřovat vůči standardnímu chování Gitu. Vyvinuli jsme také rámce pro deterministické simulační testování, které nám umožňují ověřovat invarianty a vlastnosti systému. Díky nim jsme mohli odhalovat a omezovat nenápadné souběhové chyby i regrese ve vyjednávání a plánování balíčků.
Na tomto systému se podílelo mnoho modelů. Jakmile byly dostupné nové modely, využívali jsme je ke zlepšování výkonu a spolehlivosti Panda. S využitím GPT‑6 Astra agenti psali v jazycích Lean a TLA+ formalizovanou „teorii Panda“, kterou jsme použili k hledání a opravám chyb v implementaci i k dokazování vlastností systému. Několik nepřetržitých relací běželo stovky hodin a hledalo a opravovalo okrajové případy v backendu Pando. Také nepřetržitě provozujeme agenty s modely se schopnostmi Daybreak, kteří adversariálně testují bezpečnost Gitu a Panda.
Pando jsme nejprve nasadili jako proxy, která přeposílala čtení i zápisy do našeho stávajícího systému správy verzí. Díky tomu jsme mohli Pando a předchozí systém udržovat synchronizované a bezpečně přesouvat provoz na Pando bez výpadků.
V rámci svého systému mezipamětí umí Pando před předáním požadavků také odstranit duplicity a požadavky sloučit. To nám umožnilo udržet stávající nástroje v dobrém provozním stavu, zatímco jsme systémy jeden po druhém převáděli na Pando. S tím, jak jsme na Pando převáděli další úlohy, jejich odesílání změn plnilo mezipaměť Panda a zvyšovaly spolehlivost našeho předchozího backendu.
Abychom optimalizovali výkon při používání Panda, začali jsme naši veřejnou distribuci Gitu dodávat na počítače vývojářů a v rámci desktopové aplikace ChatGPT. Klienty jsme na Pando převáděli postupně pomocí interní obalové vrstvy nad rozhraním příkazového řádku Gitu. Vytvořili jsme řadu dalších nástrojů pro zpracování žádostí o změny a revize kódu i API pro automatizace. Spolu s provozem Gitu jsme migrovali stovky integrací závislých na starších API, webhookách a stahování úložišť.
S přechodem vývojářům a týmům pomáhal sám Codex. Vytvořili jsme dovednosti pro přípravu stávajících pracovních kopií a pro vedení agentů při používání nového systému. Vývojáři tak mohli jednoduše požádat Codex, aby migroval jejich vývojová prostředí a služby.
Přechod na Pando zvýšil spolehlivost slučování a snížil medián latence odesílání změn téměř o 80 %.
Pando dokáže dodat petabajt dat Gitu denně s pouhými několika procesorovými jádry. Za jedinou hodinu Pando poskytlo více než 57 terabajtů balíčků Gitu, přičemž přes 99 % těchto dat pocházelo z lokální diskové mezipaměti.
Opakované využívání balíčků umožňuje mnoha klientům stahovat stejnou historii úložiště s minimem výpočtů na obslužných uzlech. Tento příklad ukazuje jediné úložiště, ale Pando je navržené tak, aby jich podporovalo mnohem více. S rostoucí poptávkou můžeme doručování dat a vyjednávání protokolu Git škálovat nezávisle.
Díky tomu, že jsme Pando postavili na protokolu a klientovi Gitu, jsme odhalili chyby i příležitosti ke zlepšení výkonu samotného Gitu.
Pando například poskytuje opakovaně použitelné balíčky přímo klientům, takže jejich stahování a indexování se stalo klíčovou součástí zajištění rychlosti a spolehlivosti systému. Tato práce odhalila okrajové případy při souběžném stahování, úklidu po přerušených přenosech a obnovování stahování, která už byla dokončena. Tyto části kódu jsme opravili, přidali kontroly při přijímání existujícího balíčku pro stahování změn a přispěli zpět do hlavního projektu.
Také jsme potřebovali vidět, kde se spotřebovává čas a přenosová kapacita. Přidali jsme proto trasování stahování přes funkci packfile-uri a počítadla bajtů při vytváření balíčků. Díky tomu lze snáze určit, zda pomalé stahování změn tráví čas vytvářením balíčků, nebo jejich přenosem.
V sestavení Gitu od OpenAI(otevře se v novém okně) může klient nezávislé balíčky stahovat a indexovat paralelně. Přidali jsme koncept „explicitně dostupných commitů“, aby server mohl oznámit aktuální koncový commit větve odděleně od historie, kterou smí klient při odeslání změn ze svého balíčku vynechat. V backendu Pando to klientům umožňuje pomocí funkce --force-with-lease ověřit, že se vývojová větev neposunula, a sestavit balíček pouze vůči hlavní větvi. Kdokoli s touto historií hlavní větve pak může danou větev stáhnout pomocí stejného balíčku.
Několik těchto vylepšení už je součástí hlavního projektu Git a mnoho dalších je v našem veřejném úložišti. Dál spolupracujeme s komunitou Gitu, aby tato vylepšení byla užitečná i mimo backend Pando. Tuto práci můžete sledovat v našem veřejném přehledu patchů(otevře se v novém okně).
Díky přehodnocení zažitých přístupů ke škálování úložišť Gitu nám Pando umožňuje škálovat náš monorepozitář, aniž bychom měnili pracovní postupy v Gitu, které naši vývojáři a agenti už používají. V našem monorepozitáři Pando umožnilo vyšší míru paralelismu, zvýšilo spolehlivost slučování a zkrátilo dobu potřebnou ke zveřejnění změn kódu. Vedle monorepozitáře zkoumáme i další využití Pando v OpenAI, včetně úloh, při kterých musí velké množství agentů spolupracovat na sdíleném úložišti Gitu.
S tím, jak vývojáři a agenti vytvářejí více softwaru, bude i více týmů mimo OpenAI narážet na limity správy verzí, na které jsme narazili my. V současnosti zkoumáme, jak technologii, na níž Pando stojí, zpřístupnit našim uživatelům.
Pando staví na dvaceti letech zkušeností uživatelů a provozovatelů úložišť Gitu. Pando budeme dál rozvíjet a ve spolupráci s open-source komunitou Gitu sdílet veškerá vylepšení, která mohou prospět širšímu okruhu uživatelů Gitu.


