OpenAI

2. november 2026

Teknisk arbeid

Skalering av Git hos OpenAI

Vi bygger versjonskontroll for arbeid med agenter i stor skala og investerer samtidig i Git-økosystemet.

Av Aaron Friel, Taylor Blau og Ted Nyman, tekniske medarbeidere

Laster inn …

Det siste året har både størrelsen på monorepoet vårt og kravene til det økt i et svimlende tempo. Det var tydelig at vi måtte fornye versjonskontrollsystemet vårt for å håndtere så mye parallelt arbeid som Codex gjør mulig.

Vi bygget Pando, en ny backend for Git-lagre, uten å avbryte utviklingsarbeidet eller endre hvordan teamet vårt bruker Git. Pando driver nå all utvikling i OpenAIs monorepo. Pando er laget for å nå to ambisiøse mål: For det første horisontalt skalerbar skriving, optimalisert for samtidige push-operasjoner til mange grener. For det andre å maksimere gjennomstrømmingen ved lesing, slik at klienter kan laste ned data raskt nok til å utnytte all tilgjengelig nettverksbåndbredde.

Vi nådde disse målene og beholdt samtidig kompatibiliteten med Git-protokollen og -klienten. Dermed kunne både utviklerne og agentene våre ta i bruk Pando sømløst i sine eksisterende arbeidsflyter. I produksjon reduserte Pando medianforsinkelsen for push til nesten en femtedel. Det leverer nå nesten én petabyte Git-data per dag.

Her forklarer vi hvorfor vi bygget et nytt versjonskontrollsystem, og hvorfor vi valgte å beholde kompatibiliteten med Git. Vi sammenligner systemet vårt med vanlige tilnærminger til skalering av Git og beskriver hvorfor Pando er spesielt godt rustet til å møte OpenAIs skaleringsbehov. Vi ser også på veien videre og hvordan vi vil fortsette å bidra til Git for å støtte arbeid i den skalaen agenter muliggjør.

Hvorfor vi trengte et nytt versjonskontrollsystem

Codex har endret omfanget og tempoet i utviklingen hos OpenAI og gjort det mulig for utviklere å gjøre langt mer arbeid parallelt. Det betyr flere grener og push-operasjoner, flere flettinger og flere hentinger og kloninger fra utviklingsmiljøer, forskningsjobber og CI.

Disse kravene forsterker hverandre. Én utvikler har nå flere endringer klare for CI samtidig, og hver push kan få flere jobber til å hente den samme commiten.

Mot slutten av fjoråret begynte systemene som understøttet monorepoet vårt, å slite med å holde tritt. Vi opplevde trege hentinger og push-operasjoner samt mislykkede flettinger, og påliteligheten falt til tider under 90 %. Hadde vi nådd en grense i selve Git, eller i måten vi driftet monorepoet på? Git vokste frem fra et miljø som utvekslet kodeendringer til kjernen via e-post. I mer enn to tiår har utviklere tilpasset verktøyene sine rundt Git etter hvert som måten de bygger programvare på har endret seg. Da vi begynte å møte motstand i vårt eget monorepo, var det viktig for oss å kunne bygge videre på dette arbeidet.

Vi så en måte å beholde den kjente arbeidsflyten på, samtidig som vi bygget om backenden for å håndtere push-operasjoner til ulike grener parallelt og levere den samme historikken til mange lesende klienter samtidig. Ved å bruke Git som grunnlag for versjonskontrollarbeidsflyten vår kunne forbedringer i klienten også komme andre enn OpenAI til gode. Vi ønsket å bidra med disse forbedringene fra vår kopi av Git⁠(åpnes i et nytt vindu) til hovedprosjektet og dele dem med resten av miljøet.

Utvikling for Git i stor skala

Skalering av et versjonskontrollsystem har to sentrale dimensjoner: mange skrivende klienter som pusher til ulike grener, og mange lesende klienter som henter den samme historikken. Pando optimaliserer for begge deler.

For det første koordinerer Pando oppdateringer av hver gren uavhengig av de andre. Oppdateringer av uavhengige grener kan skje parallelt, og en klient som henter en gren, får historikken som trengs for den revisjonen. Kompromisset er at en klient som ser på flere grener, kan se dem slik de var på ulike tidspunkter.

For det andre gjenbruker Pando pakkefiler (Gits format for å komprimere flere objekter sammen) på tvers av hentinger, i stedet for å generere en egen pakkefil for hver klient. En klient kan laste ned enkelte objekter den allerede har, men serveren kan levere de samme bytene til mange klienter med lite beregningsarbeid.

