OpenAI

2 november 2026

Engineering

Git opschalen bij OpenAI

Versiebeheer bouwen voor werk op de schaal van agents, en tegelijk investeren in het Git-ecosysteem.

Door Aaron Friel, Taylor Blau en Ted Nyman, leden van het technische team

Bezig met laden...

Het afgelopen jaar zijn zowel de omvang van onze monorepo als de eisen die we eraan stellen in een duizelingwekkend tempo toegenomen. Het was duidelijk dat we ons versiebeheersysteem moesten vernieuwen om het gelijktijdige werk aan te kunnen dat Codex mogelijk maakt.

We bouwden Pando, een nieuwe backend voor Git-repository's, zonder ontwikkelwerk te onderbreken of te veranderen hoe ons team Git gebruikt. Pando ondersteunt nu al het ontwikkelwerk in de monorepo van OpenAI. Pando is ontworpen met twee ambitieuze doelen: ten eerste horizontaal schaalbare schrijfbewerkingen, geoptimaliseerd voor gelijktijdige pushes naar veel branches. En ten tweede een maximale leesdoorvoer, zodat clients data snel genoeg kunnen downloaden om hun beschikbare netwerkbandbreedte volledig te benutten.

We bereikten deze doelen met behoud van compatibiliteit met het Git-protocol en de Git-client, zodat onze engineers en agents Pando naadloos konden opnemen in hun bestaande workflows. In productie bracht Pando de mediane pushlatentie terug tot bijna een vijfde. Het levert nu bijna een petabyte aan Git-data per dag.

Hier bespreken we waarom we een nieuw versiebeheersysteem bouwden en waarom we de compatibiliteit met Git wilden behouden. We vergelijken ons systeem met gangbare benaderingen voor het opschalen van Git en leggen uit waarom Pando bij uitstek aan de schaalbaarheidseisen van OpenAI kan voldoen. Ook bespreken we de volgende stappen en hoe we blijven bijdragen aan Git om werk op de schaal van agents te ondersteunen.

Waarom we een nieuw versiebeheersysteem nodig hadden

Codex heeft de schaal en het tempo van ontwikkeling bij OpenAI veranderd, waardoor engineers veel meer werk parallel kunnen uitvoeren. Dat betekent meer branches en pushes, meer merges, en meer fetches en clones vanuit ontwikkelomgevingen, onderzoekstaken en CI.

Die belasting stapelt zich op. Eén engineer heeft nu meerdere wijzigingen tegelijk klaar voor CI, en elke push kan meerdere taken starten die dezelfde commit ophalen.

Rond het einde van vorig jaar kregen de systemen achter onze monorepo moeite om dit bij te houden. We hadden last van trage fetches en pushes en mislukte merges, waarbij de betrouwbaarheid soms onder de 90% zakte. Hadden we een grens van Git zelf bereikt, of van de manier waarop we onze monorepo beschikbaar stelden? Git is ontstaan uit een community die kernelpatches via e-mail uitwisselde. Al meer dan twintig jaar passen ontwikkelaars hun tools rond Git aan, terwijl hun manier van software bouwen verandert. Toen we in onze eigen monorepo tegen knelpunten aanliepen, vonden we het belangrijk om op dat werk te kunnen blijven voortbouwen.

We zagen een manier om die vertrouwde workflow te behouden en tegelijk de backend opnieuw op te bouwen, zodat deze pushes naar verschillende branches parallel kon verwerken en dezelfde geschiedenis gelijktijdig aan veel lezende clients kon leveren. Door Git als basis voor onze versiebeheerworkflow te gebruiken, konden verbeteringen aan de client ook buiten OpenAI van nut zijn. We wilden die verbeteringen vanuit onze kopie van Git⁠(opent in een nieuw venster) bijdragen aan het upstreamproject en zo delen met de bredere community.

Ontwerpen voor Git op grote schaal

Het opschalen van een versiebeheersysteem kent twee essentiële dimensies: veel schrijvende clients die naar verschillende branches pushen, en veel lezende clients die dezelfde geschiedenis ophalen. Pando optimaliseert beide.

Ten eerste coördineert Pando updates aan elke branch afzonderlijk. Updates aan onafhankelijke branches kunnen parallel verlopen, en een client die een branch ophaalt, krijgt de geschiedenis die nodig is voor die revisie. De keerzijde is dat een client die meerdere branches bekijkt, deze in toestanden van verschillende tijdstippen kan zien.

Ten tweede hergebruikt Pando packbestanden (het Git-formaat om meerdere objecten samen te comprimeren) voor verschillende fetches, in plaats van voor elke client een apart packbestand te genereren. Een client downloadt misschien enkele objecten die hij al heeft, maar de server kan met weinig rekenwerk dezelfde bytes aan veel clients leveren.

