OpenAI

2026. november 2.

Mérnöki tevékenység

A Git skálázása az OpenAI-nál

Az ügynökök léptékéhez igazodó verziókezelés kiépítése a Git-ökoszisztéma fejlesztésével párhuzamosan.

Szerzők: Aaron Friel, Taylor Blau és Ted Nyman, a műszaki csapat tagjai

Betöltés…

Az elmúlt évben monorepónk mérete és a vele szemben támasztott igények elképesztő ütemben nőttek. Egyértelművé vált, hogy meg kell újítanunk verziókezelő rendszerünket, hogy lépést tarthasson a Codex által lehetővé tett párhuzamos munkavégzés mértékével.

Létrehoztuk a Pandót, a Git-adattárak új háttérrendszerét, anélkül, hogy megszakítottuk volna a fejlesztői munkát vagy megváltoztattuk volna csapatunk Git-használatát. Ma már az OpenAI monorepójában minden fejlesztés a Pandóra épül. A Pandót két ambiciózus célra terveztük. Az első a horizontálisan skálázható írás, sok ágra történő egyidejű feltöltésre optimalizálva. A második az olvasási adatátviteli egység maximalizálása, hogy a kliensek elég gyorsan tölthessék le az adatokat a rendelkezésükre álló hálózati sávszélesség teljes kihasználásához.

E célokat a Git protokolljával és kliensével való kompatibilitás megőrzésével értük el, így mérnökeink és ügynökeink zökkenőmentesen illeszthették be a Pandót meglévő munkafolyamataikba. Éles környezetben a Pando közel az ötödére csökkentette a feltöltések medián késleltetését. Ma már naponta közel egy petabájtnyi Git-adatot szolgál ki.

Bemutatjuk, miért építettünk új verziókezelő rendszert, és miért döntöttünk a Git-kompatibilitás megőrzése mellett. Összehasonlítjuk rendszerünket a Git skálázásának elterjedt megközelítéseivel, és leírjuk, miért különösen alkalmas a Pando az OpenAI skálázhatósági igényeinek kielégítésére. Kitérünk a következő lépésekre is, és arra, hogyan járulunk hozzá továbbra is a Git fejlesztéséhez az ügynökök munkájának léptékéhez igazodva.

Miért volt szükségünk új verziókezelő rendszerre?

A Codex megváltoztatta a fejlesztés léptékét és ütemét az OpenAI-nál: a mérnökök jóval több munkát végezhetnek párhuzamosan. Ez több ágat, feltöltést és egyesítést, valamint több lekérést és klónozást jelent a fejlesztői környezetek, kutatási feladatok és CI-folyamatok részéről.

Ezek az igények egymást erősítik. Egy mérnöknek ma már egyszerre több módosítása is készen áll a CI-re, és minden feltöltés több olyan feladatot indíthat el, amely ugyanazt a commitot kéri le.

Nagyjából a tavalyi év végétől a monorepónkat kiszolgáló rendszerek egyre nehezebben tartottak lépést. A lekérések és feltöltések lelassultak, az egyesítések meghiúsultak, a megbízhatóság pedig időnként 90% alá esett. Vajon magának a Gitnek a korlátját értük el, vagy annak, ahogyan a monorepónkat kiszolgáltuk? A Git egy olyan közösségből nőtt ki, amely e-mailben cserélt kerneljavításokat. A fejlesztők több mint két évtizede igazítják eszközeiket a Githez, miközben a szoftverkészítés módja folyamatosan változik. Amikor saját monorepónkban nehézségeket kezdtünk tapasztalni, fontos volt számunkra, hogy továbbra is erre a munkára építhessünk.

Láttunk lehetőséget a megszokott munkafolyamat megőrzésére úgy, hogy közben újraépítjük a háttérrendszert: az párhuzamosan kezelheti a különböző ágakra történő feltöltéseket, és egyszerre sok olvasónak szolgálhatja ki ugyanazt az előzményt. Mivel verziókezelési munkafolyamatunkat a Gitre alapoztuk, a kliens fejlesztései az OpenAI-on kívül is hasznosak lehetnek. Szerettük volna ezeket a fejlesztéseket a saját Git-változatunkból⁠(új ablakban nyílik meg) visszajuttatni az eredeti projektbe, megosztva őket a szélesebb közösséggel.

Mérnöki megoldások a Git nagy léptékű használatához

A verziókezelő rendszerek skálázásának két alapvető dimenziója van: sok író tölt fel különböző ágakra, és sok olvasó kéri le ugyanazt az előzményt. A Pando mindkettőre optimalizál.

Először is, a Pando minden ág frissítéseit egymástól függetlenül koordinálja. A független ágak frissítései párhuzamosan haladhatnak, és az ágat lekérő olvasó megkapja az adott verzióhoz szükséges előzményt. Ennek ára, hogy a több ágat vizsgáló olvasó eltérő időpillanatokhoz tartozó állapotukat láthatja.

Másodszor, a Pando újrafelhasználja a csomagfájlokat a lekérések között, ahelyett, hogy minden kliensnek külön készítene egyet. A csomagfájl a Git több objektum együttes tömörítésére szolgáló formátuma. A kliens így néhány már meglévő objektumot is letölthet, cserébe a szerver kevés számítással sok kliensnek szolgálhatja ki ugyanazokat a bájtokat.

Ezek a döntések lehetővé teszik az írások ágak közötti skálázását és az olvasások gyorsítótárból való kiszolgálását. Hogy megértsük, hogyan kapcsolódnak egymáshoz, először nézzük meg, miként tárolja és továbbítja a Git az előzményeket.

