Skalering af Git hos OpenAI
Vi bygger versionsstyring til agenternes skala og investerer samtidig i Git-økosystemet.
I løbet af det seneste år er både størrelsen på og kravene til vores monorepo vokset i et svimlende tempo. Det stod klart, at vi måtte modernisere vores versionsstyringssystem for at skalere til det omfang af parallelt arbejde, som Codex gør muligt.
Vi byggede Pando, en ny backend til Git-lagre, uden at afbryde udviklingsarbejdet eller ændre den måde, vores team bruger Git på. Pando driver nu al udvikling i OpenAI's monorepo. Pando er designet til at opfylde to ambitiøse mål: For det første horisontalt skalerbare skrivninger, optimeret til samtidige push til mange grene. For det andet at maksimere læsegennemløbet, så klienter kan downloade data hurtigt nok til at udnytte deres tilgængelige netværksbåndbredde fuldt ud.
Vi nåede disse mål og bevarede samtidig kompatibiliteten med Git-protokollen og -klienten, så både vores udviklere og agenter problemfrit kunne tage Pando i brug i deres eksisterende arbejdsgange. I produktion reducerede Pando medianlatenstiden for push til næsten en femtedel. Det leverer nu næsten en petabyte Git-data om dagen.
Her gennemgår vi vores motivation for at bygge et nyt versionsstyringssystem, og hvorfor vi valgte at bevare kompatibiliteten med Git. Vi sammenligner vores system med almindelige tilgange til skalering af Git og forklarer, hvorfor Pando er særligt velegnet til at opfylde OpenAI's skaleringsbehov. Vi ser også på de næste skridt, og hvordan vi fortsat vil bidrage til Git for at understøtte arbejde i agenternes skala.
Codex har ændret omfanget og tempoet i udviklingen hos OpenAI og gjort det muligt for udviklere at udføre langt mere arbejde parallelt. Det betyder flere grene og push, flere merges samt flere fetch- og kloningsoperationer fra udviklingsmiljøer, forskningsjob og CI.
Disse krav forstærker hinanden. En enkelt udvikler har nu flere ændringer klar til CI samtidig, og hvert push kan få flere job til at hente den samme commit.
Omkring slutningen af sidste år begyndte systemerne bag vores monorepo at få svært ved at følge med. Vi oplevede langsomme fetch- og push-operationer samt mislykkede merges, og pålideligheden faldt til tider til under 90 %. Havde vi nået en grænse i selve Git eller i den måde, vi stillede vores monorepo til rådighed på? Git udsprang af et fællesskab, der udvekslede patches til kernen via e-mail. I mere end to årtier har udviklere tilpasset deres værktøjer omkring Git, i takt med at deres måde at bygge software på har ændret sig. Da vi begyndte at opleve problemer i vores eget monorepo, var det vigtigt for os at kunne bygge videre på det arbejde.
Vi så en mulighed for at bevare den velkendte arbejdsgang og samtidig genopbygge backenden, så den kunne håndtere push til forskellige grene parallelt og levere den samme historik til mange læsende klienter samtidig. Ved at bruge Git som fundament for vores arbejdsgang til versionsstyring kunne forbedringer af klienten også komme andre end OpenAI til gode. Vi ønskede at bidrage med disse forbedringer fra vores kopi af Git(åbner i et nyt vindue) til hovedprojektet og dele dem med det bredere fællesskab.
Skalering af et versionsstyringssystem har to centrale dimensioner: mange skrivende klienter, der pusher til forskellige grene, og mange læsende klienter, der henter den samme historik. Pando optimerer begge dele.
For det første koordinerer Pando opdateringer til hver gren uafhængigt. Opdateringer til uafhængige grene kan ske parallelt, og en klient, der henter en gren, får den historik, der er nødvendig for den pågældende revision. Kompromiset er, at en klient, der læser flere grene, kan se dem på forskellige tidspunkter.
For det andet genbruger Pando pakkefiler (Gits format til at komprimere flere objekter sammen) på tværs af fetch-operationer i stedet for at generere en særskilt pakkefil til hver klient. En klient kan komme til at downloade objekter, den allerede har, men serveren kan levere de samme bytes til mange klienter med et lavt beregningsforbrug.
Disse valg gør det muligt at skalere skrivninger på tværs af grene og betjene læsninger fra cacher. For at se, hvordan det hele hænger sammen, begynder vi med, hvordan Git gemmer og overfører historik.
Git skal kunne holde to dele af et lager synkroniseret: objekter og referencer.
Et Git-lager registrerer øjebliksbilleder af filer og den historik, der forbinder dem, som en graf af objekter. Et objekt kan være en blob, et træ, en commit eller et tag. Blobs indeholder indholdet af en enkelt fil, og træer knytter navne til blobs eller andre træer for at danne en mappestruktur. Commits peger på et rodtræ og nul eller flere forældrecommits og danner dermed lagerets historik.
Objekter identificeres ved en hash af deres type og indhold. Objekter er også uforanderlige: Når en ændret fil registreres, oprettes en ny blob, mens uændrede filer og mapper kan genbruge eksisterende objekter.
Grene og tags er referencer, også kaldet »refs«, der peger på commits. Når man committer på en gren, oprettes en ny commit, og grenens reference opdateres, så den peger på den.
Git-objekter henviser til hinanden via deres ID: commits til træer og træer til andre træer og blobs. Grenen refs/heads/main peger på commit C2, hvis forælder er C1. C1 peger på rodtræet T1, hvor parser.py henviser til blob B1. C2 peger på rodtræet T2, hvor parser.py henviser til blob B2. I begge rodtræer henviser docs/ til det samme træ, T3. Dets README.md-post peger på blob B3, og usage.md peger på blob B4.
Git gemmer objekter i enten »løs« eller »pakket« form. Løse objekter gemmes i separate filer, der identificeres ved deres objekt-ID. Pakkede objekter gemmes sammen i en pakkefil, så lignende objekter kan gemmes som deltaer i forhold til hinanden.
Git kan gemme blobs som separate filer eller pakke dem sammen og kode én blob som et delta i forhold til en anden.
I Git kan et objekt nås fra en commit, hvis Git kan finde det ved at følge links fra denne commit gennem dens forældre, træer og blobs. Under en fetch fortæller klienten serveren, hvilke commits den ønsker, og hvilke den allerede har. Serveren finder de objekter, der kan nås fra de ønskede commits, udelader dem, den ved, at klienten allerede har, og sender resten som en pakkefil i det samme format, som Git bruger på disken. En fuld kloning starter uden objekter og har derfor brug for alle de objekter, der kan nås fra den historik, som klones.
Et push overfører objekter i den modsatte retning ved at bede serveren opdatere en eller flere referencer. Klienten bruger serverens annoncerede referencer til at undgå at sende objekter, som serveren allerede har. Den pakke, der sendes, kan derfor afhænge af eksisterende objekter: Det er ikke nok at modtage pakken alene for at genskabe den nye historik.
Replikering af et Git-lager betyder at holde både objekter og referencer tilgængelige på tværs af flere servere. Før en replika kan levere en references nye værdi, skal den kunne levere de objekter, der kan nås fra denne værdi. Tjenesten skal også koordinere konkurrerende referenceopdateringer, dvs. push, der forsøger at flytte den samme gren fra det samme gamle endepunkt til forskellige nye commits. Systemer, der pålægger alle skrivninger til et lager én fælles rækkefølge, kan få push til uafhængige grene til at vente på hinanden.
Da vi designede Pando, vurderede vi konventionelle tilgange til at skalere versionsstyring med Git: én, der koordinerer referenceopdateringer på tværs af lagerreplikaer med trefaset commit (3PC, en protokol til at koordinere en commit på tværs af flere noder), og én, der fastlægger rækkefølgen af opdateringer via en fælles write-ahead-log (WAL, en log over ændringer, der skrives, før de anvendes) i et bloblager.
I det design med trefaset commit, vi modellerede, har Git-tjenesten en komplet kopi af lageret på flere replikaer. Når en server modtager et push, kopieres den indgående pakkefil og sendes til alle replikaer parallelt. For at publicere pushet skal et flertal af replikaerne acceptere referenceopdateringen. Serveren, der styrer dette, er koordinatoren, og processen kræver flere kommunikationsrunder mellem koordinatoren og hver af de øvrige replikaer.
For at acceptere en referenceopdatering låser hver replika sin kopi af referencen og kontrollerer, at den stadig peger på den commit, klienten forventer. Koordinatoren sender derefter en pre-commit-meddelelse til de replikaer, der accepterer. Når et flertal har bekræftet pre-commit-meddelelsen, beder koordinatoren dem om at udføre opdateringen. Enhver ajourført replika kan derefter annoncere grenens nye endepunkt fra sin lokale kopi af lageret.
Komplette replikaer gør det muligt at betjene læsninger lokalt og garanterer, at klienter får et konsistent billede af alle referencer, uanset hvilken replika de vælger. Derfor opdateres referencer én ad gangen, og hvert push kræver flere kommunikationsrunder for at koordinere referenceopdateringen mellem replikaerne.
Ventetiden på et quorum betyder, at replikaernes diskydeevne og kommunikationslatenstid påvirker push-forløbet. Når mængden af skrivninger vokser, skal replikaerne kunne følge med både indgående pakker og læsetrafikken.
I det WAL-baserede design, vi modellerede, bruger Git-tjenesten et fælles manifest i bloblageret til at fastlægge rækkefølgen af opdateringer på tværs af lageret. Når en server modtager et push, uploader den den indgående pakkefil til et bloblager og forbereder referenceopdateringen i sin lokale kopi af lageret. For at publicere pushet tilføjer den en post i et fælles manifest i bloblageret, som registrerer referenceopdateringen og peger på pakkefilen. Git-serveren opdaterer derefter manifestet med en compare-and-swap-operation. Den erstatter manifestet atomisk eller afviser ændringen. Hvis en anden skrivende klient har opdateret det i mellemtiden, læser serveren de mellemliggende poster, kontrollerer, at dens referenceopdatering stadig er gyldig, og prøver igen med den pakkefil, den allerede har uploadet.
Skrivende klienter kan uploade deres pakkefiler parallelt, men selv push til uafhængige grene kræver opdatering af det samme manifest. Svartiden for en vellykket compare-and-swap begrænser, hvor ofte lageret kan publicere et nyt manifest. Et bloblager med lav latenstid gør dette trin hurtigt. Batchbehandling lader ét manifest opdatere flere referencer samtidig, men kræver, at opdateringer sættes i kø, hvilket øger latenstiden.
Bloblageret kan modtage pakkefiler parallelt, men opdateringer til det fælles manifest konkurrerer stadig med hinanden. At øge bloblagerets kapacitet alene fjerner ikke denne flaskehals. Hver replika skal stadig læse og indeksere pakker fra bloblageret, før den kan levere dem lokalt. Hvis indgående skrivninger sker hurtigere end dette arbejde, kommer replikaerne bagud, og klienterne må vente længere på at hente nyligt pushede commits.
Det bringer os til Pando. Pando er en ny arkitektur til at stille Git-lagre til rådighed, som skal kunne skalere til mange klienter, der pusher samtidig. Sådan fungerer det:
Før vi udviklede Pando, havde vi en fornemmelse af, at vi kunne opfylde vores krav uden at gå på kompromis med Git. Men vi måtte gå på kompromis med nogle af de ting, som de fleste Git-systemer anser for at være krav:
Konsistens på tværs af hele lageret: At levere et konsistent øjebliksbillede af alle referencer kræver koordinering på tværs af lageret. I et system, der fastlægger en fælles rækkefølge for alle referenceopdateringer, konkurrerer push til uafhængige grene om ét koordineringspunkt.
Unikke pakkefiler til hver klient: Hvis to klienter med forskellig lokal historik henter den samme gren, kan serveren være nødt til at generere en særskilt pakke til hver for at undgå at sende objekter, de allerede har. At generere disse svar kræver CPU-ressourcer og disk-I/O, hvilket gør det svært at skalere til mange læsende klienter.
Pando bruger i stedet en lempeligere konsistensmodel (kausal konsistens) til at skalere skrivninger og genbruger pakkefiler til at skalere læsninger.
Når en klient pusher til en gren, gemmer Pando den indgående pakkefil som en uforanderlig blob i et bloblager. Hver gang grenen flyttes, registrerer Pando en referenceversion i et metadatalager. Den indeholder det nye endepunkt, den pakke, der leverede det, og eventuelle tidligere referenceversioner, som pakken afhænger af. Både bloblageret og metadatalageret kan skaleres horisontalt.
At hente en commit eller reference fra Pando kræver, at et sæt referenceversioner (pakkefiler) afspilles hos klienten. Det genskaber tilstanden hos den klient, der oprettede den referenceversion, som hentes. Det giver en svagere form for konsistens på tværs af hele lageret, men sikrer serialiserbarhed for opdateringer af de enkelte referencer. Klienter kommer kun i konflikt, når de pusher til den samme reference samtidig. En klient, der læser flere grene, kan ikke antage, at læsningerne beskriver det samme tidspunkt på tværs af lageret.
Et push til hovedgrenen tilføjer en inkrementel pakke med en eller flere commits. Hvert push til en sidegren medtager igen grenens tidligere commits, så en klient med den annoncerede version af hovedgrenen kan hente sidegrenen i én pakke.
Det er en form for kausal konsistens. For at hente en reference ser den læsende klient historikken hos den skrivende klient, der oprettede den. Under et push styrer Pando, hvilken fælles historik klienten og serveren bliver enige om. Det sikrer, at klienterne producerer pakkefiler, som er lettere at dele.
Derfor leverer Pando hverken et øjebliksbillede af alle referencer under Git-operationer eller en rækkefølge for referenceopdateringer inden for en kloning eller fetch. Antag konkret, at grenene foo og bar begge opdateres under forhandlingen af en fuld kloning. Under forhandlingen kan én klient se foo i version 1 og bar i version 2, mens en anden klient ser foo i version 2 og bar i version 1. I praksis er brugere og agenter uafhængige af den globale rækkefølge af referenceopdateringer. De afhænger i stedet af rækkefølgen af opdateringer til en enkelt reference.
I denne simulering af de tre konsistensmodeller konfigurerer vi 1.000 klienter til hver at vælge sin egen gren og derefter gentagne gange pushe unikke commits. Vi måler den samlede forløbne tid og latenstiden fra push til fetch. Sidstnævnte er en vigtig måling for arbejdsbelastninger, da den måler svartiden, når brugere og agenter samarbejder eller bruger andre systemer, der er afhængige af versionsstyring. Simuleringen modellerer hardware fra standard- til premiumklassen for virtuelle maskiner og bloblagersystemer. I denne simulering har vi lempet kravet om konsistens på tværs af hele lageret for trefaset commit.
Simulating Git Server Implementations
Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.
Kør denne simulering, der sammenligner Pando med to andre tilgange. Vælg infrastruktur fra standard- til premiumhardware, antal klienter og andre indstillinger for at se, hvordan de klarer sig i forhold til hinanden.
Det modellerede system med trefaset commit har markant højere skrivelatenstid under belastning, når diskene når deres kapacitetsgrænse. Systemer med write-ahead-log har derimod markant højere læselatenstid af samme grund, da serverne har svært ved at følge med læsningen fra journalen. Både i praktiske test på vores monorepo og i denne simulering understøtter Pando mange tusinde klienter, der kan pushe samtidig uden at påvirke ydeevnen under belastning.
Når Pando håndterer et push, validerer det den indgående pakkefil, mens den streames til bloblageret, uden at pakke den ud i et lokalt lager. Når en klient starter en fetch, vælger Pando de gemte pakker, klienten har brug for, hvorefter klienten downloader dem. Ingen server behøver at anvende pakkefiler på et lokalt lager eller udføre ressourcekrævende udpaknings- og pakningsoperationer. Da disse servere primært forhandler og udvælger pakkefiler, kan Pando-servere køre på standardhardware.
Det gør det muligt for Pando at skalere helt op til grænserne for klienterne, netværket mellem klienterne og Pando samt bloblagertjenesten. Med tilstrækkelig kapacitet kan klienter, der arbejder på uafhængige grene, hver især overføre pakker så hurtigt, som deres egen forbindelse tillader. For at betjene flere klienter med den hastighed kan vi tilføje Pando-servere og skalere metadatalageret og bloblageret sammen med dem.
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.
En agent redigerer filer, opretter en commit lokalt og kører git push, som opretter en pakkefil med ændringerne for at opdatere en gren. Pando modtager pakken og streamer den til bloblageret. Efter at have valideret det indgående push med én gennemløbende afkodning bruger Pando en transaktion med flere nøgler til at registrere en referenceversion i vores metadatalager og atomisk opdatere referencepointeren. Det gør det muligt for Pando at sikre konsistens, hvor klienten altid kan læse sine egne skrivninger. Hele denne skriveoperation kræver læsninger og skrivninger af metadata.
Når en anden klient henter grenen, vælger Pando et sæt gemte pakker ud fra den historik, klienten allerede har. Denne operation kræver læsninger af metadata, hvor er den korteste afstand i referenceversioner mellem det, klienten har, og det, klienten ønsker. Serveren returnerer pakkefil-URI'er, og klienten downloader filerne gennem tjenesten Express, som gemmer pakkefiler i regionale cacher.
Dette design bygger på de tre centrale tjenester i Pandos arkitektur: push, fetch og komprimering.
Når en klient pusher, er en konventionel Git-server som en pokerspiller, der viser alle sine kort: Den afslører hver eneste gren, den har, for klienten. Klienten samler derefter commits, træer og blobs for at oprette en pakkefil, der udelader objekter, som den ved, at serveren allerede har.
Annoncering af flere referencer kan gøre pushet mindre. Det kan også gøre pakken sværere at genbruge, fordi en anden klient kan mangle objekter, som den skrivende klient udelod.
Pando bruger en anden tilgang. I stedet for at vise hele hånden annoncerer det kun et udvalgt sæt grene til klienten. Det gør vi af to grunde:
Det er ressourcekrævende at levere et konsistent billede af alle referencer. Med millioner af grene kan selve referenceannonceringen overstige 100 MiB. Det kræver, at hver server enten vedligeholder en opdateret oversigt, hvilket medfører et betydeligt koordineringsarbejde, eller foretager en databaseforespørgsel, der skalerer med antallet af referencer.
Når klienten kun får oplyst en delmængde af referencerne, genererer den en pakke, der kun afhænger af det, serveren udtrykkeligt tillader klienten at udelade. Ved at holde styr på afhængighederne kan Pando genbruge den pakkefil, en klient genererer i et push, som svar på en senere fetch.
Jo flere referencer en Git-server annoncerer, desto flere kilder kan pushende klienter bruge til at udelade objekter, så de kan producere mindre pakker. Genbrug af disse pakkefiler kræver dog, at serveren kan bevise, at de hentende klienter har alle afhængighederne. Ellers kan pakkefilen udelade objekter, som klienten har brug for.
Hvis en Git-server omvendt slet ikke annoncerer nogen referencer, vil klienten generere en pakkefil med hele lagerets historik. Den pakkefil kan bruges af enhver anden klient, men med den betydelige omkostning, at hentning af enhver gren reelt bliver en fuld kloning.
Annoncering af flere referencer giver mindre pakkefiler. Det meste af reduktionen i størrelse kan opnås ved blot at annoncere én.
Når en klient pusher en ny featuregren, annoncerer Pando som standard kun en udpeget hovedgren som grundlag. Det holder annonceringens størrelse konstant, efterhånden som lageret får flere grene, men indebærer nogle kompromiser. Når en udvikler enten pusher mange gange til den samme gren eller bygger en featuregren oven på en anden, skal klienten sende alt uden for hovedgrenen igen.
Det gør successive push til grenen større, da de kan indeholde overflødige objekter, og bruger mere af klientens båndbredde. I vores monorepo var medianstørrelsen for pakkefiler produceret på denne måde under 20 KiB, og 99-percentilen var under 3 MiB. For os var det et fornuftigt kompromis, fordi en klient, der allerede har den annoncerede historik for hovedgrenen, kan hente grenen med denne ene pakkefil.
Efter at have valgt, hvilke referencer der skal annonceres til klienten, accepterer Pando pushet og opdaterer sit metadatalager. Pando opdeler metadataopdateringen i synkrone og asynkrone dele. Den synkrone metadataopdatering sikrer, at klienter straks kan hente den netop opdaterede commit. Den skriver en ny referenceversion og opdaterer referencepointeren til den nye version. Her gemmes det nye endepunkt, de afhængigheder, der blev brugt til at forhandle pakkefilen, samt et link til den pushede pakkefil.
Samtidig registreres asynkront arbejde i den samme transaktion, såsom planlægning af komprimering. Ved meget store push skrives kun en delmængde af commits synkront. Dermed kræves kun et konstant antal læsninger og skrivninger i metadatalageret, uanset pushets størrelse.
Det er vigtigt, at Pando-servere kan acceptere push til uafhængige referencer uafhængigt af hinanden. Den eneste nøgle, der kan opstå konkurrence om, er referencepointeren, som vi beskytter med en atomisk operation svarende til compare-and-swap. Vi kan skalere skrivegennemløbet ved at tilføje Pando-servere, skaleringsenheder til metadatalageret eller bloblagre.
Når en klient udfører en fetch, sender den to sæt commit-ID'er til serveren, som i protokollen kaldes have og want. En konventionel Git-server beregner mængdedifferensen mellem de objekter, der kan nås fra have-angivelserne, og dem, der kan nås fra want-angivelserne, komprimerer dem og sender resultatet til klienten. Selvom open source-versionen af Git-serveren har forskellige lokale indekser til at fremskynde denne operation, kan pakkefilerne være ressourcekrævende at beregne og er generelt vanskelige at cache.
Pando bruger i stedet de commit-tilknytninger, der registreres ved push, til at slå klientens have og want op og omsætte dem til et sæt referenceversioner, der indeholder disse commits. Hver af disse referenceversioner linker til en pakkefil, som Pando tidligere har modtaget. Serveren bruger dette til at finde ud af, hvad klienten har brug for, og vælger de pakkefiler, der dækker de nødvendige objekter.
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 omsætter Git-protokollens have- og want-angivelser til en sekvens af referenceversioner. At anmode om en sidegren indebærer også at anmode om historikken frem til dens afhængigheder på hovedgrenen.
Git-protokollen tillader kun, at der sendes én pakkefil i HTTP-svaret ved en kloning eller fetch. En ofte overset funktion i Git-protokollen, packfile-uri, gør det dog muligt for en server at returnere links til separat download af yderligere pakker. Pando bruger udelukkende denne funktion og sender en tom pakkefil direkte i svaret. For at understøtte funktionen bidrog OpenAI med en række forbedringer af packfile-uri til hovedprojektet.
OpenAI's systemer kører i mange geografiske regioner, så vi har Pando-klynger med lokale cacher i hver region. Pakkeleveringstjenesten Express cacher vores bloblager, og en separat tjeneste cacher metadatalageret. Disse cacher gør det muligt for Pando at betjene forespørgsler på et commit-ID eller en særlig læsning af en reference udelukkende fra den cachede tilstand i den pågældende klynge. Vi bruger konsistent hashing til at dirigere forespørgsler på en pakkefil til Express-servere med en cache-træfprocent på over 99 %.
Dette cachesystem giver et ekstremt højt gennemløb, der kan udnytte netværkskapaciteten mellem klienterne og Pando fuldt ud. Med disse pakkefil-URI'er og lokale cacher har vi bekræftet, at klonings- og fetch-operationer fuldt ud udnytter netværksforbindelser på 10 Gbps, selv når vi kun bruger standardvirtuelle maskiner fra cloududbydere.
På de fleste Git-servere er ompakning og oprydning af ubrugte objekter nødvendige for at bevare ydeevnen i et voksende lager. Begge trin frigør plads, men kræver mange beregnings- og I/O-ressourcer.
I et lager med hyppige ændringer som OpenAI's monorepo er det ikke praktisk muligt at sende en særskilt pakkefil-URI for hvert push siden sidste fetch. Hver dag ville en klient skulle downloade titusindvis af pakkefiler for at ajourføre sin hovedgren.
Pando samler i stedet pakker i baggrunden for at reducere dette ekstraarbejde med et design, der minder om komprimering i logstrukturerede flettetræer. Hvert push producerer det, Pando kalder en »L0«-pakkefil, som er den mindste dataenhed, Pando kan overføre. Over tid samler baggrundsprocesser disse pakkefiler i grupper, der dækker en sekvens af referenceversioner og vokser geometrisk fra L0. I dag bruger vores monorepo pakkefilgrupper op til L4.
Disse grupper tjener to formål. Når en gruppe er tilstrækkeligt fuld, overtager en komprimeringsproces den og producerer en enkelt pakkefil. Det forbedrer komprimeringsgraden ved at omorganisere objekterne i pakkefilen. Men grupperne forbedrer også ydeevnen ved fetch-forhandling ved at fungere som en springliste over referenceversioner. Ved at gennemgå indekser på hvert niveau samtidig kan Pando vælge et sæt pakker, der skal sendes til en klient, i logaritmisk tid. Disse pakkefilgrupper giver, uanset om de er ompakket eller ej, en mere effektiv løsning til fetch-planlægning, der reducerer kravene til både Pandos gennemløb og latenstid.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
Komprimering samler pakkefiler fra mange referenceversioner i baggrunden og gør fetch-operationer mere effektive med færre filer.
I modsætning til mange Git-servere bevares pakkefiler på alle niveauer og bliver hverken kasseret eller fjernet ved oprydning. Pakker på alle niveauer bruges løbende til at betjene klienter ud fra de have og want, de angiver i en fetch. For klienter, der bruger Pando som en API, kræver læsning af et vilkårligt objekt fra en pakkefil typisk læsning af færre data fra en L0 end fra en L4, fordi objektet med mindre sandsynlighed er komprimeret.
Komprimering i Pando reducerer den samlede datamængde, der sendes til klienter, markant og lader samtidig pakkeplanlægningen skalere sublineært i forhold til, hvor langt klienten er bagud. Det er især vigtigt for store lagre med hyppige ændringer, som vi forventer bliver den nye normal i softwareudvikling.
Vi byggede Pando med Codex og gik fra projektstart til produktion på blot 10 uger. Kernen i Pando blev designet af én udvikler, implementeret af teams af agenter og sat i drift af en håndfuld udviklere. Pando begyndte at håndtere produktionstrafik i april i år og er en del af et større system af værktøjer til versionsstyring, som bruges hos OpenAI.
Vi brugte en lang række tilgange til at validere Pandos design. Vi anvendte vores principper for at bygge et sikrere softwarefundament. Da projektet begyndte fra bunden, valgte vi at implementere Pando i Rust og uden afhængigheder af ældre systemer. Vi skrev en testsuite til overensstemmelsestest, så agenter kunne teste deres ændringer mod Gits standardadfærd. Vi udviklede også testmiljøer til deterministisk simulering, så vi kunne kontrollere systemets invarianter og egenskaber. Det gjorde os i stand til at finde og afhjælpe subtile kapløbstilstande og regressioner i forhandling og pakkeplanlægning.
Mange modeller bidrog til dette system. Efterhånden som nye modeller blev tilgængelige, brugte vi dem til at forbedre Pandos ydeevne og pålidelighed. Med GPT‑6 Astra skrev agenter Lean og TLA+ for at formalisere en »teori om Pando«, som vi brugte til at finde og rette fejl i implementeringen og bevise systemets egenskaber. Flere uafbrudte sessioner kørte i hundredvis af timer for at finde og rette fejl i Pandos håndtering af grænsetilfælde. Vi lader også agenter køre døgnet rundt for at udføre red teaming af Git og Pando med modeller, der understøtter Daybreak.
Vi satte først Pando i drift som en proxy, der videresendte læsninger og skrivninger til vores eksisterende versionsstyringssystem. Det gjorde det muligt at holde Pando og det tidligere system synkroniseret, så vi sikkert kunne flytte trafik over på Pando uden nedetid.
Som en del af sit cachedesign kan Pando også fjerne dubletter og samle forespørgsler, før de videresendes. Det holdt vores eksisterende værktøjer velfungerende, mens vi flyttede systemerne til Pando ét ad gangen. Efterhånden som vi flyttede flere arbejdsbelastninger til Pando, fyldte deres push Pandos cache og forbedrede pålideligheden af vores tidligere backend.
For at optimere ydeevnen ved brug af Pando begyndte vi at levere vores offentlige Git-distribution til udviklernes maskiner og som en del af ChatGPT‑skrivebordsappen. Vi brugte en intern wrapper omkring Gits CLI til at flytte klienterne til Pando i etaper. Vi skabte mange andre værktøjer til at håndtere ændringsanmodninger og kodegennemgang samt API'er til automatisering. Sammen med Git-trafikken flyttede vi hundredvis af integrationer, der var afhængige af ældre API'er, webhooks og downloads af lagre.
Codex hjalp selv udviklere og teams med overgangen. Vi oprettede færdigheder til at klargøre eksisterende checkouts og vejlede agenter i at bruge det nye system. Dermed kunne udviklere blot bede Codex om at flytte deres udviklingsmiljøer og tjenester.
Overgangen til Pando forbedrede pålideligheden af merges og reducerede medianlatenstiden for push med næsten 80 %.
Pando kan levere en petabyte Git-data om dagen med blot en håndfuld CPU-kerner. Pando har leveret mere end 57 terabyte Git-pakker på en enkelt time, hvor over 99 % af disse bytes kom fra den lokale diskcache.
Genbrug af pakker lader mange klienter downloade den samme lagerhistorik med et lavt beregningsforbrug på de noder, der leverer dataene. Dette eksempel viser ét lager, men Pando er designet til at understøtte mange flere. Når efterspørgslen vokser, kan vi skalere datalevering og Git-forhandling uafhængigt af hinanden.
Ved at bygge Pando på Git-protokollen og -klienten fandt vi fejl og muligheder for at forbedre Gits egen ydeevne.
Pando leverer for eksempel genanvendelige pakker direkte til klienterne, så download og indeksering af disse pakker blev en central del af arbejdet med at gøre systemet hurtigt og pålideligt. Arbejdet afdækkede grænsetilfælde ved samtidige downloads, oprydning efter afbrudte overførsler og genoptagelse af downloads, der allerede var færdige. Vi rettede disse kodeforløb, tilføjede valideringer ved accept af en eksisterende pakke til en fetch og bidrog med arbejdet til hovedprojektet.
Vi havde også brug for at se, hvor tid og båndbredde blev brugt. Derfor tilføjede vi sporing af packfile-uri-downloads og optælling af bytes ved pakkegenerering, så det blev lettere at afgøre, om en langsom fetch brugte tiden på at generere pakker eller overføre dem.
I OpenAI's Git-build(åbner i et nyt vindue) kan klienten downloade og indeksere uafhængige pakker parallelt. Vi tilføjede begrebet »eksplicitte have-angivelser«, så en server kan annoncere en grens aktuelle endepunkt separat fra den historik, som en pushende klient må udelade fra sin pakke. I Pando gør det klienter i stand til at bruge --force-with-lease til at kontrollere, at en featuregren ikke er flyttet, og opbygge pakken alene ud fra hovedgrenen. Enhver med denne historik for hovedgrenen kan derefter hente grenen med den samme pakke.
Flere af disse forbedringer er allerede med i Gits hovedprojekt, og mange andre findes i vores offentlige lager. Vi fortsætter samarbejdet med Git-fællesskabet for at gøre forbedringerne nyttige også uden for Pando. Du kan følge arbejdet i vores offentlige patchoversigt(åbner i et nyt vindue).
Ved at gentænke gængse antagelser om skalering af Git-lagre gør Pando det muligt at skalere vores monorepo uden at ændre de Git-arbejdsgange, vores udviklere og agenter allerede bruger. I vores monorepo har Pando muliggjort mere parallelt arbejde, forbedret pålideligheden af merges og reduceret den tid, det tager at publicere kodeændringer. Ud over vores monorepo undersøger vi også andre anvendelser af Pando hos OpenAI, herunder arbejdsbelastninger, hvor mange agenter skal samarbejde i et fælles Git-lager.
Efterhånden som udviklere og agenter producerer mere software, vil flere teams uden for OpenAI støde på de begrænsninger i versionsstyringen, som vi mødte. Vi undersøger i øjeblikket, hvordan vi kan dele den teknologi, der gør Pando muligt, med vores brugere.
Pando bygger på to årtiers erfaring fra brugere og driftsansvarlige for Git-lagre. Vi vil fortsætte med at udvikle Pando og samarbejde med Gits open source-fællesskab om at dele forbedringer, der gavner Git-brugere generelt.