Dankzij deze keuzes kunnen we schrijfbewerkingen over branches opschalen en leesverzoeken vanuit caches afhandelen. Om te begrijpen hoe dit samenwerkt, kijken we eerst hoe Git geschiedenis opslaat en overdraagt.

De basis van objectopslag in Git

Git moet twee onderdelen van een repository gesynchroniseerd kunnen houden: objecten en referenties.

Een Git-repository legt momentopnamen van bestanden en de geschiedenis die ze verbindt vast als een graaf van objecten. Een object kan een blob, tree, commit of tag zijn. Blobs bevatten de inhoud van één bestand. Trees koppelen namen aan blobs of andere trees en vormen zo een mappenstructuur. Commits verwijzen naar een root-tree en nul of meer parentcommits en vormen zo de geschiedenis van je repository.

Objecten worden geïdentificeerd door een hash van hun type en inhoud. Objecten zijn ook onveranderlijk: het vastleggen van een gewijzigd bestand maakt een nieuwe blob aan, terwijl ongewijzigde bestanden en mappen bestaande objecten kunnen hergebruiken.

Branches en tags zijn referenties, oftewel "refs", die naar commits verwijzen. Committen op een branch maakt een nieuwe commit aan en werkt de ref van de branch bij zodat deze ernaar verwijst.

De branch refs/heads/main verwijst naar commit C2, waarvan C1 de parent is. C1 verwijst naar root-tree T1, waarin parser.py naar blob B1 verwijst. C2 verwijst naar root-tree T2, waarin parser.py naar blob B2 verwijst. In beide root-trees verwijst docs/ naar dezelfde tree, T3. Daarin verwijst README.md naar blob B3 en usage.md naar blob B4.

Git-objecten verwijzen naar elkaar via hun ID: commits naar trees, en trees naar andere trees en blobs. De branch refs/heads/main verwijst naar commit C2, waarvan C1 de parent is. C1 verwijst naar root-tree T1, waarin parser.py naar blob B1 verwijst. C2 verwijst naar root-tree T2, waarin parser.py naar blob B2 verwijst. In beide root-trees verwijst docs/ naar dezelfde tree, T3. Daarin verwijst README.md naar blob B3 en usage.md naar blob B4.

Git slaat objecten "los" of "gebundeld" op. Losse objecten worden opgeslagen in afzonderlijke bestanden, geïdentificeerd door hun object-ID. Gebundelde objecten worden samen opgeslagen in een packbestand, waardoor vergelijkbare objecten als delta's ten opzichte van elkaar kunnen worden opgeslagen.

Onder $GIT_DIR/objects staan twee versies van parser.py. Als losse objecten bevat blob A parse() en bevat blob B zowel parse() als parse_file(). Het packbestand slaat A volledig op en geeft B weer als instructies om heel A te kopiëren en parse_file() toe te voegen. Het bijbehorende *.idx-bestand geeft op basis van object-ID's de locatie van beide blobs in het packbestand aan.

Git kan blobs als afzonderlijke bestanden opslaan of samen inpakken, waarbij de ene blob wordt gecodeerd als een delta ten opzichte van de andere.

In Git is een object bereikbaar vanuit een commit als Git het kan vinden door vanaf die commit verwijzingen te volgen via zijn parents, trees en blobs. Tijdens een fetch vertelt de client de server welke commits hij wil en welke hij al heeft. De server zoekt de objecten die vanuit de gewenste commits bereikbaar zijn, sluit de objecten uit waarvan hij weet dat de client ze al heeft en verstuurt de rest als een packbestand in hetzelfde formaat dat Git op schijf gebruikt. Een volledige clone begint zonder objecten en heeft dus alle objecten nodig die bereikbaar zijn vanuit de geschiedenis die wordt gekloond.

Een push draagt objecten in de andere richting over door de server te vragen een of meer refs bij te werken. De client gebruikt de door de server bekendgemaakte refs om geen objecten te verzenden die de server al heeft. Het verzonden pack kan daardoor afhankelijk zijn van bestaande objecten: alleen het pack ontvangen is niet genoeg om de nieuwe geschiedenis te reconstrueren.

Een Git-repository repliceren betekent zowel objecten als referenties op meerdere servers beschikbaar houden. Voordat een replica de nieuwe waarde van een referentie kan aanbieden, moet deze de objecten kunnen leveren die vanuit die waarde bereikbaar zijn. De dienst moet ook concurrerende referentie-updates coördineren: pushes die dezelfde branch van dezelfde oude tip naar verschillende nieuwe commits proberen te verplaatsen. Systemen die alle schrijfbewerkingen naar een repository in één volgorde plaatsen, kunnen ervoor zorgen dat pushes naar onafhankelijke branches op elkaar moeten wachten.

Bestaande architectuurpatronen voor gedistribueerde Git-diensten