A Git objektumtárolásának alapjai

A Gitnek az adattár két részét kell szinkronban tartania: az objektumokat és a referenciákat.

A Git-adattár a fájlok pillanatképeit és az azokat összekapcsoló előzményt objektumgráfként rögzíti. Egy objektum blob, fa, commit vagy címke lehet. A blobok egy-egy fájl tartalmát tárolják, a fák pedig neveket rendelnek blobokhoz vagy más fákhoz, így alakítva ki a könyvtárstruktúrát. A commitok egy gyökérfára és nulla vagy több szülőcommitra mutatnak, létrehozva az adattár előzményét.

Az objektumokat a típusukból és tartalmukból képzett hash azonosítja. Az objektumok megváltoztathatatlanok is: egy módosított fájl rögzítése új blobot hoz létre, míg a változatlan fájlok és könyvtárak újrafelhasználhatják a meglévő objektumokat.

Az ágak és címkék commitokra mutató referenciák, röviden „refek”. Ha egy ágon commitot készítünk, létrejön az új commit, és az ág referenciája frissül, hogy arra mutasson.

A refs/heads/main ág a C2 commitra mutat, amelynek szülője C1. C1 a T1 gyökérfára mutat, amelyben a parser.py a B1 blobra hivatkozik. C2 a T2 gyökérfára mutat, amelyben a parser.py a B2 blobra hivatkozik. Mindkét gyökérfában a docs/ ugyanarra a fára, T3-ra hivatkozik. Ennek README.md bejegyzése a B3 blobra, a usage.md pedig a B4 blobra mutat.

A Git-objektumok azonosító alapján hivatkoznak egymásra: a commitok fákra, a fák pedig más fákra és blobokra. A refs/heads/main ág a C2 commitra mutat, amelynek szülője C1. C1 a T1 gyökérfára mutat, amelyben a parser.py a B1 blobra hivatkozik. C2 a T2 gyökérfára mutat, amelyben a parser.py a B2 blobra hivatkozik. Mindkét gyökérfában a docs/ ugyanarra a fára, T3-ra hivatkozik. Ennek README.md bejegyzése a B3 blobra, a usage.md pedig a B4 blobra mutat.

A Git az objektumokat „különálló” vagy „csomagolt” állapotban tárolja. A különálló objektumokat egyedi fájlok tárolják, amelyeket az objektumazonosítójuk azonosít. A csomagolt objektumok közösen, egy csomagfájlban találhatók, így a hasonló objektumok egymáshoz viszonyított különbségként tárolhatók.

A parser.py két verziója látható a $GIT_DIR/objects alatt. Különálló objektumként az A blob a parse() függvényt, a B blob pedig a parse() és a parse_file() függvényt is tartalmazza. A csomagfájl A-t teljes egészében tárolja, B-t pedig olyan utasításokkal írja le, amelyek átmásolják A egészét, és hozzáfűzik a parse_file() függvényt. A hozzá tartozó *.idx fájl objektumazonosító alapján adja meg mindkét blob helyét a csomagfájlban.

A Git külön fájlokban tárolhatja a blobokat, vagy közös csomagba rendezheti őket, az egyik blobot a másikhoz képest vett különbségként kódolva.

A Gitben egy objektum akkor érhető el egy commitból, ha a Git a commitból indulva, a szülőkre, fákra és blobokra mutató hivatkozásokat követve meg tudja találni. Lekéréskor a kliens közli a szerverrel, mely commitokat kéri, és melyekkel rendelkezik már. A szerver megkeresi a kért commitokból elérhető objektumokat, kizárja azokat, amelyekről tudja, hogy a kliensnél már megvannak, a maradékot pedig a Git lemezen használt formátumával megegyező csomagfájlban küldi el. A teljes klónozás meglévő objektumok nélkül indul, ezért szüksége van a klónozott előzményből elérhető összes objektumra.

A feltöltés az ellenkező irányba továbbítja az objektumokat, és egy vagy több referencia frissítését kéri a szervertől. A kliens a szerver által meghirdetett referenciák halmaza alapján kerüli el a szerveren már meglévő objektumok elküldését. Az elküldött csomag tehát meglévő objektumoktól függhet: önmagában a csomag fogadása nem elég az új előzmény rekonstruálásához.

Egy Git-adattár replikálása azt jelenti, hogy az objektumokat és a referenciákat is több szerveren tesszük elérhetővé. Mielőtt egy replika kiszolgálhatná egy referencia új értékét, képesnek kell lennie az abból elérhető objektumok biztosítására. A szolgáltatásnak a versengő referenciafrissítéseket is koordinálnia kell, vagyis azokat a feltöltéseket, amelyek ugyanazt az ágat ugyanarról a régi ágvégről különböző új commitokra próbálják átállítani. Az adattár összes írását egyetlen sorrendbe rendező rendszerekben a független ágakra történő feltöltések is várakozásra kényszeríthetik egymást.

Az elosztott Git-szolgáltatások bevett mintái

A Pando tervezésekor megvizsgáltuk a Git-alapú verziókezelés skálázásának hagyományos megközelítéseit. Az egyik háromfázisú véglegesítéssel (3PC, a véglegesítést több csomópont között koordináló protokoll) hangolja össze az adattár replikáinak referenciafrissítéseit. A másik a blobtárolóban elhelyezett közös előzetes naplóval (WAL, a változtatásokat alkalmazásuk előtt rögzítő napló) rendezi sorba a frissítéseket.

Háromfázisú véglegesítés (3PC)