Disse valgene lar oss skalere skriving på tvers av grener og betjene lesinger fra hurtigbuffere. For å se hvordan dette henger sammen, starter vi med hvordan Git lagrer og overfører historikk.

En innføring i objektlagring i Git

Git må kunne holde to deler av et lager synkronisert: objekter og referanser.

Et Git-lager registrerer øyeblikksbilder av filer og historikken som forbinder dem, som en graf av objekter. Et objekt kan være en blob, et tre, en commit eller en tagg. Blobber inneholder dataene i én enkelt fil, og trær knytter navn til blobber eller andre trær for å danne en mappestruktur. Commiter peker til et rottre og null eller flere foreldrecommiter, og danner dermed historikken i lageret.

Objekter identifiseres med en hash av typen og innholdet. Objekter er også uforanderlige: Når en endret fil registreres, opprettes en ny blob, mens uendrede filer og mapper kan gjenbruke eksisterende objekter.

Grener og tagger er referanser, eller «refs», som peker til commiter. Når du committer på en gren, opprettes en ny commit, og grenens referanse oppdateres til å peke til den.

Grenen refs/heads/main peker til commit C2, som har C1 som forelder. C1 peker til rottreet T1, der parser.py viser til blob B1. C2 peker til rottreet T2, der parser.py viser til blob B2. I begge rottrærne viser docs/ til det samme treet, T3. README.md-oppføringen i dette treet peker til blob B3, og usage.md peker til blob B4.

Git-objekter viser til hverandre med ID-ene sine: commiter til trær, og trær til trær og blobber. Grenen refs/heads/main peker til commit C2, som har C1 som forelder. C1 peker til rottreet T1, der parser.py viser til blob B1. C2 peker til rottreet T2, der parser.py viser til blob B2. I begge rottrærne viser docs/ til det samme treet, T3. README.md-oppføringen i dette treet peker til blob B3, og usage.md peker til blob B4.

Git lagrer objekter enten «løst» eller «pakket». Løse objekter lagres i separate filer som identifiseres med objekt-ID-en. Pakkede objekter lagres sammen i en pakkefil, slik at lignende objekter kan lagres som deltaer i forhold til hverandre.

To versjoner av parser.py vises under $GIT_DIR/objects. Som løse objekter inneholder blob A parse(), mens blob B inneholder både parse() og parse_file(). Pakkefilen lagrer hele A og representerer B som instruksjoner om å kopiere hele A og legge til parse_file(). Den tilhørende *.idx-filen finner begge blobbene i pakkefilen ved hjelp av objekt-ID.

Git kan lagre blobber som separate filer eller pakke dem sammen, der én blob kodes som et delta i forhold til en annen.

I Git er et objekt nåbart fra en commit hvis Git kan finne det ved å følge koblinger fra commiten gjennom foreldre, trær og blobber. Under en henting forteller klienten serveren hvilke commiter den ønsker, og hvilke den allerede har. Serveren finner objektene som kan nås fra de ønskede commitene, utelater dem den vet at klienten allerede har, og sender resten som en pakkefil i samme format som Git bruker på disk. En full kloning starter uten objekter og trenger derfor alle objektene som kan nås fra historikken som klones.

En push overfører objekter i motsatt retning ved å be serveren oppdatere én eller flere referanser. Klienten bruker serverens sett med annonserte referanser for å unngå å sende objekter serveren allerede har. Pakken som sendes, kan derfor være avhengig av eksisterende objekter: Å motta bare pakken er ikke nok til å rekonstruere den nye historikken.

Å replikere et Git-lager innebærer å holde både objekter og referanser tilgjengelige på flere servere. Før en replika kan levere den nye verdien til en referanse, må den kunne levere objektene som kan nås fra denne verdien. Tjenesten må også koordinere konkurrerende referanseoppdateringer, altså push-operasjoner som prøver å flytte den samme grenen fra det samme gamle endepunktet til ulike nye commiter. Systemer som tvinger alle skrivinger til et lager inn i én felles rekkefølge, kan gjøre at push-operasjoner til uavhengige grener må vente på hverandre.

Etablerte mønstre for distribuerte Git-tjenester

Da vi utformet Pando, vurderte vi tradisjonelle tilnærminger til å skalere versjonskontroll med Git. Den ene koordinerer referanseoppdateringer på tvers av lagerreplikaer med trefase-commit (3PC, en protokoll for å koordinere en commit på flere noder). Den andre ordner oppdateringer gjennom en felles forhåndslogg (WAL, en logg der endringer skrives før de utføres) i bloblagring.

Trefase-commit (3PC)