Bij het ontwerpen van Pando onderzochten we gangbare benaderingen om versiebeheer met Git op te schalen. De ene coördineert ref-updates tussen repositoryreplica's via three-phase commit (3PC, een protocol om een commit over meerdere nodes te coördineren). De andere ordent updates via een gedeeld write-ahead log (WAL, een logboek waarin wijzigingen worden vastgelegd voordat ze worden toegepast) in blobopslag.

Three-phase commit (3PC)

In het three-phase-commitontwerp dat we modelleerden, bewaart de Git-dienst op meerdere replica's een volledige kopie van de repository. Wanneer een server een push ontvangt, wordt het binnenkomende packbestand gekopieerd en parallel naar alle replica's verstuurd. Om de push te publiceren, moet een meerderheid van de replica's de ref-update accepteren. De server die dit regelt, is de coördinator. Hiervoor zijn meerdere communicatierondes nodig tussen de coördinator en elke andere replica.

Om een referentie-update te accepteren, vergrendelt elke replica zijn kopie van de ref en controleert of deze nog verwijst naar de commit die de client verwacht. De coördinator stuurt vervolgens een pre-commitbericht naar de replica's die de update accepteren. Zodra een meerderheid het pre-commitbericht bevestigt, geeft de coördinator hun opdracht de update uit te voeren. Elke bijgewerkte replica kan vervolgens de nieuwe branchtip bekendmaken vanuit zijn lokale kopie van de repository.

Volledige replica's maken het mogelijk leesverzoeken lokaal af te handelen en garanderen dat clients een consistent beeld van alle referenties krijgen, ongeacht welke replica ze kiezen. Hierdoor worden referenties één voor één bijgewerkt en vereist elke push meerdere retourcommunicaties om de ref-update tussen replica's te coördineren.

Door het wachten op een quorum worden de schijfprestaties van replica's en de communicatielatentie bepalend voor de duur van een push. Naarmate het schrijfvolume groeit, moeten de replica's zowel binnenkomende packs als leesverkeer kunnen bijhouden.

Write-ahead log (WAL)

In het WAL-gebaseerde ontwerp dat we modelleerden, gebruikt de Git-dienst een gedeeld manifest in blobopslag om updates in de hele repository te ordenen. Wanneer een server een push ontvangt, uploadt hij het binnenkomende packbestand naar een blobstore en bereidt hij de ref-update voor in zijn lokale kopie van de repository. Om de push te publiceren, voegt hij aan een gedeeld manifest in de blobstore een item toe dat de ref-update vastlegt en naar het packbestand verwijst. De Git-server werkt het manifest vervolgens bij met een compare-and-swap-bewerking. Die vervangt het manifest atomair of wijst de wijziging af. Als een andere schrijvende client het manifest intussen heeft bijgewerkt, leest de server de tussentijds toegevoegde items, controleert of zijn ref-update nog geldig is en probeert het opnieuw met het al geüploade packbestand.

Schrijvende clients kunnen hun packbestanden parallel uploaden, maar ook pushes naar onafhankelijke branches vereisen een update van hetzelfde manifest. De retourtijd voor een geslaagde compare-and-swap beperkt hoe vaak de repository een nieuw manifest kan publiceren. Een blobstore met lage latentie maakt deze stap snel. Door updates te bundelen kan één manifest meerdere refs tegelijk bijwerken, maar daarvoor moeten updates in een wachtrij worden gezet en neemt de latentie toe.

De blobstore kan packbestanden parallel ontvangen, maar updates aan het gedeelde manifest blijven met elkaar concurreren. Alleen de capaciteit van de blobstore vergroten lost dat knelpunt niet op. Elke replica moet nog steeds packs uit de blobstore lezen en indexeren voordat hij ze lokaal kan aanbieden. Als schrijfbewerkingen sneller binnenkomen dan dit werk kan worden uitgevoerd, raken replica's achter en wachten clients langer om pas gepushte commits op te halen.

De architectuur van Pando

Dat brengt ons bij Pando. Pando is een nieuwe architectuur voor het aanbieden van Git-repository's, ontworpen om op te schalen naar veel clients die gelijktijdig pushen. Zo werkt het:

Randvoorwaarden heroverwegen

Voordat we Pando ontwikkelden, vermoedden we al dat we aan onze eisen konden voldoen zonder concessies te doen aan Git. Wel moesten we enkele uitgangspunten loslaten die de meeste Git-systemen als vereisten beschouwen:

  • Consistentie in de hele repository: Een consistente momentopname van alle refs vereist coördinatie binnen de hele repository. In een systeem dat alle ref-updates in één volgorde plaatst, concurreren pushes naar onafhankelijke branches om één coördinatiepunt.

  • Unieke packbestanden voor elke client: Als twee clients met verschillende lokale geschiedenissen dezelfde branch ophalen, moet de server mogelijk voor elke client een ander pack genereren om geen objecten te verzenden die ze al hebben. Het samenstellen van deze antwoorden kost CPU-capaciteit en schijf-I/O, waardoor opschalen naar grote aantallen lezende clients lastig is.