Az általunk modellezett háromfázisú véglegesítési megoldásban a Git-szolgáltatás több replikán tartja meg az adattár teljes másolatát. Amikor bármelyik szerver feltöltést fogad, a beérkező csomagfájlt lemásolja, és párhuzamosan elküldi az összes replikának. A feltöltés közzétételéhez a replikák többségének el kell fogadnia a referenciafrissítést. Az ezt irányító szerver a koordinátor; a folyamat több kommunikációs kört igényel közte és az összes többi replika között.

A referenciafrissítés elfogadásához minden replika zárolja a referencia saját példányát, és ellenőrzi, hogy az továbbra is a kliens által várt commitra mutat-e. A koordinátor ezután előzetes véglegesítési üzenetet küld az elfogadó replikáknak. Amint a többség nyugtázta az előzetes véglegesítési üzenetet, a koordinátor utasítja őket a frissítés végrehajtására. Ezután bármely naprakész replika meghirdetheti az új ágvéget az adattár helyi másolatából.

A teljes replikák fenntartása lehetővé teszi az olvasások helyi kiszolgálását, és garantálja, hogy a kliensek minden referenciáról konzisztens nézetet kapjanak, bármelyik replikát választják is. Ennek következtében a referenciák egyesével frissülnek, és minden feltöltés több oda-vissza kommunikációs kört igényel a referenciafrissítés replikák közötti koordinálásához.

A kvórumra várakozás miatt a replikák lemezteljesítménye és a kommunikáció késleltetése közvetlenül befolyásolja a feltöltést. Ahogy nő az írások mennyisége, a replikáknak az olvasási forgalom mellett a beérkező csomagokkal is lépést kell tartaniuk.

Előzetes naplózás (WAL)

Az általunk modellezett WAL-alapú megoldásban a Git-szolgáltatás egy blobtárolóban lévő közös manifest segítségével rendezi sorba az adattár frissítéseit. Amikor egy szerver feltöltést fogad, a beérkező csomagfájlt blobtárolóba tölti, és az adattár helyi másolatában előkészíti a referenciafrissítést. A feltöltés közzétételéhez egy bejegyzést ad a blobtárolóban lévő közös manifesthez, amely rögzíti a referenciafrissítést, és a csomagfájlra mutat. A Git-szerver ezután összehasonlítás és csere művelettel frissíti a manifestet. Ez atomikusan lecseréli a manifestet, vagy elutasítja a módosítást. Ha időközben egy másik író frissítette, a szerver beolvassa a közben bekerült bejegyzéseket, ellenőrzi, hogy referenciafrissítése továbbra is érvényes-e, majd a már feltöltött csomagfájllal újrapróbálkozik.

Az írók párhuzamosan tölthetik fel csomagfájljaikat, de még a független ágakra történő feltöltésekhez is ugyanazt a manifestet kell frissíteni. Egy sikeres összehasonlítás és csere oda-vissza ideje korlátozza, milyen gyakran tehet közzé az adattár új manifestet. Az alacsony késleltetésű blobtároló gyorssá teszi ezt a lépést. Kötegelt feldolgozással egy manifest egyszerre több referenciát is frissíthet, ennek ára azonban a frissítések sorba állítása és a nagyobb késleltetés.

A blobtároló párhuzamosan fogadhat csomagfájlokat, de a közös manifest frissítései továbbra is versengenek egymással. Önmagában a blobtároló kapacitásának növelése nem oldja fel ezt a szűk keresztmetszetet. Minden replikának továbbra is be kell olvasnia és indexelnie kell a csomagokat a blobtárolóból, mielőtt helyben kiszolgálhatná őket. Ha az írások ennél gyorsabban érkeznek, a replikák lemaradnak, és a klienseknek tovább kell várniuk a frissen feltöltött commitok lekérésére.

A Pando architektúrája

Ezzel el is érkeztünk a Pandóhoz. A Pando új architektúrát kínál a Git-adattárak kiszolgálására, azzal a céllal, hogy sok, egyszerre feltöltő kliensre skálázható legyen. Így működik:

A megkötések újragondolása

A Pando fejlesztése előtt úgy sejtettük, hogy a Git feladása nélkül is teljesíthetjük a követelményeinket. Néhány olyan elvárásból azonban engednünk kellett, amelyet a legtöbb Git-rendszer alapkövetelménynek tekint:

  • Az egész adattárra kiterjedő konzisztencia: Az összes referencia konzisztens, adott időpillanatot tükröző pillanatképéhez az egész adattárra kiterjedő koordináció szükséges. Ha egy rendszer az összes referenciafrissítést közös sorrendbe rendezi, a független ágakra történő feltöltések egyetlen koordinációs pontért versengenek.

  • Egyedi csomagfájl minden kliensnek: Ha két, eltérő helyi előzménnyel rendelkező kliens ugyanazt az ágat kéri le, a szervernek külön csomagot kellhet előállítania mindkettőnek, hogy ne küldjön el már meglévő objektumokat. Ezeknek a válaszoknak az előállítása CPU-kapacitást és lemez-I/O-t igényel, ami megnehezíti sok olvasó kliens kiszolgálását.

A Pando ehelyett lazább konzisztenciamodellt (kauzális konzisztenciát) biztosít az írások skálázásához, az olvasásokat pedig a csomagfájlok újrafelhasználásával skálázza.

Kauzális konzisztencia (CC)