I løsningen med trefase-commit som vi modellerte, holder Git-tjenesten en komplett kopi av lageret på flere replikaer. Når en server mottar en push, kopieres den innkommende pakkefilen og sendes til alle replikaene parallelt. For å publisere push-operasjonen må et flertall av replikaene godta referanseoppdateringen. Serveren som styrer dette, er koordinatoren, og prosessen krever flere kommunikasjonsrunder mellom koordinatoren og hver av de andre replikaene.

For å godta en referanseoppdatering låser hver replika sin kopi av referansen og kontrollerer at den fortsatt peker til commiten klienten forventer. Koordinatoren sender deretter en pre-commit-melding til replikaene som godtar oppdateringen. Når et flertall bekrefter pre-commit-meldingen, ber koordinatoren dem utføre oppdateringen. Alle oppdaterte replikaer kan da annonsere grenens nye endepunkt fra sin lokale kopi av lageret.

Komplette replikaer gjør det mulig å betjene lesinger lokalt og garanterer at klientene får en konsistent oversikt over alle referanser, uansett hvilken replika de velger. Dermed oppdateres referansene én om gangen, og hver push krever flere tur-retur-runder for å koordinere referanseoppdateringen mellom replikaene.

Fordi operasjonen må vente på et beslutningsdyktig flertall, påvirkes push-tiden av replikaenes diskytelse og kommunikasjonsforsinkelse. Etter hvert som skrivevolumet øker, må replikaene holde tritt med både innkommende pakker og lesetrafikk.

Forhåndslogg (WAL)

I den WAL-baserte løsningen vi modellerte, bruker Git-tjenesten et felles manifest i bloblagring for å ordne oppdateringer på tvers av lageret. Når en server mottar en push, laster den opp den innkommende pakkefilen til et bloblager og forbereder referanseoppdateringen i sin lokale kopi av lageret. For å publisere push-operasjonen legger serveren til en oppføring i et felles manifest i bloblageret. Oppføringen registrerer referanseoppdateringen og peker til pakkefilen. Git-serveren oppdaterer deretter manifestet med en compare-and-swap-operasjon. Denne erstatter manifestet atomisk eller avviser endringen. Hvis en annen skrivende klient har oppdatert manifestet i mellomtiden, leser serveren de nye oppføringene, kontrollerer at referanseoppdateringen fortsatt er gyldig, og prøver på nytt med pakkefilen den allerede har lastet opp.

Skrivende klienter kan laste opp pakkefilene sine parallelt, men selv push-operasjoner til uavhengige grener krever oppdatering av det samme manifestet. Tur-retur-tiden for en vellykket compare-and-swap begrenser hvor ofte lageret kan publisere et nytt manifest. Et bloblager med lav forsinkelse gjør dette trinnet raskt. Gruppering av oppdateringer lar ett manifest oppdatere flere referanser samtidig, men krever at oppdateringer settes i kø og øker dermed forsinkelsen.

Bloblageret kan motta pakkefiler parallelt, men oppdateringer av det felles manifestet konkurrerer fortsatt med hverandre. Å øke kapasiteten til bloblageret alene fjerner ikke denne flaskehalsen. Hver replika må fortsatt lese og indeksere pakker fra bloblageret før den kan levere dem lokalt. Hvis nye skrivinger kommer raskere enn dette arbeidet kan utføres, blir replikaene hengende etter, og klientene må vente lenger på å hente nylig pushede commiter.

Pandos arkitektur

Det bringer oss til Pando. Pando er en ny arkitektur for å betjene Git-lagre, med mål om å skalere til mange klienter som pusher samtidig. Slik fungerer det:

Et nytt blikk på begrensningene

Før vi utviklet Pando, hadde vi en anelse om at vi kunne oppfylle kravene våre uten å gå på akkord med Git. Men vi måtte fire på noen av kravene de fleste Git-systemer tar for gitt:

  • Konsistens i hele lageret: Et konsistent øyeblikksbilde av alle referanser krever koordinering på tvers av lageret. I et system som ordner alle referanseoppdateringer i én felles rekkefølge, konkurrerer push-operasjoner til uavhengige grener om ett enkelt koordineringspunkt.

  • Unike pakkefiler for hver klient: Hvis to klienter med ulik lokal historikk henter den samme grenen, kan serveren måtte generere en egen pakke for hver av dem for å unngå å sende objekter de allerede har. Å lage disse svarene krever CPU-kapasitet og disk-I/O, noe som gjør det vanskelig å skalere til mange lesende klienter.

Pando bruker i stedet en svakere konsistensmodell (kausal konsistens) for å skalere skriving, og gjenbruker pakkefiler for å skalere lesing.