Pando gebruikt daarom een minder strikt consistentiemodel (causale consistentie) om schrijfbewerkingen op te schalen en hergebruikt packbestanden om leesbewerkingen op te schalen.

Causale consistentie (CC)

Wanneer een client naar een branch pusht, slaat Pando het binnenkomende packbestand als een onveranderlijke blob op in een blobstore. Telkens wanneer de branch opschuift, legt Pando in een metadatastore een ref-versie vast met de nieuwe tip, het pack dat deze heeft aangeleverd en alle eerdere ref-versies waarvan het pack afhankelijk is. Zowel de blobstore als de metadatastore is horizontaal schaalbaar.

Om een commit of referentie uit Pando op te halen, moet een reeks ref-versies (packbestanden) naar de client worden gestuurd. Daarmee wordt de toestand gereconstrueerd van de client die de opgehaalde ref-versie heeft aangemaakt. Dit biedt een zwakkere vorm van consistentie voor de repository als geheel, maar garandeert wel serialiseerbaarheid voor updates aan afzonderlijke refs. Clients komen alleen met elkaar in conflict als ze gelijktijdig naar dezelfde ref pushen. Een client die meerdere branches leest, kan er niet van uitgaan dat die leesbewerkingen één momentopname van de repository beschrijven.

Links bevatten drie opeenvolgende trunkpacks de commits A, B en C; D en E; en F. Rechts splitsen trunk en topic zich bij C. Trunk gaat verder naar G, terwijl topic verdergaat via D, E en F. Het nieuwste topicpack bevat D, E en F. Een stippellijn geeft aan dat D en E ook in het eerdere topicpack aanwezig zijn. De blauwe pijl geeft aan dat het nieuwe pack afhankelijk is van trunk bij G. De parent van F in Git is E.

Een push naar trunk voegt een incrementeel pack toe met een of meer commits. Elke push naar een zijbranch bevat opnieuw de eerdere commits van die topicbranch, zodat een client met de bekendgemaakte trunkversie de topicbranch in één pack kan ophalen.

Dit is een vorm van causale consistentie. Om een referentie op te halen, krijgt de lezende client de geschiedenis te zien van de schrijvende client die deze heeft aangemaakt. Tijdens een push bepaalt Pando welke gedeelde geschiedenis de client en server overeenkomen, zodat clients packbestanden produceren die gemakkelijker te delen zijn.

Daardoor biedt Pando tijdens Git-bewerkingen geen momentopname van alle referenties en ook geen ordening van ref-updates binnen een clone of fetch. Stel bijvoorbeeld dat de branches foo en bar allebei worden bijgewerkt tijdens de onderhandeling voor een volledige clone. Tijdens de onderhandeling kan de ene client foo op versie 1 en bar op versie 2 zien, terwijl een andere client foo op versie 2 en bar op versie 1 ziet. In de praktijk zijn gebruikers en agents niet afhankelijk van de globale volgorde van ref-updates, maar van de volgorde van updates aan één referentie.

Benaderingen vergelijken

In deze simulatie van de drie consistentiemodellen stellen we 1.000 clients zo in dat ze elk een eigen branch kiezen en daar herhaaldelijk unieke commits naartoe pushen. We meten de totale verstreken tijd en de latentie tussen push en fetch. Die laatste is een belangrijke maatstaf voor workloads: hij meet de retourtijd die gebruikers en agents nodig hebben om samen te werken of andere systemen te gebruiken die op versiebeheer steunen. De simulatie modelleert hardware variërend van standaard tot hoogwaardige virtuele machines en blobopslagsystemen. In deze simulatie hebben we de consistentie-eisen voor de hele repository versoepeld voor three-phase 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.

Voer deze simulatie uit om Pando met twee andere benaderingen te vergelijken. Kies infrastructuur variërend van standaard tot hoogwaardige hardware, het aantal clients en andere instellingen om te zien hoe de benaderingen zich tot elkaar verhouden.

Het gemodelleerde three-phase-commitsysteem vertoont onder belasting een aanzienlijk hogere schrijflatentie doordat de schijven hun capaciteitsgrens bereiken. Systemen met een write-ahead log vertonen om dezelfde reden een aanzienlijk hogere leeslatentie: de servers kunnen het lezen van het logboek niet bijhouden. Zowel in praktijktests op onze monorepo als in deze simulatie ondersteunt Pando vele duizenden clients die gelijktijdig kunnen pushen, zonder prestatieverlies onder belasting.