Amikor egy kliens feltölt egy ágra, a Pando a beérkező csomagfájlt megváltoztathatatlan blobként menti egy blobtárolóba. Az ág minden elmozdulásakor a Pando egy referenciaverziót rögzít egy metaadattárolóban: ez tartalmazza az új ágvéget, az azt biztosító csomagot és az összes korábbi referenciaverziót, amelytől a csomag függ. A blobtároló és a metaadattároló egyaránt horizontálisan skálázható.

Egy commit vagy referencia lekéréséhez a Pandónak referenciaverziók (csomagfájlok) sorozatát kell átadnia a kliensnek, rekonstruálva annak a kliensnek az állapotát, amely a lekért referenciaverziót létrehozta. Ez az egész adattárra nézve gyengébb konzisztenciát biztosít, miközben az egyes referenciák frissítései sorosíthatók maradnak. A kliensek csak akkor ütköznek, ha egyszerre töltenek fel ugyanarra a referenciára. A több ágat olvasó kliens nem feltételezheti, hogy ezek az olvasások az adattár ugyanazon időpillanatbeli állapotát írják le.

Bal oldalon a főág három egymást követő csomagja az A, B és C; a D és E; illetve az F commitot tartalmazza. Jobb oldalon a főág és a témaág a C commitnál válik szét. A főág a G commitig halad, míg a témaág a D, E és F commiton keresztül fejlődik. A témaág legújabb csomagja a D, E és F commitot tartalmazza. Szaggatott körvonal jelzi, hogy D és E a témaág korábbi csomagjában is szerepel. A kék nyíl jelzi, hogy az új csomag a főág G commitjánál lévő állapotától függ. F Git szerinti szülője E.

A főágra történő feltöltés egy vagy több commitot tartalmazó inkrementális csomagot ad hozzá. A mellékágra történő minden feltöltés újra tartalmazza a témaág korábbi commitjait, így a főág meghirdetett verziójával rendelkező kliens egyetlen csomagban lekérheti a témaágat.

Ez a kauzális konzisztencia egyik formája. Egy referencia lekéréséhez az olvasó annak az író kliensnek az előzményét látja, amely a referenciát létrehozta. Feltöltés közben a Pando szabályozza, hogy a kliens és a szerver milyen közös előzményben állapodik meg, így a kliensek könnyebben újrafelhasználható csomagfájlokat állítanak elő.

Ennek következtében a Pando a Git-műveletek során nem biztosít az összes referenciára kiterjedő, egyetlen időpillanathoz tartozó állapotot, sem a referenciafrissítések sorrendjét egy klónozáson vagy lekérésen belül. Tegyük fel például, hogy a foo és a bar ág is frissül egy teljes klónozás egyeztetési szakaszában. Az egyeztetés során az egyik kliens a foo ág 1. és a bar ág 2. verzióját láthatja, míg egy másik a foo ág 2. és a bar ág 1. verzióját. A gyakorlatban a felhasználók és az ügynökök nem a referenciafrissítések globális sorrendjére támaszkodnak, hanem egy-egy referencia frissítéseinek sorrendjére.

A megközelítések összehasonlítása

A három konzisztenciamodell szimulációjában 1000 klienst állítunk be úgy, hogy mindegyik külön ágat válasszon, majd ismételten egyedi commitokat töltsön fel. Közben mérjük a teljes tényleges futási időt és a feltöltéstől a lekérhetőségig eltelt időt. Az utóbbi fontos mutató a munkaterhelések szempontjából: azt méri, mennyi idő kell ahhoz, hogy a felhasználók és ügynökök együttműködhessenek, vagy más, verziókezelésre épülő rendszereket használhassanak. A szimuláció az általános célú megoldásoktól a prémium kategóriáig modellez virtuális gépeket és blobtároló rendszereket. Ebben a szimulációban a háromfázisú véglegesítésnél lazítottunk az egész adattárra kiterjedő konzisztencián.

Simulating Git Server Implementations

Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.

Three-phase commit

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Object-store WAL

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Pando

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Writer progress through verified fetches

  • Three-phase commit
  • Object-store WAL
  • Pando
05001,00000 s
  • Waiting
  • Pushing
  • All pushes acknowledged
0 sLater

Synthetic teaching models, not production benchmarks. Mean push to fetch covers topic pushes, from the first attempt through retries until an independent reader verifies that commit. It includes waiting for a reader slot and excludes builds and tests. With merges enabled, writer completion includes the required fetches and trunk merge.

Experiment settings

Preparing simulation…

Results

Play the comparison to see the recorded results.

Synthetic teaching models, not production benchmarks. Mean push to fetch covers topic pushes, from the first attempt through retries until an independent reader verifies that commit. It includes waiting for a reader slot and excludes builds and tests. With merges enabled, writer completion includes the required fetches and trunk merge.

Futtasd ezt a szimulációt, amely a Pandót két másik megközelítéssel hasonlítja össze. Válassz infrastruktúrát az általános célú hardvertől a prémium kategóriáig, állítsd be a kliensek számát és a többi paramétert, majd hasonlítsd össze az eredményeket.

A modellezett háromfázisú véglegesítési rendszerben terhelés alatt jelentősen nő az írási késleltetés, ahogy a lemezek elérik kapacitásuk határát. Az előzetes naplózást használó rendszereknél ugyanez az olvasási késleltetést növeli jelentősen, mert a szerverek nehezen tartanak lépést a napló olvasásával. A monorepónkon végzett valós tesztekben és ebben a szimulációban is sok ezer kliens tölthet fel párhuzamosan a Pandóval anélkül, hogy terhelés alatt romlana a teljesítmény.