Kausal konsistens (CC)

Når en klient pusher til en gren, lagrer Pando den innkommende pakkefilen som en uforanderlig blob i et bloblager. Hver gang grenen flyttes, registrerer Pando en referanseversjon i et metadatalager. Den inneholder det nye endepunktet, pakken det kom fra, og eventuelle tidligere referanseversjoner pakken er avhengig av. Både bloblageret og metadatalageret kan skaleres horisontalt.

For å hente en commit eller referanse fra Pando må et sett med referanseversjoner (pakkefiler) spilles av på nytt hos klienten. Dette rekonstruerer tilstanden til klienten som opprettet referanseversjonen som hentes. Dette gir en svakere form for konsistens på tvers av hele lageret, samtidig som oppdateringer av enkeltreferanser er serialiserbare. Klienter kommer bare i konflikt når de pusher til samme referanse samtidig. En klient som leser flere grener, kan ikke anta at lesingene gjenspeiler ett og samme tidspunkt i hele lageret.

Til venstre inneholder tre påfølgende hovedgrenpakker commitene A, B og C; D og E; og F. Til høyre deler hovedgrenen og temagrenen seg ved C. Hovedgrenen går videre til G, mens temagrenen går videre gjennom D, E og F. Den nyeste temagrenpakken inneholder D, E og F. En stiplet ramme markerer at D og E også finnes i den tidligere temagrenpakken. Den blå pilen markerer den nye pakkens avhengighet av hovedgrenen ved G. E er forelder til F i Git.

En push til hovedgrenen legger til en inkrementell pakke med én eller flere commiter. Hver push til en sidegren inkluderer grenens tidligere commiter på nytt, slik at en klient med den annonserte hovedgrenversjonen kan hente sidegrenen i én pakke.

Dette er en form for kausal konsistens. For å hente en referanse må den lesende klienten se historikken til klienten som opprettet den. Under en push styrer Pando hvilken felles historikk klienten og serveren blir enige om. Det sikrer at klientene lager pakkefiler som er enklere å dele.

Dermed gir ikke Pando et øyeblikksbilde av alle referanser under Git-operasjoner, eller en felles rekkefølge for referanseoppdateringer i en kloning eller henting. Anta for eksempel at grenene foo og bar begge oppdateres under forhandlingen om en full kloning. Under forhandlingen kan én klient se foo i versjon 1 og bar i versjon 2, mens en annen klient ser foo i versjon 2 og bar i versjon 1. I praksis er brukere og agenter ikke avhengige av den globale rekkefølgen på referanseoppdateringer, men av rekkefølgen på oppdateringene av én enkelt referanse.

Sammenligning av tilnærminger

I denne simuleringen av de tre konsistensmodellene setter vi opp 1 000 klienter som velger hver sin gren og deretter pusher unike commiter gjentatte ganger. Vi måler den totale medgåtte tiden og forsinkelsen fra push til henting. Det siste er et viktig mål for arbeidslastene, siden det måler tur-retur-tiden når brukere og agenter samarbeider eller bruker andre systemer som er avhengige av versjonskontroll. Simuleringen modellerer maskinvare fra standardklasse til premiumklasse for virtuelle maskiner og bloblagringssystemer. I denne simuleringen har vi lempet på kravet om konsistens i hele lageret for trefase-commit.

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.

Kjør denne simuleringen for å sammenligne Pando med to andre tilnærminger. Velg infrastruktur fra standard- til premiummaskinvare, antall klienter og andre innstillinger for å se hvordan de står seg mot hverandre.

Det modellerte systemet med trefase-commit viser betydelig høyere skriveforsinkelse under belastning når diskene når kapasitetsgrensen. Systemer med forhåndslogg viser derimot betydelig høyere leseforsinkelse av samme grunn, ettersom serverne sliter med å holde tritt med lesingen fra journalen. Både i praktiske tester på monorepoet vårt og i denne simuleringen støtter Pando mange tusen klienter som kan pushe samtidig uten at ytelsen svekkes under belastning.

Når Pando håndterer en push, validerer det den innkommende pakkefilen mens den strømmes til bloblageret, uten å pakke den ut i et lokalt lager. Når en klient starter en henting, velger Pando de lagrede pakkene klienten trenger, og klienten laster dem ned. Ingen server trenger å bruke pakkefilene til å oppdatere et lokalt lager eller utføre kostbar utpakking og pakking. Siden disse serverne først og fremst håndterer forhandling og valg av pakkefiler, kan Pando-servere kjøre på standard maskinvare.