Bij het verwerken van een push valideert Pando het binnenkomende packbestand terwijl het naar blobopslag wordt gestreamd, zonder het uit te pakken in een lokale repository. Wanneer een client een fetch start, selecteert Pando de opgeslagen packs die de client nodig heeft, waarna de client ze downloadt. Geen enkele server hoeft packbestanden op een lokale repository toe te passen of kostbare in- en uitpakbewerkingen uit te voeren. Omdat deze servers voornamelijk onderhandelen en packbestanden selecteren, kunnen Pando-servers op standaardhardware draaien.

Hierdoor kan Pando opschalen tot de grenzen van de clients, het netwerk tussen clients en Pando en de blobopslagdienst. Bij voldoende capaciteit kunnen clients die aan onafhankelijke branches werken elk packs overdragen zo snel als hun eigen verbinding toelaat. Om meer clients op die snelheid te bedienen, kunnen we Pando-servers toevoegen en de metadata- en blobstores mee opschalen.

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

Een agent bewerkt bestanden, maakt lokaal een commit aan en voert git push uit. Dat maakt een packbestand van die wijzigingen om een branch bij te werken. Pando ontvangt het pack en streamt het naar blobopslag. Na validatie van de binnenkomende push door deze in één doorgang te decoderen, gebruikt Pando een transactie over meerdere sleutels om een ref-versie vast te leggen in onze metadatastore en de ref-pointer atomair bij te werken. Hierdoor kan Pando consistentie bieden waarbij een client altijd zijn eigen geschreven gegevens kan lezen. Deze volledige schrijfbewerking vereist O(1)O(1) lees- en schrijfbewerkingen op metadata.

Wanneer een andere client de branch ophaalt, selecteert Pando een set opgeslagen packs op basis van de geschiedenis die de client al heeft. Deze bewerking vereist O(log⁡Δ)O(\log \Delta) leesbewerkingen op metadata, waarbij Δ\Delta de kortste afstand in ref-versies is tussen wat de client heeft en wat de client wil. De server retourneert URI's voor packbestanden, en de client downloadt deze via Express, een dienst die packbestanden opslaat in regionale caches.

Dit ontwerp steunt op de drie centrale diensten in de architectuur van Pando: pushes, fetches en compactie.

Pushes opschalen

Wanneer een client pusht, gedraagt een conventionele Git-server zich als een pokerspeler die al zijn kaarten laat zien: hij maakt elke branch die hij heeft aan de client bekend. De client verzamelt vervolgens de commits, trees en blobs om een packbestand te maken dat objecten uitsluit waarvan hij weet dat de server ze al heeft.

Meer refs bekendmaken kan de push kleiner maken. Het kan het pack ook lastiger herbruikbaar maken, omdat een andere client mogelijk objecten mist die de schrijvende client heeft weggelaten.

Pando pakt het anders aan. In plaats van al zijn kaarten te laten zien, maakt het slechts een selecte set branches aan de client bekend. Dat heeft twee redenen:

  1. Een consistent overzicht van alle refs bieden is kostbaar. Bij miljoenen branches kan alleen al het bekendmaken van de refs meer dan 100 MiB beslaan. Hiervoor moet elke server óf een actuele mapping bijhouden, wat veel coördinatie vergt, óf een databasequery uitvoeren waarvan de kosten meegroeien met het aantal refs.

  2. Door slechts een deel van de refs aan de client te verstrekken, genereert de client een pack dat alleen afhankelijk is van wat de server hem expliciet toestaat weg te laten. Door deze afhankelijkheden bij te houden, kan Pando het packbestand dat een client bij een push genereert hergebruiken als antwoord op een latere fetch.

Hoe meer refs een Git-server bekendmaakt, hoe meer bronnen pushende clients hebben om objecten uit te sluiten en dus kleinere packs te maken. Om deze packbestanden te hergebruiken, moet de server echter kunnen aantonen dat de ophalende clients alle afhankelijkheden hebben. Anders kan het packbestand objecten missen die de client nodig heeft.

Maakt een Git-server daarentegen helemaal geen refs bekend, dan genereert de client een packbestand met de volledige geschiedenis van de repository. Dat packbestand is door elke andere client te gebruiken, maar heeft een groot nadeel: het ophalen van een willekeurige branch wordt feitelijk een volledige clone.

Curve die laat zien hoe bekendgemaakte referenties de grootte van packbestanden beïnvloeden: van de volledige geschiedenis zonder referenties, via het bekendmaken van trunk, tot alleen nieuwe objecten bij alle referenties.

Meer refs bekendmaken levert kleinere packbestanden op. Het grootste deel van die verkleining kan al worden bereikt door slechts één ref bekend te maken.

Wanneer een client een nieuwe featurebranch pusht, maakt Pando standaard alleen een aangewezen trunkbranch bekend als basis. Hierdoor blijft de omvang van de bekendmaking gelijk als de repository meer branches krijgt, maar er zijn ook nadelen. Wanneer een engineer vaak naar dezelfde branch pusht of de ene featurebranch op de andere stapelt, moet de client alles buiten trunk opnieuw verzenden.