Feltöltéskor a Pando a beérkező csomagfájlt a blobtárolóba továbbítás közben ellenőrzi, anélkül, hogy kicsomagolná egy helyi adattárba. Amikor egy kliens lekérést indít, a Pando kiválasztja a számára szükséges tárolt csomagokat, a kliens pedig letölti őket. Egyetlen szervernek sem kell csomagfájlokat alkalmaznia egy helyi adattárra, sem költséges ki- és becsomagolási műveleteket végeznie. Mivel a szerverek elsősorban egyeztetést és csomagfájl-kiválasztást végeznek, a Pando általános célú hardveren is futtatható.

Így a Pando a kliensek, a kliensek és a Pando közötti hálózat, illetve a blobtároló szolgáltatás korlátaiig skálázható. Elegendő kapacitás mellett a független ágakon dolgozó kliensek mindegyike a saját kapcsolata által megengedett sebességgel továbbíthat csomagokat. Ha több klienst szeretnénk ugyanilyen sebességgel kiszolgálni, további Pando-szervereket adhatunk a rendszerhez, és velük együtt bővíthetjük a metaadattárolót és a blobtárolót.

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.

Stream the push

  • Packfile bytes
  • Metadata
  • Request / acknowledgment
  • Packfile URIs

Egy ügynök helyben szerkeszti a fájlokat, commitot készít, majd futtatja a git push parancsot, amely csomagfájlt hoz létre a módosításokból az ág frissítéséhez. A Pando fogadja a csomagot, és folyamatosan továbbítja a blobtárolóba. Miután egyetlen dekódolási menetben ellenőrizte a beérkező feltöltést, a Pando több kulcsra kiterjedő tranzakcióval rögzít egy referenciaverziót a metaadattárolónkban, és atomikusan frissíti a referenciamutatót. Így a Pando „saját írás visszaolvasása” típusú konzisztenciát biztosít. A teljes írási művelet O(1)O(1) metaadat-olvasást és -írást igényel.

Amikor egy másik kliens lekéri az ágat, a Pando a kliens meglévő előzménye alapján kiválasztja a tárolt csomagok megfelelő halmazát. Ez a művelet O(log⁡Δ)O(\log \Delta) metaadat-olvasást igényel, ahol Δ\Delta a kliens meglévő és kívánt állapota közötti legrövidebb távolság, referenciaverziókban mérve. A szerver visszaadja a csomagfájlok URI-jait, a kliens pedig az Express szolgáltatáson keresztül tölti le őket, amely regionális gyorsítótárakban tárolja a csomagfájlokat.

Ez a megoldás a Pando architektúrájának három központi szolgáltatására épül: a feltöltésre, a lekérésre és az összevonásra.

A feltöltések skálázása

Feltöltéskor a hagyományos Git-szerver úgy viselkedik, mint egy pókerjátékos, aki kiteríti az összes lapját: minden meglévő ágát felfedi a kliens előtt. A kliens ezután összegyűjti a commitokat, fákat és blobokat, hogy olyan csomagfájlt hozzon létre, amely kihagyja azokat az objektumokat, amelyekről tudja, hogy a szerveren már megvannak.

Több referencia meghirdetésével csökkenthető a feltöltés mérete. Ez viszont megnehezítheti a csomag újrafelhasználását, mert egy másik kliensnél hiányozhatnak az író által kihagyott objektumok.

A Pando más megközelítést alkalmaz. Ahelyett, hogy minden lapját megmutatná, csak néhány kiválasztott ágat hirdet meg a kliensnek. Ennek két oka van:

  1. Az összes referencia konzisztens nézetének biztosítása költséges. Több millió ág esetén már önmagában a referenciák meghirdetése is meghaladhatja a 100 MiB-ot. Ehhez minden szervernek vagy naprakész leképezést kell fenntartania jelentős koordinációs többlettel, vagy olyan adatbázis-lekérdezést kell futtatnia, amelynek költsége a referenciák számával nő.

  2. Ha a kliens csak a referenciák egy részét kapja meg, olyan csomagot állít elő, amely kizárólag attól függ, amit a szerver kifejezetten megengedett kihagyni belőle. A függőségek nyomon követésével a Pando újrafelhasználhatja a kliens által egy feltöltéskor létrehozott csomagfájlt, és egy későbbi lekérés válaszaként szolgálhatja ki.

Minél több referenciát hirdet meg egy Git-szerver, annál több forrás alapján hagyhatnak ki objektumokat a feltöltő kliensek, így kisebb csomagokat készíthetnek. E csomagfájlok újrafelhasználásához azonban a szervernek bizonyítania kell tudni, hogy a lekérő kliensek az összes függőséggel rendelkeznek. Ellenkező esetben a csomagfájlból hiányozhatnak a kliens számára szükséges objektumok.

Ha viszont a Git-szerver egyetlen referenciát sem hirdet meg, a kliens az adattár teljes előzményét tartalmazó csomagfájlt állít elő. Ezt a csomagfájlt bármely más kliens használhatja, de ennek nagy ára van: bármely ág lekérése gyakorlatilag teljes klónozássá válik.

A meghirdetett referenciák csomagfájlméretre gyakorolt hatását szemléltető görbe: referenciák nélkül a teljes előzmény, a főág meghirdetésével kisebb csomag, az összes referencia meghirdetésével pedig csak az új objektumok.

Több referencia meghirdetése kisebb csomagfájlokat eredményez. A méretcsökkenés nagy része már egyetlen referencia meghirdetésével is elérhető.