Dermed kan Pando skalere helt opp til kapasitetsgrensene til klientene, nettverket mellom klientene og Pando, og bloblagringstjenesten. Med nok kapasitet kan klienter som arbeider på uavhengige grener, overføre pakker så raskt som deres egen forbindelse tillater. For å betjene flere klienter med samme hastighet kan vi legge til Pando-servere og skalere metadata- og bloblagrene i takt 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.

Stream the push

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

En agent redigerer filer, oppretter en commit lokalt og kjører git push, som lager en pakkefil med endringene for å oppdatere en gren. Pando mottar pakken og strømmer den til bloblageret. Etter å ha validert den innkommende push-operasjonen med én dekodingsgjennomgang bruker Pando en transaksjon over flere nøkler til å registrere en referanseversjon i metadatalageret vårt og oppdatere referansepekeren atomisk. Dette gjør det mulig for Pando å tilby at klienter alltid kan lese det de selv har skrevet. Hele denne skriveoperasjonen krever O(1)O(1) metadatalesinger og -skrivinger.

Når en annen klient henter grenen, velger Pando et sett med lagrede pakker ut fra historikken klienten allerede har. Denne operasjonen krever O(log⁡Δ)O(\log \Delta) metadatalesinger, der Δ\Delta er den korteste avstanden målt i referanseversjoner mellom det klienten har, og det den ønsker. Serveren returnerer URI-er til pakkefiler, og klienten laster dem ned via tjenesten Express, som lagrer pakkefiler i regionale hurtigbuffere.

Denne utformingen bygger på de tre sentrale tjenestene i Pandos arkitektur: push, henting og komprimering.

Skalering av push

Når en klient pusher, er en tradisjonell Git-server som en pokerspiller som viser alle kortene sine: Den viser klienten hver eneste gren den har. Klienten samler deretter commiter, trær og blobber for å lage en pakkefil som utelater objekter den vet at serveren allerede har.

Å annonsere flere referanser kan redusere størrelsen på push-operasjonen. Det kan også gjøre pakken vanskeligere å gjenbruke, fordi en annen klient kan mangle objekter som den skrivende klienten utelot.

Pando bruker en annen tilnærming. I stedet for å vise alle kortene annonserer det bare et utvalg grener til klienten. Det er to grunner til dette:

  1. Det er kostbart å gi en konsistent oversikt over alle referanser. Med millioner av grener kan selve referanseannonseringen overstige 100 MiB. Dette krever at hver server enten vedlikeholder en oppdatert oversikt, med betydelig koordineringsarbeid, eller kjører en databasespørring som skalerer med antallet referanser.

  2. Ved å gi klienten bare et utvalg av referansene vil klienten generere en pakke som kun er avhengig av det serveren uttrykkelig lar den utelate. Ved å holde oversikt over avhengighetene kan Pando gjenbruke pakkefilen en klient genererer i en push, som svar på en senere henting.

Jo flere referanser en Git-server annonserer, desto flere kilder har pushende klienter å utelate objekter fra. Dermed kan de lage mindre pakker. Men for å gjenbruke disse pakkefilene må serveren kunne bevise at klientene som henter dem, har alle avhengighetene. Ellers kan pakkefilen mangle objekter klienten trenger.

Hvis en Git-server derimot ikke annonserer noen referanser, vil klienten generere en pakkefil som inneholder hele historikken til lageret. Denne pakkefilen kan brukes av alle andre klienter, men prisen er høy: Henting av en hvilken som helst gren blir i praksis en full kloning.

Kurve som viser hvordan annonserte referanser påvirker pakkefilstørrelsen: fra full historikk uten referanser, via annonsering av hovedgrenen, til bare nye objekter når alle referanser annonseres.

Annonsering av flere referanser gir mindre pakkefiler. Det meste av størrelsesreduksjonen kan oppnås ved å annonsere bare én.

Som standard annonserer Pando bare en utpekt hovedgren som grunnlag når en klient pusher en ny funksjonsgren. Det holder størrelsen på annonseringen konstant selv om lageret får flere grener, men innebærer noen kompromisser. Når en utvikler enten pusher til én gren mange ganger eller bygger én funksjonsgren oppå en annen, må klienten sende alt utenfor hovedgrenen på nytt.

Dermed blir påfølgende push-operasjoner til grenen større, med potensielt overflødige objekter, og bruker mer av klientens båndbredde. I monorepoet vårt var medianstørrelsen på pakkefiler laget på denne måten under 20 KiB, og 99-persentilen var under 3 MiB. For oss var dette et fornuftig kompromiss, fordi en lesende klient som allerede har den annonserte hovedgrenhistorikken, kan hente grenen med denne ene pakkefilen.