Daardoor worden opeenvolgende pushes naar een branch groter, bevatten ze mogelijk overbodige objecten en gebruiken ze extra bandbreedte van de client. In onze monorepo lag de mediane grootte van zo'n packbestand onder 20 KiB en het 99e percentiel onder 3 MiB. Voor ons was dat een redelijke afweging, omdat een lezende client die de bekendgemaakte trunkgeschiedenis al heeft de branch met dat ene packbestand kan ophalen.

Nadat Pando heeft gekozen welke refs het aan de client bekendmaakt, accepteert het de push en werkt het zijn metadatastore bij. Pando splitst de metadata-update op in synchrone en asynchrone delen. De synchrone metadata-update zorgt ervoor dat clients de zojuist bijgewerkte commit direct kunnen ophalen. Hierbij wordt een nieuwe ref-versie geschreven en wordt de ref-pointer naar die nieuwe versie bijgewerkt. De nieuwe tip, de afhankelijkheden die zijn gebruikt om het packbestand overeen te komen en een link naar het gepushte packbestand worden vastgelegd.

Tegelijkertijd wordt in dezelfde transactie asynchroon werk vastgelegd, zoals het inplannen van compactie. Bij zeer grote pushes wordt slechts een deel van de commits synchroon geschreven. Daardoor is slechts een constant aantal lees- en schrijfbewerkingen op de metadatastore nodig, ongeacht de grootte van de push.

Belangrijk is dat Pando-servers pushes naar onafhankelijke refs afzonderlijk kunnen accepteren. De enige sleutel waarvoor concurrentie kan ontstaan, is de ref-pointer. Die beschermen we met een atomaire bewerking die vergelijkbaar is met compare-and-swap. We kunnen de schrijfdoorvoer opschalen door Pando-servers, schaaleenheden voor de metadatastore of blobstores toe te voegen.

Fetches en clones op maximale netwerksnelheid

Bij een fetch geeft de client de server twee sets commit-ID's, in het protocol aangeduid met have en want. Een conventionele Git-server berekent het verzamelingsverschil tussen de objecten die bereikbaar zijn via de haves en die via de wants, comprimeert die objecten en stuurt het resultaat naar de client. Hoewel de opensource-Git-server lokaal verschillende indexen heeft om deze bewerking te versnellen, kan het berekenen van deze packbestanden kostbaar zijn en zijn ze doorgaans lastig te cachen.

Pando gebruikt in plaats daarvan de bij pushes vastgelegde commitmappings om de have- en want-waarden van de client op te zoeken en te vertalen naar een set ref-versies die die commits bevatten. Elk van die ref-versies verwijst naar een packbestand dat Pando eerder heeft ontvangen. De server bepaalt hiermee wat de client nodig heeft en selecteert de packbestanden die alle benodigde objecten bevatten.

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 zet de haves en wants van het Git-protocol om in een reeks ref-versies. Een zijbranch opvragen betekent ook de benodigde geschiedenis opvragen tot aan de afhankelijkheden op trunk.

Het Git-protocol staat bij een clone of fetch slechts één packbestand toe in het HTTP-antwoord. Een ondergewaardeerde functie van het Git-protocol, packfile-uri, stelt een server echter in staat links terug te sturen om aanvullende packs afzonderlijk te downloaden. Pando gebruikt uitsluitend deze functie en stuurt een leeg packbestand mee in het antwoord. Om deze functie te ondersteunen, heeft OpenAI een aantal verbeteringen aan packfile-uri bijgedragen aan het upstreamproject.

De systemen van OpenAI draaien in veel geografische regio's, dus beheren we in elke regio Pando-clusters met lokale caches. Express, een dienst voor het leveren van packs, cachet onze blobstore. Een aparte dienst cachet de metadatastore. Dankzij deze caches kan Pando verzoeken voor een commit-ID of een speciale leesbewerking op een ref volledig afhandelen vanuit de gecachete toestand in dat cluster. We gebruiken consistente hashing om verzoeken voor een packbestand naar Express-servers te routeren, met een cachehitpercentage van meer dan 99%.

Dit cachesysteem levert een extreem hoge doorvoer, waarmee de netwerkcapaciteit tussen clients en Pando volledig kan worden benut. Met deze packbestand-URI's en lokale caches hebben we aangetoond dat clone- en fetchbewerkingen netwerkverbindingen van 10 Gbps volledig benutten, met uitsluitend standaard virtuele machines van cloudproviders.

Compactie en tijd tot de eerste byte

Bij de meeste Git-servers zijn opnieuw inpakken en garbagecollection nodig om de prestaties van een groeiende repository op peil te houden. Beide stappen maken ruimte vrij, maar vergen veel rekenwerk en I/O.