Alapértelmezés szerint, amikor egy kliens új funkcióágat tölt fel, a Pando csak egy kijelölt főágat hirdet meg alapként. Így a meghirdetés mérete akkor sem nő, ha új ágak kerülnek az adattárba, ennek azonban vannak hátrányai. Ha egy mérnök sokszor tölt fel ugyanarra az ágra, vagy egymásra épít funkcióágakat, a kliensnek újra el kell küldenie mindent, ami nincs benne a főágban.

Emiatt az egymást követő ágfeltöltések nagyobbak lesznek, esetleg redundáns objektumokat is tartalmaznak, és több sávszélességet használnak a kliensnél. Monorepónkban az így előállított csomagfájlok medián mérete 20 KiB alatt, a 99. percentilis pedig 3 MiB alatt volt. Számunkra ez észszerű kompromisszum volt, mert a főág meghirdetett előzményével már rendelkező olvasó egyetlen csomagfájllal lekérheti az ágat.

Miután kiválasztotta a kliensnek meghirdetendő referenciákat, a Pando fogadja a feltöltést, és frissíti a metaadattárolóját. A Pando szinkron és aszinkron részre bontja a metaadatok frissítését. A szinkron metaadat-frissítés biztosítja, hogy a kliensek azonnal lekérhessék a frissen frissített commitot. Ez új referenciaverziót ír, és a referenciamutatót erre az új verzióra állítja. A verzió tárolja az új ágvéget, a csomagfájl egyeztetéséhez használt függőségeket és a feltöltött csomagfájlra mutató hivatkozást.

Ezzel egy időben, ugyanabban a tranzakcióban az aszinkron feladatokat is rögzíti, például az összevonás ütemezését. Nagyon nagy feltöltéseknél csak a commitok egy részét írja szinkron módon. Így a feltöltés méretétől függetlenül állandó számú olvasásra és írásra van szükség a metaadattárolóban.

Fontos, hogy a Pando-szerverek egymástól függetlenül fogadhatnak feltöltéseket a nem összefüggő referenciákra. Az egyetlen kulcs, amelyért versengés folyhat, a referenciamutató; ezt atomikus, összehasonlítás és csere jellegű művelettel védjük. Az írási adatátviteli egységet további Pando-szerverekkel, a metaadattároló skálázási egységeivel vagy blobtárolókkal növelhetjük.

Lekérés és klónozás a hálózat teljes sebességével

Lekéréskor a kliens két commitazonosító-halmazt ad át a szervernek; ezeket a protokoll have és want néven ismeri. A hagyományos Git-szerver kiszámítja a kért és a meglévő commitokból elérhető objektumok halmazkülönbségét, tömöríti az objektumokat, majd elküldi az eredményt a kliensnek. Bár a nyílt forráskódú Git-szerver többféle helyi indexszel gyorsítja ezt a műveletet, e csomagfájlok előállítása költséges lehet, és általában nehezen gyorsítótárazhatók.

A Pando ehelyett a feltöltéskor rögzített commit-leképezésekben keresi meg a kliens have és want elemeit, és az azokat tartalmazó referenciaverziók halmazára oldja fel őket. Minden ilyen referenciaverzió egy korábban a Pando által fogadott csomagfájlra hivatkozik. A szerver ez alapján határozza meg, mire van szüksége a kliensnek, és kiválasztja a szükséges objektumhalmazt lefedő csomagfájlokat.

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.

Already heldFetchMissing branch dependency

Fetch 27 trunk packs.

Select a branch to include its trunk dependencies.

A Pando a Git-protokoll meglévő és kért commitjait referenciaverziók sorozatára képezi le. Egy mellékág lekérése a főágon lévő függőségeinek lekérését is magában foglalja.

A Git-protokoll klónozáskor vagy lekéréskor csak egyetlen csomagfájl elküldését engedi a HTTP-válaszban. A Git-protokoll egy kevéssé ismert funkciója, a packfile-uri azonban lehetővé teszi, hogy a szerver további csomagok külön letöltésére szolgáló hivatkozásokat adjon vissza. A Pando kizárólag ezt használja, és magában a válaszban üres csomagfájlt továbbít. E funkció támogatásához az OpenAI több packfile-uri-fejlesztéssel járult hozzá az eredeti projekthez.

Az OpenAI rendszerei számos földrajzi régióban működnek, ezért mindegyikben helyi gyorsítótárakkal rendelkező Pando-klasztereket tartunk fenn. Az Express csomagkiszolgáló szolgáltatás a blobtárolónkat gyorsítótárazza, a metaadattárolóhoz pedig külön gyorsítótár-szolgáltatás tartozik. Ezekkel a gyorsítótárakkal a Pando egy commitazonosítóra irányuló kérést vagy egy referencia speciális olvasását teljes egészében az adott klaszter gyorsítótárazott állapotából szolgálhatja ki. Konzisztens hasheléssel irányítjuk a csomagfájlokra vonatkozó kéréseket az Express-szerverekhez, 99% feletti gyorsítótár-találati aránnyal.

Ez a gyorsítótár-rendszer rendkívül nagy adatátviteli egységet biztosít, így teljesen kihasználhatja a kliensek és a Pando közötti hálózati kapacitást. E csomagfájl-URI-k és helyi gyorsítótárak használatával igazoltuk, hogy a klónozások és lekérések pusztán a felhőszolgáltatók szokásos virtuális gépein is teljesen kihasználják a 10 Gbps-os hálózati kapcsolatokat.

Összevonás és az első bájtig eltelt idő

A legtöbb Git-szervernél újracsomagolásra és szemétgyűjtésre van szükség a növekvő adattár teljesítményének fenntartásához. Mindkét lépés tárhelyet szabadít fel, de számítás- és I/O-igényes.