Etter å ha valgt hvilke referanser som skal annonseres til klienten, godtar Pando push-operasjonen og oppdaterer metadatalageret. Pando deler metadataoppdateringen i en synkron og en asynkron del. Den synkrone metadataoppdateringen sikrer at klienter umiddelbart kan hente den nylig oppdaterte commiten. Den skriver en ny referanseversjon og oppdaterer referansepekeren til den nye versjonen. Samtidig lagres det nye endepunktet, avhengighetene som ble brukt til å forhandle om pakkefilen, og en lenke til pakkefilen som ble pushet.

Asynkront arbeid, som planlegging av komprimering, registreres samtidig i den samme transaksjonen. Ved svært store push-operasjoner skrives bare et utvalg av commitene synkront. Dermed trenger vi bare et konstant antall lesinger og skrivinger i metadatalageret, uavhengig av størrelsen på push-operasjonen.

Et viktig poeng er at Pando-servere kan ta imot push-operasjoner til uavhengige referanser hver for seg. Den eneste nøkkelen det kan oppstå konkurranse om, er referansepekeren. Den beskytter vi med en atomisk operasjon som ligner compare-and-swap. Vi kan øke gjennomstrømmingen ved skriving ved å legge til Pando-servere, skaleringsenheter i metadatalageret eller bloblagre.

Henting og kloning med full nettverkshastighet

Når en klient henter data, gir den serveren to sett med commit-ID-er, kjent i protokollen som have og want. En tradisjonell Git-server beregner mengdedifferansen mellom objektene som kan nås fra have-oppføringene og objektene som kan nås fra want-oppføringene, komprimerer dem og sender resultatet til klienten. Selv om Git-serveren med åpen kildekode har ulike lokale indekser som gjør operasjonen raskere, kan disse pakkefilene være kostbare å beregne og er generelt lite egnet for hurtigbufring.

Pando bruker i stedet commit-koblingene som registreres ved push, til å slå opp klientens have og want og finne et sett med referanseversjoner som inneholder disse commitene. Hver av disse referanseversjonene lenker til en pakkefil Pando har mottatt tidligere. Serveren bruker dette til å finne ut hva klienten trenger, og velger pakkefilene som dekker det nødvendige settet med 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.

Already heldFetchMissing branch dependency

Fetch 27 trunk packs.

Select a branch to include its trunk dependencies.

Pando kobler Git-protokollens have- og want-oppføringer til en sekvens av referanseversjoner. Å be om en sidegren innebærer også å be om historikken frem til avhengighetene den har i hovedgrenen.

Git-protokollen tillater bare at én pakkefil sendes i HTTP-svaret ved kloning eller henting. En undervurdert funksjon i Git-protokollen, packfile-uri, lar imidlertid serveren returnere lenker for å laste ned flere pakker separat. Pando bruker utelukkende denne funksjonen og sender en tom pakkefil direkte i svaret. For å støtte denne funksjonen bidro OpenAI med flere forbedringer av packfile-uri til hovedprosjektet.

OpenAIs systemer kjører i mange geografiske regioner, så vi har Pando-klynger med lokale hurtigbuffere i hver av dem. Pakkeleveringstjenesten Express hurtigbufrer bloblageret vårt, og en egen tjeneste hurtigbufrer metadatalageret. Disse hurtigbufferne gjør at Pando kan betjene forespørsler om en commit-ID eller en spesiell lesing av en referanse utelukkende fra den hurtigbufrede tilstanden i klyngen. Vi bruker konsistent hashing til å rute forespørsler om pakkefiler til Express-servere, med en treffrate i hurtigbufferen på over 99 %.

Dette systemet av hurtigbuffere gir svært høy gjennomstrømming og kan utnytte hele nettverkskapasiteten mellom klientene og Pando. Med disse pakkefil-URI-ene og lokale hurtigbufferne har vi bekreftet at kloning og henting fullt ut utnytter nettverksforbindelser på 10 Gbps, selv med bare standard virtuelle maskiner fra skyleverandører.

Komprimering og tid til første byte

For de fleste Git-servere er ompakking og søppeltømming nødvendig for å opprettholde ytelsen i et voksende lager. Begge trinnene frigjør plass, men krever mye beregningskapasitet og I/O.

I et lager med raske endringer, som OpenAIs monorepo, er det ikke praktisk mulig å sende en egen pakkefil-URI for hver push som har skjedd siden forrige henting. Hver dag måtte en klient da laste ned titusenvis av pakkefiler for å oppdatere hovedgrenen sin.