In een repository die snel verandert, zoals de monorepo van OpenAI, is het niet haalbaar om voor elke push sinds de laatste fetch een afzonderlijke packbestand-URI te versturen. Een client zou dagelijks tienduizenden packbestanden moeten downloaden om zijn trunk bij te werken.

Daarom combineert Pando packs op de achtergrond om deze overhead te beperken, met een ontwerp dat lijkt op compactie in log-structured merge-trees. Elke push levert een packbestand op dat Pando "L0" noemt: de kleinste data-eenheid die Pando kan verzenden. Achtergrondworkers voegen deze packbestanden na verloop van tijd samen in bundels die een reeks ref-versies afdekken en vanaf L0 geometrisch in omvang toenemen. Momenteel gebruikt onze monorepo packbundels tot en met L4.

Deze bundels dienen twee doelen. Zodra een bundel voldoende gevuld is, neemt een compactieworker deze in behandeling en maakt er één packbestand van. Dit verbetert de compressieverhouding door de objecten in het packbestand opnieuw te ordenen. Maar het versnelt ook de fetchonderhandeling doordat de bundels als een skiplist over ref-versies fungeren. Door de indexen op elk niveau gelijktijdig te doorlopen, kan Pando in logaritmische tijd een set packs selecteren om naar een client te sturen. Deze packbundels, al dan niet opnieuw ingepakt, maken fetchplanning efficiënter en verlagen de eisen aan zowel doorvoer als latentie voor Pando.

Compaction in Pando

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

Compaction in progressPublished cover

Compactie combineert op de achtergrond packbestanden van veel ref-versies, waardoor fetches efficiënter worden en minder bestanden nodig hebben.

Anders dan bij veel Git-servers blijven packbestanden op elk niveau bewaard: ze worden niet verwijderd of via garbagecollection opgeruimd. Packs van elk niveau worden voortdurend gebruikt om clients te bedienen, op basis van de have- en want-waarden die zij bij een fetch opgeven. Voor clients die Pando als API gebruiken, vereist het lezen van een willekeurig object uit een packbestand doorgaans minder data uit een L0 dan uit een L4, omdat de kans kleiner is dat het object gecomprimeerd is.

Compactie in Pando vermindert de totale hoeveelheid data die naar clients wordt gestuurd aanzienlijk en zorgt dat packplanning sublineair schaalt met de achterstand van de client. Dat is vooral belangrijk voor grote, snel veranderende repository's, waarvan we verwachten dat ze de nieuwe norm worden in softwareontwikkeling.

Pando bouwen

We bouwden Pando met Codex, van de aftrap tot productie in slechts 10 weken. De kern van Pando werd ontworpen door één engineer, geïmplementeerd door teams van agents en uitgerold door een handvol engineers. Pando begon in april van dit jaar productieverkeer af te handelen en maakt deel uit van een groter stelsel van versiebeheertools bij OpenAI.

We gebruikten uiteenlopende benaderingen om het ontwerp van Pando te valideren. We pasten onze principes toe voor het bouwen van veiligere softwarefundamenten⁠. Omdat we van nul af aan begonnen, kozen we ervoor Pando in Rust te implementeren, zonder afhankelijkheden van verouderde systemen. We schreven een conformiteitstestsuite waarmee agents hun wijzigingen konden toetsen aan het standaardgedrag van Git. Ook ontwikkelden we harnesses voor deterministische simulatietests om invarianten en eigenschappen van het systeem te controleren. Daarmee konden we subtiele racecondities en regressies in de onderhandeling en packplanning opsporen en beperken.

Veel modellen hebben bijgedragen aan dit systeem. Zodra nieuwe modellen beschikbaar kwamen, gebruikten we die om de prestaties en betrouwbaarheid van Pando te verbeteren. Met GPT‑6 Astra schreven agents Lean en TLA+ om een "theorie van Pando" te formaliseren. Daarmee spoorden we bugs in de implementatie op, verholpen we ze en bewezen we eigenschappen van het systeem. Meerdere ononderbroken sessies draaiden honderden uren om randgevallen in Pando op te sporen en op te lossen. Ook laten we dag en nacht agents redteamtests uitvoeren op Git en Pando met modellen die Daybreak ondersteunen.

Pando uitrollen

We zetten Pando aanvankelijk in als een read-through- en write-through-proxy voor ons bestaande versiebeheersysteem. Zo konden we Pando en het vorige systeem gesynchroniseerd houden en verkeer veilig naar Pando verplaatsen, zonder uitvaltijd.

Als onderdeel van zijn cacheontwerp kan Pando verzoeken ook ontdubbelen en combineren voordat het ze doorstuurt. Daardoor bleven onze bestaande tools goed functioneren terwijl we systemen één voor één naar Pando overzetten. Naarmate we meer workloads naar Pando migreerden, vulden hun pushes de cache van Pando en verbeterde de betrouwbaarheid van onze vorige backend.