Egy olyan gyorsan változó adattárban, mint az OpenAI monorepója, nem járható út, hogy az utolsó lekérés óta történt minden feltöltéshez külön csomagfájl-URI-t küldjünk. Egy kliensnek naponta több tízezer csomagfájlt kellene letöltenie, hogy naprakészre hozza a főágát.

A Pando ehelyett a háttérben egyesíti a csomagokat e többletterhelés csökkentésére, a naplóstruktúrájú egyesítőfák összevonási eljárásához hasonló megoldással. Minden feltöltés egy, a Pando által „L0” csomagfájlnak nevezett fájlt hoz létre: ez a Pando által továbbítható legkisebb adategység. Idővel háttérfolyamatok referenciaverziók sorozatát lefedő csoportokba rendezik ezeket a csomagfájlokat, az L0-tól mértani léptékben növekedve. Monorepónk ma az L4 szintig használ csomagfájl-lefedéseket.

Ezek a lefedések kettős célt szolgálnak. Amikor egy lefedés kellően megtelik, egy összevonó folyamat lefoglalja, és egyetlen csomagfájlt készít belőle. Ez a csomagfájlon belüli objektumok átrendezésével javítja a tömörítési arányt. Emellett a lekérési egyeztetés teljesítményét is javítja, mert a referenciaverziók felett ugrólistaként működik. Az egyes szintek indexeinek egyidejű bejárásával a Pando logaritmikus idő alatt választhatja ki a kliensnek elküldendő csomagokat. Ezek a csomagfájl-lefedések újracsomagolva és anélkül is hatékonyabbá teszik a lekérések tervezését, csökkentve a Pando adatátviteli egységére és késleltetésére vonatkozó követelményeket.

Compaction in Pando

Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.

Compaction in progressPublished cover

Az összevonás a háttérben több referenciaverzió csomagfájljait egyesíti, így kevesebb fájllal, hatékonyabban végezhetők a lekérések.

Sok Git-szerverrel ellentétben itt minden szint csomagfájljai megmaradnak; nem dobjuk el és szemétgyűjtéssel sem töröljük őket. Minden szint csomagjait folyamatosan használjuk a kliensek kiszolgálására, a lekérésben megadott have és want elemeik alapján. A Pandót API-ként használó klienseknél pedig egy tetszőleges objektum csomagfájlból való kiolvasása általában kevesebb adat beolvasását igényli L0-ból, mint L4-ből, mert kisebb valószínűséggel tömörített.

A Pando összevonási eljárása jelentősen csökkenti a klienseknek elküldött adatok összméretét, és lehetővé teszi, hogy a csomagtervezés költsége a kliens lemaradásánál lassabb, szublineáris ütemben növekedjen. Ez különösen fontos a nagy, gyorsan változó adattáraknál, amelyek várakozásaink szerint mindennapossá válnak a szoftverfejlesztésben.

A Pando megépítése

A Pandót a Codex segítségével építettük meg: az indulástól az éles üzemig mindössze 10 hét telt el. A Pando magját egyetlen mérnök tervezte, ügynökcsapatok valósították meg, és néhány mérnök helyezte üzembe. A Pando idén áprilisban kezdett éles forgalmat kiszolgálni, és az OpenAI-nál használt verziókezelési eszközök nagyobb rendszerének egyik eleme.

A Pando terveinek ellenőrzésekor sokféle megközelítést alkalmaztunk. Alkalmaztuk a biztonságosabb szoftveralapok kiépítésére⁠ vonatkozó elveinket. Mivel zöldmezős projektről volt szó, úgy döntöttünk, hogy a Pandót Rustban, örökölt függőségek nélkül valósítjuk meg. Megfelelőségi tesztcsomagot írtunk, hogy az ügynökök a Git szokásos működéséhez mérten ellenőrizhessék módosításaikat. Determinisztikus szimulációs teszteléshez is fejlesztettünk végrehajtási környezeteket, amelyekkel ellenőrizhettük a rendszer invariánsait és tulajdonságait. Így az egyeztetés és a csomagtervezés nehezen észrevehető versenyhelyzeteit és regresszióit is felderíthettük és mérsékelhettük.

Sok modell járult hozzá a rendszerhez; ahogy új modellek váltak elérhetővé, a Pando teljesítményének és megbízhatóságának javítására használtuk őket. A GPT‑6 Astra használatával az ügynökök Lean és TLA+ nyelven formalizálták a „Pando elméletét”. Ezzel hibákat találtunk és javítottunk a megvalósításban, valamint bizonyítottuk a rendszer egyes tulajdonságait. Több folyamatos munkamenet futott több száz órán át, hogy felderítse és javítsa a Pando szélső eseteit. Emellett éjjel-nappal futtatunk ügynököket, amelyek Daybreak-képességekkel rendelkező modellekkel támadói szemszögből tesztelik a Git és a Pando biztonságát.

A Pando bevezetése

A Pandót kezdetben a meglévő verziókezelő rendszerünk elé helyezett, az olvasásokat és írásokat továbbító proxyként vezettük be. Így szinkronban tarthattuk a Pandót és a korábbi rendszert, és biztonságosan, leállás nélkül irányíthattuk át a forgalmat a Pandóra.

Gyorsítótárazási megoldásának részeként a Pando a továbbítás előtt a kérések ismétlődéseit is kiszűrheti, és összevonhatja őket. Így a meglévő eszközeink megbízhatóan működhettek, miközben rendszereinket egyenként átállítottuk a Pandóra. Ahogy egyre több munkaterhelést vittünk át a Pandóra, a feltöltések megtöltötték annak gyorsítótárát, és javították a korábbi háttérrendszerünk megbízhatóságát.