I stedet slår Pando sammen pakker i bakgrunnen for å redusere dette merarbeidet, med en utforming som ligner komprimering i loggstrukturerte flettetrær. Hver push produserer det Pando kaller en «L0»-pakkefil, som er den minste dataenheten Pando kan overføre. Over tid samler bakgrunnsprosesser disse pakkefilene i dekningsgrupper som spenner over en sekvens av referanseversjoner og skalerer geometrisk fra L0. I dag bruker monorepoet vårt dekningsgrupper for pakkefiler opp til L4.

Disse dekningsgruppene har to formål. Når en dekningsgruppe er tilstrekkelig full, tar en komprimeringsprosess hånd om den og produserer én enkelt pakkefil. Dette gir bedre komprimeringsgrad ved å omorganisere objektene i pakkefilen. Men det gjør også forhandlingen ved henting raskere ved å fungere som en hoppliste over referanseversjoner. Ved å skanne indeksene på hvert nivå samtidig kan Pando velge et sett med pakker som skal sendes til en klient, i logaritmisk tid. Disse dekningsgruppene for pakkefiler, enten de er pakket om eller ikke, gjør henteplanleggingen mer effektiv og reduserer kravene til både gjennomstrømming og forsinkelse i Pando.

Compaction in Pando

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

Compaction in progressPublished cover

Komprimering slår sammen pakkefiler fra mange referanseversjoner i bakgrunnen, slik at henting blir mer effektiv med færre filer.

I motsetning til på mange Git-servere beholdes pakkefiler på alle nivåer, og de verken forkastes eller fjernes ved søppeltømming. Pakker på alle nivåer brukes kontinuerlig til å betjene klienter, basert på have og want som klientene oppgir ved henting. For klienter som bruker Pando som et API, krever lesing av et vilkårlig objekt fra en pakkefil vanligvis mindre datalesing fra L0 enn fra L4, fordi objektet med mindre sannsynlighet er komprimert.

Komprimering i Pando reduserer den totale datamengden som overføres til klientene betydelig, og gjør at pakkeplanleggingen skalerer sublineært med hvor langt bak klienten ligger. Dette er særlig viktig for store lagre med raske endringer, noe vi forventer vil bli den nye normalen innen programvareutvikling.

Utviklingen av Pando

Vi bygget Pando med Codex og gikk fra oppstart til produksjon på bare 10 uker. Kjernen i Pando ble utformet av én utvikler, implementert av team av agenter og satt i drift av en håndfull utviklere. Pando begynte å betjene produksjonstrafikk i april i år og inngår i et større system av versjonskontrollverktøy som brukes hos OpenAI.

Vi brukte et bredt spekter av tilnærminger for å validere Pandos utforming. Vi fulgte prinsippene våre for å bygge sikrere programvarefundamenter⁠. Siden vi startet fra bunnen av, valgte vi å implementere Pando i Rust uten avhengigheter til eldre systemer. Vi skrev en samsvarstestsuite slik at agenter kunne teste endringene sine mot standardoppførselen i Git. Vi utviklet også rammeverk for deterministisk simuleringstesting som lot oss kontrollere systemets invarianter og egenskaper. Dermed kunne vi finne og begrense subtile kappløpstilstander og regresjoner i forhandling og pakkeplanlegging.

Mange modeller bidro til dette systemet. Etter hvert som nye modeller ble tilgjengelige, brukte vi dem til å forbedre Pandos ytelse og pålitelighet. Med GPT‑6 Astra skrev agenter Lean og TLA+ for å formalisere en «teori om Pando». Vi brukte den til å finne og rette feil i implementasjonen og bevise egenskaper ved systemet. Flere sammenhengende økter kjørte i hundrevis av timer for å finne og rette feil i grensetilfeller i Pando. Vi kjører også agenter døgnet rundt for å sikkerhetsteste Git og Pando med simulerte angrep, ved hjelp av modeller med Daybreak-kapasitet.

Utrulling av Pando

Vi satte først Pando i drift som en proxy som videresendte lesinger og skrivinger til det eksisterende versjonskontrollsystemet vårt. Det lot oss holde Pando og det tidligere systemet synkronisert, slik at vi trygt kunne flytte trafikken til Pando uten nedetid.

Som en del av hurtigbufferutformingen kan Pando også deduplisere og slå sammen forespørsler før de videresendes. Dermed kunne vi holde de eksisterende verktøyene stabile mens vi flyttet ett system om gangen til Pando. Etter hvert som vi flyttet flere arbeidslaster til Pando, fylte push-operasjonene deres Pandos hurtigbuffer og forbedret påliteligheten til den tidligere backenden vår.