Om de prestaties bij het gebruik van Pando te optimaliseren, begonnen we onze openbare Git-distributie uit te rollen naar ontwikkelaarsmachines en mee te leveren met de ChatGPT‑desktopapp. Met een interne wrapper rond de Git-CLI migreerden we clients gefaseerd naar Pando. We maakten veel andere tools voor wijzigingsverzoeken en codereviews, en API's voor automatiseringen. Naast het Git-verkeer migreerden we honderden integraties die afhankelijk waren van verouderde API's, webhooks en repositorydownloads.

Codex zelf hielp ontwikkelaars en teams bij de overstap. We maakten skills om bestaande checkouts voor te bereiden en agents te begeleiden bij het gebruik van het nieuwe systeem. Daardoor konden ontwikkelaars Codex simpelweg vragen hun ontwikkelomgevingen en diensten te migreren.

De overstap naar Pando verbeterde de betrouwbaarheid van merges en verminderde de mediane pushlatentie met bijna 80%.

Pando kan met slechts een handvol CPU-cores een petabyte aan Git-data per dag leveren. Pando heeft in één uur meer dan 57 terabyte aan Git-packs geleverd, waarbij meer dan 99% van die bytes uit de lokale schijfcache kwam.

Door packs te hergebruiken, kunnen veel clients dezelfde repositorygeschiedenis downloaden met weinig rekenwerk op de nodes die de data leveren. Dit voorbeeld gaat over één repository, maar Pando is ontworpen om er veel meer te ondersteunen. Naarmate de vraag groeit, kunnen we datalevering en Git-onderhandeling onafhankelijk van elkaar opschalen.

Teruggeven aan het Git-project

Door Pando op het Git-protocol en de Git-client te bouwen, ontdekten we bugs en mogelijkheden om de prestaties van Git zelf te verbeteren.

Pando levert bijvoorbeeld herbruikbare packs rechtstreeks aan clients. Het downloaden en indexeren van die packs werd daardoor een essentieel onderdeel van een snel en betrouwbaar systeem. Dat werk bracht randgevallen aan het licht bij gelijktijdige downloads, het opruimen na onderbroken overdrachten en het hervatten van downloads die al voltooid waren. We repareerden die codepaden, voegden validaties toe voor het accepteren van een bestaand pack bij een fetch en droegen dat werk bij aan het upstreamproject.

We moesten ook kunnen zien waaraan tijd en bandbreedte werden besteed. Daarom voegden we tracing toe voor packfile-uri-downloads en bytellingen voor packgeneratie. Zo werd het eenvoudiger te bepalen of een trage fetch tijd kwijt was aan het genereren of overdragen van packs.

In de Git-build van OpenAI⁠(opent in een nieuw venster) kan de client onafhankelijke packs parallel downloaden en indexeren. We voegden het concept "expliciete haves" toe, zodat een server de huidige tip van een branch afzonderlijk kan bekendmaken van de geschiedenis die een pushende client uit zijn pack mag weglaten. In Pando kunnen clients hierdoor met --force-with-lease controleren of een featurebranch niet is opgeschoven en hun pack uitsluitend op basis van trunk samenstellen. Iedereen met die trunkgeschiedenis kan de branch vervolgens met hetzelfde pack ophalen.

Verschillende van deze verbeteringen zijn al opgenomen in upstream Git, en veel andere staan in onze openbare repository. We blijven samenwerken met de Git-community om deze verbeteringen ook buiten Pando bruikbaar te maken. Je kunt dat werk volgen in onze openbare patchtracker⁠(opent in een nieuw venster).

Volgende stappen

Door gangbare aannames over het opschalen van Git-repository's te heroverwegen, stelt Pando ons in staat onze monorepo op te schalen zonder de Git-workflows te veranderen die onze engineers en agents al gebruiken. In onze monorepo heeft Pando meer parallel werk mogelijk gemaakt, de betrouwbaarheid van merges verbeterd en de publicatietijd van codewijzigingen verkort. Naast onze monorepo onderzoeken we ook andere toepassingen voor Pando binnen OpenAI, waaronder workloads waarbij grote aantallen agents moeten samenwerken in een gedeelde Git-repository.

Naarmate engineers en agents meer software produceren, zullen ook meer teams buiten OpenAI tegen de grenzen aanlopen die wij bij versiebeheer bereikten. We onderzoeken momenteel hoe we de technologie achter Pando met onze gebruikers kunnen delen.

Pando bouwt voort op twintig jaar ervaring van gebruikers en beheerders van Git-repository's. We blijven Pando ontwikkelen en werken samen met de opensource-Git-community om verbeteringen te delen waar Git-gebruikers in bredere zin baat bij hebben.

Auteurs

OpenAI