A Pando használata melletti teljesítmény optimalizálásához elkezdtük eljuttatni nyilvános Git-disztribúciónkat a fejlesztői gépekre és a ChatGPT asztali alkalmazásába. A klienseket a Git parancssori felülete köré épített belső burkolóprogrammal, szakaszosan állítottuk át a Pandóra. Számos további eszközt készítettünk a módosítási kérelmek és a kódellenőrzés kezeléséhez, valamint API-kat az automatizálásokhoz. A Git-forgalommal együtt több száz, régi API-kra, webhookokra és adattárletöltésekre támaszkodó integrációt is áttelepítettünk.

Maga a Codex is segített a fejlesztőknek és a csapatoknak az átállásban. Készségeket készítettünk a meglévő munkapéldányok előkészítésére és arra, hogy az ügynököket az új rendszer használatában irányítsák. Ennek köszönhetően a fejlesztők egyszerűen megkérhették a Codexet, hogy állítsa át fejlesztői környezeteiket és szolgáltatásaikat.

A Pandóra való átállás javította az egyesítések megbízhatóságát, és közel 80%-kal csökkentette a feltöltések medián késleltetését.

A Pando mindössze néhány CPU-maggal képes naponta egy petabájtnyi Git-adatot kiszolgálni. A Pando egyetlen óra alatt több mint 57 terabájtnyi Git-csomagot szolgált ki, a bájtok több mint 99%-át helyi lemezes gyorsítótárból.

A csomagok újrafelhasználásával sok kliens töltheti le ugyanazt az adattárelőzményt, miközben a kiszolgáló csomópontoknak kevés számítást kell végezniük. Ez a példa egyetlen adattárat mutat be, de a Pandót jóval több támogatására terveztük. Az igények növekedésével az adatkiszolgálást és a Git-egyeztetést egymástól függetlenül skálázhatjuk.

Hozzájárulás a Git projekthez

Mivel a Pandót a Git protokolljára és kliensére építettük, hibákat és lehetőségeket azonosíthattunk magának a Gitnek a teljesítményjavítására.

A Pando például közvetlenül a klienseknek szolgál ki újrafelhasználható csomagokat, ezért ezek letöltése és indexelése központi szerepet kapott a rendszer gyorssá és megbízhatóvá tételében. E munka során szélső esetek kerültek felszínre a párhuzamos letöltéseknél, a megszakadt átvitelek utáni takarításnál és a már befejezett letöltések folytatásánál. Javítottuk ezeket a kódútvonalakat, ellenőrzéseket vezettünk be a meglévő csomagok lekéréshez való elfogadásakor, és a módosításokat visszajuttattuk az eredeti projektbe.

Azt is látnunk kellett, mire megy el az idő és a sávszélesség, ezért nyomkövetést adtunk a packfile-uri-letöltésekhez, a csomaggeneráláshoz pedig bájtszámlálást. Így könnyebb megállapítani, hogy egy lassú lekérés idejét a csomagok előállítása vagy továbbítása emészti-e fel.

Az OpenAI Git-változatában⁠(új ablakban nyílik meg) a kliens párhuzamosan töltheti le és indexelheti a független csomagokat. Bevezettük az „explicit meglévő commitok” fogalmát, hogy a szerver az ág aktuális végét külön hirdethesse meg attól az előzménytől, amelyet a feltöltő kliens kihagyhat a csomagjából. A Pandón ez lehetővé teszi, hogy a kliensek a --force-with-lease kapcsolóval ellenőrizzék, hogy egy funkcióág nem mozdult-e el, a csomagot pedig kizárólag a főág alapján építsék fel. Ezután bárki, aki rendelkezik a főág adott előzményével, ugyanazzal a csomaggal kérheti le az ágat.

E fejlesztések közül több már az eredeti Gitben is megtalálható, sok másik pedig a nyilvános adattárunkban érhető el. Továbbra is együttműködünk a Git közösségével, hogy ezek a fejlesztések a Pandón túl is hasznosak legyenek. Ezt a munkát a nyilvános javításkövetőnkben⁠(új ablakban nyílik meg) követheted.

A következő lépések

A Git-adattárak skálázásáról alkotott bevett elképzelések újragondolásával a Pando lehetővé teszi monorepónk bővítését anélkül, hogy változtatnunk kellene a mérnökeink és ügynökeink által már használt Git-munkafolyamatokon. A Pando nagyobb párhuzamosságot tett lehetővé monorepónkban, javította az egyesítések megbízhatóságát, és csökkentette a kódmódosítások közzétételéhez szükséges időt. A monorepón túl a Pando más felhasználási lehetőségeit is vizsgáljuk az OpenAI-on belül, például olyan munkaterheléseknél, ahol sok ügynöknek kell együttműködnie egy közös Git-adattárban.

Ahogy a mérnökök és ügynökök egyre több szoftvert készítenek, az OpenAI-on kívül is egyre több csapat szembesül majd azokkal a verziókezelési korlátokkal, amelyekbe mi ütköztünk. Jelenleg azt vizsgáljuk, hogyan oszthatnánk meg felhasználóinkkal a Pandót lehetővé tevő technológiát.

A Pando a Git-adattárak felhasználóinak és üzemeltetőinek két évtizedes tapasztalatára épít. Folytatjuk a Pando fejlesztését, és a nyílt forráskódú Git közösségével együttműködve megosztjuk azokat a fejlesztéseket, amelyek a Git-felhasználók szélesebb körének is hasznára válnak.

Szerző

OpenAI