For å optimalisere ytelsen med Pando begynte vi å rulle ut vår offentlige Git-distribusjon til utviklermaskiner og som en del av ChatGPT‑skrivebordsappen. Vi brukte et internt omslag rundt Gits kommandolinjegrensesnitt for å flytte klientene til Pando trinnvis. Vi laget mange andre verktøy for håndtering av endringsforespørsler og kodegjennomgang, samt API-er for automatisering. Sammen med Git-trafikken flyttet vi hundrevis av integrasjoner som var avhengige av eldre API-er, webhooks og lagernedlastinger.

Codex hjalp selv utviklere og team med overgangen. Vi opprettet ferdigheter for å klargjøre eksisterende arbeidskopier og veilede agenter i bruken av det nye systemet. Dermed kunne utviklere ganske enkelt be Codex om å flytte utviklingsmiljøene og tjenestene deres.

Overgangen til Pando gjorde flettinger mer pålitelige og reduserte medianforsinkelsen for push med nesten 80 %.

Pando kan levere én petabyte Git-data per dag med bare en håndfull CPU-kjerner. Pando har levert over 57 terabyte Git-pakker på én time, med mer enn 99 % av bytene fra lokal diskhurtigbuffer.

Gjenbruk av pakker lar mange klienter laste ned den samme lagerhistorikken med lite beregningsarbeid på nodene som leverer dataene. Dette eksemplet viser ett enkelt lager, men Pando er laget for å støtte mange flere. Etter hvert som etterspørselen øker, kan vi skalere datalevering og Git-forhandling uavhengig av hverandre.

Bidrag tilbake til Git-prosjektet

Ved å bygge Pando på Git-protokollen og -klienten fant vi feil og muligheter til å forbedre Gits egen ytelse.

Pando leverer for eksempel gjenbrukbare pakker direkte til klientene. Derfor ble nedlasting og indeksering av disse pakkene sentralt for å gjøre systemet raskt og pålitelig. Dette arbeidet avdekket grensetilfeller knyttet til samtidige nedlastinger, opprydding etter avbrutte overføringer og gjenopptakelse av nedlastinger som allerede var fullført. Vi rettet disse kodebanene, la til validering når en eksisterende pakke godtas for en henting, og bidro med arbeidet til hovedprosjektet.

Vi trengte også å se hva tiden og båndbredden gikk til. Derfor la vi til sporing av packfile-uri-nedlastinger og bytetelling for pakkegenerering, slik at det ble enklere å se om en treg henting brukte tiden på å generere pakker eller overføre dem.

I OpenAIs Git-utgave⁠(åpnes i et nytt vindu) kan klienten laste ned og indeksere uavhengige pakker parallelt. Vi innførte konseptet «eksplisitte have-oppføringer», slik at en server kan annonsere det gjeldende endepunktet til en gren separat fra historikken en pushende klient kan utelate fra pakken sin. I Pando lar dette klienter bruke --force-with-lease til å kontrollere at en funksjonsgren ikke har flyttet seg, og bygge pakken med bare hovedgrenen som grunnlag. Alle som har denne hovedgrenhistorikken, kan deretter hente grenen med den samme pakken.

Flere av disse forbedringene finnes allerede i Gits hovedprosjekt, og mange andre ligger i det offentlige lageret vårt. Vi fortsetter samarbeidet med Git-miljøet for å gjøre disse forbedringene nyttige også utenfor Pando. Du kan følge arbeidet i vår offentlige endringsoversikt⁠(åpnes i et nytt vindu).

Veien videre

Ved å utfordre etablerte oppfatninger om hvordan Git-lagre skal skaleres, gjør Pando det mulig å skalere monorepoet vårt uten å endre Git-arbeidsflytene som utviklerne og agentene våre allerede bruker. I monorepoet vårt har Pando muliggjort mer parallelt arbeid, gjort flettinger mer pålitelige og redusert tiden det tar å publisere kodeendringer. Vi utforsker også andre bruksområder for Pando i OpenAI, utover monorepoet. Blant dem er arbeidslaster der et stort antall agenter må samarbeide i et felles Git-lager.

Etter hvert som utviklere og agenter produserer mer programvare, vil flere team utenfor OpenAI støte på de samme begrensningene i versjonskontroll som vi gjorde. Vi undersøker nå hvordan vi kan dele teknologien som gjør Pando mulig, med brukerne våre.

Pando bygger på to tiår med erfaring fra dem som bruker og drifter Git-lagre. Vi vil fortsette å utvikle Pando og samarbeide med Gits åpen kildekode-miljø om å dele forbedringer som kommer flere Git-brukere til gode.

Forfatter

OpenAI