Så skalar vi Git på OpenAI
Vi bygger versionshantering för arbete med agenter i stor skala och investerar samtidigt i Gits ekosystem.
Under det senaste året har både storleken på vårt monorepo och kraven på det ökat i en svindlande takt. Det stod klart att vi behövde göra om vårt versionshanteringssystem för att klara den mängd parallellt arbete som Codex möjliggör.
Vi byggde Pando, en ny backend för Git-förvar, utan att avbryta något utvecklingsarbete eller ändra hur vårt team använder Git. Pando stöder nu all utveckling i OpenAI:s monorepo. Pando är utformat för att uppfylla två ambitiösa mål. Det första är horisontellt skalbara skrivningar, optimerade för samtidiga pushar till många grenar. Det andra är att maximera läskapaciteten, så att klienter kan ladda ned data tillräckligt snabbt för att utnyttja hela sin tillgängliga nätverksbandbredd.
Vi uppnådde dessa mål och behöll samtidigt kompatibiliteten med Git-protokollet och Git-klienten. Det gjorde att både våra utvecklare och agenter smidigt kunde börja använda Pando i sina befintliga arbetsflöden. I produktion minskade Pando medianlatensen för pushar till ungefär en femtedel. Pando levererar nu nästan en petabyte Git-data per dag.
Här går vi igenom varför vi byggde ett nytt versionshanteringssystem och varför vi valde att behålla kompatibiliteten med Git. Vi jämför vårt system med vanliga metoder för att skala Git och beskriver varför Pando är unikt lämpat att möta OpenAI:s skalbarhetsbehov. Vi tar också upp nästa steg och hur vi fortsätter att bidra till Git för att stödja arbete i den skala som agenter möjliggör.
Codex har förändrat utvecklingens omfattning och tempo på OpenAI och gjort det möjligt för utvecklare att utföra mycket mer arbete parallellt. Det innebär fler grenar och pushar, fler sammanslagningar och fler hämtningar och kloningar från utvecklingsmiljöer, forskningsjobb och CI.
Dessa krav förstärker varandra. En utvecklare har nu flera ändringar redo för CI samtidigt, och varje push kan få flera jobb att hämta samma commit.
Mot slutet av förra året började systemen bakom vårt monorepo få svårt att hänga med. Vi drabbades av långsamma hämtningar och pushar samt misslyckade sammanslagningar. Tillförlitligheten sjönk ibland under 90 %. Hade vi nått en gräns för Git i sig, eller för hur vi tillhandahöll vårt monorepo? Git växte fram ur en community som utbytte patchar till kärnan via e-post. I mer än två decennier har utvecklare anpassat sina verktyg kring Git i takt med att sättet att bygga programvara har förändrats. När vi började märka av problemen i vårt eget monorepo var det viktigt för oss att kunna bygga vidare på det arbetet.
Vi såg en möjlighet att behålla det välbekanta arbetsflödet och samtidigt bygga om vår backend för att hantera pushar till olika grenar parallellt och leverera samma historik till många läsande klienter samtidigt. Att använda Git som grund för vårt versionshanteringsflöde innebar att förbättringar av klienten kunde komma till nytta även utanför OpenAI. Vi ville bidra med dessa förbättringar från vår kopia av Git(öppnas i ett nytt fönster) till huvudprojektet och dela dem med den bredare communityn.
Att skala ett versionshanteringssystem har två centrala dimensioner: många skrivande klienter som pushar till olika grenar och många läsande klienter som hämtar samma historik. Pando optimerar för båda.
För det första samordnar Pando uppdateringar av varje gren oberoende av de andra. Uppdateringar av oberoende grenar kan ske parallellt, och en klient som hämtar en gren får den historik som behövs för den revisionen. Avvägningen är att en klient som läser flera grenar kan se dem vid olika tidpunkter.
För det andra återanvänder Pando packfiler (Gits format för att komprimera flera objekt tillsammans) mellan hämtningar, i stället för att skapa en egen packfil för varje klient. En klient kan behöva ladda ned vissa objekt som den redan har, men servern kan leverera samma byte till många klienter med få beräkningar.
Dessa val gör att vi kan skala skrivningar över flera grenar och hantera läsningar från cacheminnen. För att förstå hur detta hänger ihop börjar vi med hur Git lagrar och överför historik.
Git måste kunna hålla två delar av ett förvar synkroniserade: objekt och referenser.
Ett Git-förvar lagrar ögonblicksbilder av filer och historiken som binder ihop dem som en graf av objekt. Ett objekt kan vara en blob, ett träd, en commit eller en tagg. Blobbar innehåller enskilda filers innehåll. Träd kopplar namn till blobbar eller andra träd och bildar på så sätt en katalogstruktur. Committar pekar på ett rotträd och noll eller flera föräldercommittar och bildar förvarets historik.
Objekt identifieras av ett hashvärde baserat på deras typ och innehåll. Objekt är också oföränderliga: när en ändrad fil registreras skapas en ny blob, medan oförändrade filer och kataloger kan återanvända befintliga objekt.
Grenar och taggar är referenser, eller ”refs”, som pekar på committar. När en ny commit skapas på en gren uppdateras grenens referens så att den pekar på den.
Git-objekt hänvisar till varandra med sina ID:n: committar till träd, och träd till träd och blobobjekt. Grenen refs/heads/main pekar på commit C2, vars förälder är C1. C1 pekar på rotträdet T1, där parser.py hänvisar till blob B1. C2 pekar på rotträdet T2, där parser.py hänvisar till blob B2. I båda rotträden hänvisar docs/ till samma träd, T3. Dess README.md-post pekar på blob B3, och usage.md pekar på blob B4.
Git lagrar objekt antingen ”löst” eller ”packat”. Lösa objekt lagras i enskilda filer som identifieras med objektets ID. Packade objekt lagras tillsammans i en packfil, vilket gör att liknande objekt kan lagras som deltan i förhållande till varandra.
Git kan lagra blobobjekt som separata filer eller packa dem tillsammans och koda en blob som ett delta i förhållande till en annan.
I Git är ett objekt nåbart från en commit om Git kan hitta det genom att följa länkar från committen genom dess föräldrar, träd och blobobjekt. Vid en hämtning berättar klienten för servern vilka committar den vill ha och vilka den redan har. Servern hittar de objekt som kan nås från de önskade committarna, utesluter dem som den vet att klienten redan har och skickar resten som en packfil i samma format som Git använder på disk. En fullständig kloning börjar utan några objekt och behöver därför alla objekt som kan nås från historiken som klonas.
En push överför objekt i motsatt riktning genom att be servern uppdatera en eller flera referenser. Klienten använder serverns annonserade referenser för att undvika att skicka objekt som servern redan har. Paketet som skickas kan därför vara beroende av befintliga objekt: det räcker inte att ta emot bara paketet för att återskapa den nya historiken.
Att replikera ett Git-förvar innebär att hålla både objekt och referenser tillgängliga på flera servrar. Innan en replik kan tillhandahålla en referens nya värde måste den kunna leverera de objekt som kan nås från det värdet. Tjänsten måste också samordna konkurrerande referensuppdateringar, det vill säga pushar som försöker flytta samma gren från samma gamla spets till olika nya committar. System som kräver en gemensam ordning för alla skrivningar till ett förvar kan tvinga pushar till oberoende grenar att vänta på varandra.
När vi utformade Pando utvärderade vi vanliga metoder för att skala versionshantering med Git: en som samordnar referensuppdateringar mellan förvarsrepliker med trefas-commit (3PC, ett protokoll för att samordna en commit över flera noder), och en som ordnar uppdateringar via en gemensam förhandslogg i bloblagring (WAL, en logg där ändringar skrivs innan de tillämpas).
I den lösning med trefas-commit som vi modellerade har Git-tjänsten en fullständig kopia av förvaret på flera repliker. När någon server tar emot en push kopieras den inkommande packfilen och skickas till alla repliker parallellt. För att publicera pushen måste en majoritet av replikerna acceptera referensuppdateringen. Servern som hanterar detta är samordnaren, och processen kräver flera kommunikationsrundor mellan samordnaren och varje annan replik.
För att acceptera en referensuppdatering låser varje replik sin kopia av referensen och kontrollerar att den fortfarande pekar på den commit som klienten förväntar sig. Samordnaren skickar sedan ett pre-commit-meddelande till de repliker som accepterar. När en majoritet har bekräftat pre-commit-meddelandet instruerar samordnaren dem att utföra uppdateringen. Varje uppdaterad replik kan sedan annonsera den nya grenspetsen från sin lokala kopia av förvaret.
Fullständiga repliker gör det möjligt att hantera läsningar lokalt och garanterar att klienter får en konsistent vy av alla referenser oavsett vilken replik de väljer. Det innebär att referenser uppdateras en i taget och att varje push kräver flera kommunikationsrundor för att samordna referensuppdateringen mellan replikerna.
Väntan på ett kvorum gör att replikernas diskprestanda och kommunikationslatens påverkar tiden för en push. När skrivvolymen ökar måste replikerna hinna med både inkommande paket och lästrafik.
I den WAL-baserade lösning som vi modellerade använder Git-tjänsten ett gemensamt manifest i bloblagring för att ordna uppdateringar i hela förvaret. När en server tar emot en push laddar den upp den inkommande packfilen till ett bloblager och förbereder referensuppdateringen i sin lokala kopia av förvaret. För att publicera pushen lägger den till en post i ett gemensamt manifest i bloblagret. Posten registrerar referensuppdateringen och pekar på packfilen. Git-servern uppdaterar sedan manifestet med en compare-and-swap-operation. Den ersätter manifestet atomärt eller avvisar ändringen. Om en annan skrivande klient har uppdaterat det under tiden läser servern de tillkomna posterna, kontrollerar att dess referensuppdatering fortfarande är giltig och försöker igen med packfilen som den redan har laddat upp.
Skrivande klienter kan ladda upp sina packfiler parallellt, men även pushar till oberoende grenar kräver att samma manifest uppdateras. Tiden för en lyckad compare-and-swap, tur och retur, begränsar hur ofta förvaret kan publicera ett nytt manifest. Ett bloblager med låg latens gör detta steg snabbt. Batchning gör att ett manifest kan uppdatera flera referenser samtidigt, men innebär att uppdateringar köas och latensen ökar.
Bloblagret kan ta emot packfiler parallellt, men uppdateringar av det gemensamma manifestet konkurrerar fortfarande med varandra. Att enbart öka bloblagrets kapacitet löser inte den flaskhalsen. Varje replik måste fortfarande läsa och indexera paket från bloblagret innan den kan leverera dem lokalt. Om inkommande skrivningar går snabbare än det arbetet halkar replikerna efter, och klienter får vänta längre på att hämta nyss pushade committar.
Det för oss till Pando. Pando är en ny arkitektur för att tillhandahålla Git-förvar, med målet att kunna skala till många klienter som pushar samtidigt. Så här fungerar det:
Innan vi utvecklade Pando anade vi att vi kunde uppfylla våra krav utan att kompromissa med Git. Däremot behövde vi kompromissa med sådant som de flesta Git-system betraktar som krav:
Konsistens i hela förvaret: Att ge en konsistent ögonblicksbild av alla referenser vid en viss tidpunkt kräver samordning i hela förvaret. I ett system som ordnar alla referensuppdateringar i en gemensam sekvens konkurrerar pushar till oberoende grenar om en enda samordningspunkt.
Unika packfiler för varje klient: Om två klienter med olika lokal historik hämtar samma gren kan servern behöva skapa ett eget paket för var och en för att undvika att skicka objekt som de redan har. Att skapa dessa svar kräver CPU-resurser och disk-I/O, vilket gör det svårt att skala upp till ett stort antal läsande klienter.
Pando använder i stället en svagare konsistensmodell (kausal konsistens) för att skala skrivningar, och återanvänder packfiler för att skala läsningar.
När en klient pushar till en gren lagrar Pando den inkommande packfilen som en oföränderlig blob i ett bloblager. Varje gång grenen flyttas registrerar Pando en referensversion i ett metadatalager. Den innehåller den nya grenspetsen, paketet som levererade den och eventuella tidigare referensversioner som paketet är beroende av. Både bloblagret och metadatalagret kan skalas horisontellt.
För att hämta en commit eller referens från Pando måste en uppsättning referensversioner (packfiler) spelas upp för klienten. Det återskapar tillståndet hos den klient som skapade referensversionen som hämtas. Detta ger en svagare form av konsistens i hela förvaret, men ger möjlighet till serialisering av uppdateringar av enskilda referenser. Klienter hamnar bara i konflikt när de pushar till samma referens samtidigt. En klient som läser flera grenar kan inte förutsätta att läsningarna beskriver en och samma tidpunkt i hela förvaret.
En push till huvudgrenen lägger till ett inkrementellt paket med en eller flera committar. Varje push till en sidogren innehåller även grenens tidigare committar, så att en klient med den angivna versionen av huvudgrenen kan hämta sidogrenen i ett enda paket.
Detta är en form av kausal konsistens. För att hämta en referens tar den läsande klienten del av historiken hos den skrivande klient som skapade den. Under en push styr Pando vilken gemensam historik klienten och servern kommer överens om, så att klienterna skapar packfiler som är enklare att dela.
Därför ger Pando varken en ögonblicksbild av alla referenser vid Git-operationer eller en ordning för referensuppdateringar inom en kloning eller hämtning. Anta till exempel att grenarna foo och bar båda uppdateras under förhandlingen för en fullständig kloning. Under förhandlingen kan en klient se foo i version 1 och bar i version 2, medan en annan ser foo i version 2 och bar i version 1. I praktiken är användare och agenter inte beroende av referensuppdateringarnas globala ordning, utan av ordningen på uppdateringarna av en enskild referens.
I denna simulering av de tre konsistensmodellerna konfigurerar vi 1 000 klienter så att var och en väljer en egen gren och sedan upprepade gånger pushar unika committar. Vi mäter den totala förflutna tiden och latensen från push till hämtning. Det senare är ett viktigt mått för arbetsbelastningar, eftersom det mäter tiden för en hel tur och retur när användare och agenter samarbetar eller använder andra system som bygger på versionshantering. Simuleringen modellerar infrastruktur från virtuella maskiner och bloblagringssystem i standardklass till premiumklass. I simuleringen har vi lättat på kravet på konsistens i hela förvaret för trefas-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 den här simuleringen för att jämföra Pando med två andra metoder. Välj infrastruktur från standard- till premiumhårdvara, antal klienter och andra inställningar för att se hur metoderna står sig mot varandra.
Det modellerade systemet med trefas-commit visar betydligt högre skrivlatens under belastning när diskarna når sin kapacitetsgräns. System med förhandsloggning visar i stället betydligt högre läslatens av samma skäl, eftersom servrarna har svårt att hinna läsa från journalen. Både i praktiska tester på vårt monorepo och i denna simulering stöder Pando flera tusen klienter som kan pusha samtidigt utan att prestandan påverkas under belastning.
När Pando hanterar en push valideras den inkommande packfilen medan den strömmas till bloblagringen, utan att packas upp i ett lokalt förvar. När en klient påbörjar en hämtning väljer Pando de lagrade paket som klienten behöver, och klienten laddar sedan ned dem. Ingen server behöver applicera packfiler på ett lokalt förvar eller utföra kostsamma uppacknings- och packningsoperationer. Eftersom servrarna främst förhandlar och väljer packfiler kan Pando-servrar köras på standardhårdvara.
Det gör att Pando kan skalas upp till gränserna för klienterna, nätverket mellan klienterna och Pando samt bloblagringstjänsten. Med tillräcklig kapacitet kan klienter som arbetar på oberoende grenar överföra paket så snabbt som den egna anslutningen tillåter. För att betjäna fler klienter i den hastigheten kan vi lägga till Pando-servrar och skala metadatalagret och bloblagret 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.
En agent redigerar filer, skapar en commit lokalt och kör git push, som skapar en packfil med ändringarna för att uppdatera en gren. Pando tar emot paketet och strömmar det till bloblagringen. Efter att ha validerat den inkommande pushen genom avkodning i en enda genomgång använder Pando en transaktion över flera nycklar för att registrera en referensversion i vårt metadatalager och atomärt uppdatera referenspekaren. Det gör att Pando kan garantera att klienter alltid kan läsa sina egna skrivningar. Hela denna skrivoperation kräver läsningar och skrivningar av metadata.
När en annan klient hämtar grenen väljer Pando en uppsättning lagrade paket utifrån den historik klienten redan har. Operationen kräver metadataläsningar, där är det kortaste avståndet i referensversioner mellan det klienten har och det klienten vill ha. Servern returnerar packfilernas URI:er, och klienten laddar ned dem via tjänsten Express, som lagrar packfiler i regionala cacheminnen.
Denna utformning bygger på de tre centrala tjänsterna i Pandos arkitektur: pushar, hämtningar och komprimering.
När en klient pushar fungerar en vanlig Git-server som en pokerspelare som visar alla sina kort: den avslöjar varje gren den har för klienten. Klienten samlar sedan ihop committar, träd och blobobjekt för att skapa en packfil som utesluter objekt som den vet att servern redan har.
Att annonsera fler referenser kan göra pushen mindre. Det kan också göra paketet svårare att återanvända, eftersom en annan klient kan sakna objekt som den skrivande klienten utelämnade.
Pando använder en annan metod. I stället för att visa hela handen annonserar Pando bara ett urval av grenar för klienten. Det görs av två skäl:
Att ge en konsistent vy av alla referenser är kostsamt. Med miljontals grenar kan enbart referensannonseringen överstiga 100 MiB. Det kräver att varje server antingen håller en aktuell mappning, med betydande samordningskostnader, eller gör en databasfråga vars kostnad växer med antalet referenser.
Om klienten bara får en delmängd av referenserna skapar den ett paket som endast är beroende av det som servern uttryckligen tillåter klienten att utelämna. Genom att spåra beroendena kan Pando återanvända packfilen som en klient skapar vid en push och leverera den som svar på en senare hämtning.
Ju fler referenser en Git-server annonserar, desto fler källor kan pushande klienter utgå från för att utesluta objekt och därmed skapa mindre paket. Men för att återanvända packfilerna måste servern kunna bevisa att de hämtande klienterna har samtliga beroenden. Annars kan packfilen sakna objekt som klienten behöver.
Om en Git-server däremot inte annonserar några referenser alls skapar klienten en packfil med förvarets hela historik. Den packfilen kan användas av alla andra klienter, men till priset av att varje hämtning av en gren i praktiken blir en fullständig kloning.
Fler annonserade referenser ger mindre packfiler. Det kan räcka att annonsera en enda för att uppnå större delen av storleksminskningen.
Som standard annonserar Pando bara en utsedd huvudgren som bas när en klient pushar en ny funktionsgren. Då förblir annonseringen lika stor även när förvaret får fler grenar, men det innebär vissa avvägningar. När en utvecklare pushar till samma gren många gånger eller bygger en funktionsgren ovanpå en annan måste klienten skicka om allt som ligger utanför huvudgrenen.
Det gör att efterföljande pushar till grenen blir större, kan innehålla överflödiga objekt och förbrukar mer bandbredd hos klienten. I vårt monorepo var medianstorleken på packfiler som skapades på detta sätt under 20 KiB, och den 99:e percentilen var under 3 MiB. För oss var detta en rimlig avvägning, eftersom en läsande klient som redan har den annonserade huvudgrenens historik kan hämta grenen med den enda packfilen.
Efter att ha valt vilka referenser som ska annonseras för klienten accepterar Pando pushen och uppdaterar sitt metadatalager. Pando delar upp metadatauppdateringen i synkrona och asynkrona delar. Den synkrona metadatauppdateringen säkerställer att klienter omedelbart kan hämta den nyss uppdaterade committen. Den skriver en ny referensversion och uppdaterar referenspekaren så att den pekar på den nya versionen. Där lagras den nya grenspetsen, de beroenden som användes för att förhandla fram packfilen och en länk till packfilen som pushades.
Samtidigt registreras asynkront arbete, till exempel schemaläggning av komprimering, i samma transaktion. För mycket stora pushar skrivs bara en delmängd av committarna synkront. Därmed behöver vi bara ett konstant antal läsningar och skrivningar i metadatalagret, oavsett pushens storlek.
En viktig egenskap är att Pando-servrar kan ta emot pushar till oberoende referenser utan att samordna dem. Den enda nyckel som kan utsättas för konkurrerande åtkomst är referenspekaren, som vi skyddar med en atomär operation liknande compare-and-swap. Vi kan skala upp skrivkapaciteten genom att lägga till Pando-servrar, skalningsenheter för metadatalagret eller bloblager.
När en klient gör en hämtning skickar den två uppsättningar commit-ID:n till servern, som i protokollet kallas have och want. En vanlig Git-server beräknar mängddifferensen mellan objekten som kan nås från want-posterna och dem som kan nås från have-posterna, komprimerar dem och skickar resultatet till klienten. Git-servern med öppen källkod har visserligen flera lokala index som påskyndar operationen, men packfilerna kan vara kostsamma att beräkna och är i allmänhet svåra att cachelagra.
Pando använder i stället de commit-mappningar som registrerats vid pushar för att slå upp klientens have- och want-poster och hitta en uppsättning referensversioner som innehåller dessa committar. Var och en av dessa referensversioner länkar till en packfil som Pando tidigare har tagit emot. Servern använder detta för att avgöra vad klienten behöver och välja de packfiler som täcker den nödvändiga uppsättningen objekt.
Planning fetches in Pando
Pando compares what a client has with what it wants, then selects the stored packfiles needed—including any missing branch dependencies.
Fetch 27 trunk packs.
Select a branch to include its trunk dependencies.
Pando mappar Git-protokollets have- och want-poster till en sekvens av referensversioner. Att begära en sidogren innebär också att begära historiken fram till dess beroenden i huvudgrenen.
Git-protokollet tillåter bara att en enda packfil skickas i HTTP-svaret vid kloning eller hämtning. Men en underskattad funktion i Git-protokollet, packfile-uri, gör att en server kan returnera länkar för separat nedladdning av ytterligare paket. Pando använder enbart denna funktion och skickar en tom packfil direkt i svaret. För att stödja funktionen bidrog OpenAI med ett antal förbättringar av packfile-uri till huvudprojektet.
OpenAI:s system körs i många geografiska regioner, så vi har Pando-kluster med lokala cacheminnen i varje region. Paketleveranstjänsten Express cachelagrar vårt bloblager, och en separat tjänst cachelagrar metadatalagret. Dessa cacheminnen gör att Pando kan hantera förfrågningar om ett commit-ID eller en särskild läsning av en referens helt utifrån det cachelagrade tillståndet i klustret. Vi använder konsistent hashning för att dirigera packfilsförfrågningar till Express-servrar, med en cacheträffsfrekvens på över 99 %.
Detta system av cacheminnen ger extremt hög kapacitet och kan utnyttja hela nätverkskapaciteten mellan klienterna och Pando. Med dessa packfils-URI:er och lokala cacheminnen har vi verifierat att kloningar och hämtningar kan utnyttja hela kapaciteten i nätverkslänkar på 10 Gbit/s, med enbart vanliga virtuella maskiner från molnleverantörer.
För de flesta Git-servrar är ompackning och skräpsamling nödvändiga för att bibehålla prestandan i ett växande förvar. Båda stegen frigör utrymme, men är beräknings- och I/O-intensiva.
I ett förvar som förändras snabbt, som OpenAI:s monorepo, är det inte praktiskt möjligt att skicka en separat packfils-URI för varje push som gjorts sedan den senaste hämtningen. Varje dag skulle en klient behöva ladda ned tiotusentals packfiler för att uppdatera sin huvudgren.
Pando kombinerar i stället paket i bakgrunden för att minska denna overhead, med en utformning som liknar komprimering i loggstrukturerade merge-träd. Varje push skapar det som Pando kallar en ”L0”-packfil, den minsta dataenhet som Pando kan överföra. Med tiden samlar bakgrundsprocesser dessa packfiler i täckningsgrupper som spänner över en sekvens av referensversioner och växer geometriskt från L0. I dag använder vårt monorepo täckningsgrupper för packfiler upp till L4.
Dessa täckningsgrupper har två syften. När en täckningsgrupp är tillräckligt full tar en komprimeringsprocess hand om den och skapar en enda packfil. Det förbättrar komprimeringsgraden genom att objekten i packfilen omorganiseras. Men det förbättrar också prestandan vid hämtningsförhandlingar genom att fungera som en hopplista över referensversionerna. Genom att söka igenom index på varje nivå samtidigt kan Pando välja en uppsättning paket att skicka till en klient på logaritmisk tid. Dessa täckningsgrupper för packfiler, ompackade eller inte, ger en effektivare hämtningsplanering och minskar kraven på både kapacitet och latens för Pando.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
Komprimering kombinerar packfiler från många referensversioner i bakgrunden och möjliggör effektivare hämtningar med färre filer.
Till skillnad från på många Git-servrar behålls packfiler på varje nivå och varken kastas eller tas bort genom skräpsamling. Paket på alla nivåer används löpande för att betjäna klienter, utifrån de have- och want-poster som de anger vid en hämtning. För klienter som använder Pando som ett API kräver läsning av ett godtyckligt objekt ur en packfil oftast mindre dataläsning från en L0 än från en L4, eftersom objektet mer sällan är komprimerat.
Komprimering i Pando minskar kraftigt den totala datamängden som överförs till klienter och gör att paketplaneringen kan skala sublinjärt med hur långt efter klienten ligger. Det är särskilt viktigt för stora förvar som förändras snabbt, något vi tror kommer att bli det nya normala inom programvaruutveckling.
Vi byggde Pando med Codex och gick från projektstart till produktion på bara tio veckor. Kärnan i Pando utformades av en enda utvecklare, implementerades av flera team av agenter och driftsattes av en handfull utvecklare. Pando började hantera produktionstrafik i april i år och ingår i ett större system av versionshanteringsverktyg som används på OpenAI.
Vi använde många olika metoder för att validera Pandos utformning. Vi tillämpade våra principer för att bygga en säkrare grund för programvara. Eftersom vi började från grunden valde vi att implementera Pando i Rust, utan äldre beroenden. Vi skrev en testsvit för överensstämmelse så att agenter kunde testa sina ändringar mot Gits standardbeteende. Vi utvecklade också körmiljöer för deterministiska simuleringstester för att kontrollera systemets invarianter och egenskaper. Det hjälpte oss att hitta och åtgärda svårupptäckta kapplöpningstillstånd och regressioner i förhandling och paketplanering.
Många modeller bidrog till systemet. När nya modeller blev tillgängliga använde vi dem för att förbättra Pandos prestanda och tillförlitlighet. Med GPT‑6 Astra skrev agenter kod i Lean och TLA+ för att formalisera en ”teori om Pando”. Den använde vi för att hitta och rätta buggar i implementationen och bevisa systemets egenskaper. Flera oavbrutna sessioner kördes i hundratals timmar för att hitta och åtgärda kantfall i Pando. Vi kör också agenter dygnet runt för att säkerhetstesta Git och Pando ur ett angriparperspektiv med modeller som har Daybreak-kapacitet.
Vi driftsatte först Pando som en proxy som vidarebefordrade läsningar och skrivningar till vårt befintliga versionshanteringssystem. Det gjorde att vi kunde hålla Pando och det tidigare systemet synkroniserade och säkert flytta över trafik till Pando utan driftstopp.
Som en del av cachelösningen kan Pando också deduplicera och kombinera förfrågningar innan de vidarebefordras. Därmed kunde vi hålla våra befintliga verktyg i gott skick medan vi flyttade systemen till Pando ett i taget. När vi flyttade fler arbetsbelastningar till Pando fyllde deras pushar Pandos cache och förbättrade tillförlitligheten i vår tidigare backend.
För att optimera prestandan med Pando började vi distribuera vår offentliga Git-distribution till utvecklarnas datorer och i ChatGPT:s skrivbordsapp. Vi använde ett internt omslag runt Gits kommandoradsgränssnitt för att migrera klienterna till Pando i etapper. Vi skapade många andra verktyg för att hantera ändringsförfrågningar och kodgranskning samt API:er för automatisering. Parallellt med Git-trafiken migrerade vi hundratals integrationer som var beroende av äldre API:er, webhooks och nedladdning av förvar.
Codex hjälpte självt utvecklare och team med övergången. Vi skapade färdigheter för att förbereda befintliga arbetskopior och vägleda agenter i användningen av det nya systemet. Därför kunde utvecklare helt enkelt be Codex att migrera deras utvecklingsmiljöer och tjänster.
Övergången till Pando gjorde sammanslagningar mer tillförlitliga och minskade medianlatensen för pushar med nästan 80 %.
Pando kan leverera en petabyte Git-data per dag med bara en handfull CPU-kärnor. Pando har levererat mer än 57 terabyte Git-paket under en enda timme, med över 99 % av dessa byte från lokal diskcache.
Genom att återanvända paket kan många klienter ladda ned samma förvarshistorik med få beräkningar på de levererande noderna. Detta exempel visar ett enda förvar, men Pando är utformat för att stödja många fler. När efterfrågan växer kan vi skala dataleverans och Git-förhandling oberoende av varandra.
Genom att bygga Pando på Git-protokollet och Git-klienten kunde vi hitta buggar och möjligheter att förbättra Gits egen prestanda.
Pando levererar till exempel återanvändbara paket direkt till klienterna. Därför blev nedladdning och indexering av dessa paket centralt för att göra systemet snabbt och tillförlitligt. Det arbetet synliggjorde kantfall vid samtidiga nedladdningar, rensning efter avbrutna överföringar och återupptagning av nedladdningar som redan hade slutförts. Vi rättade dessa kodvägar, lade till valideringar när ett befintligt paket accepteras för en hämtning och bidrog med arbetet till huvudprojektet.
Vi behövde också se vad tiden och bandbredden gick åt till. Därför lade vi till spårning av packfile-uri-nedladdningar och byteräkning för paketgenerering. Det gjorde det lättare att avgöra om en långsam hämtning ägnade tiden åt att skapa paket eller överföra dem.
I OpenAI:s Git-version(öppnas i ett nytt fönster) kan klienten ladda ned och indexera oberoende paket parallellt. Vi lade till begreppet ”explicita have-poster”, så att en server kan annonsera en grens aktuella spets separat från den historik som en pushande klient får utelämna ur sitt paket. I Pando gör det att klienter kan använda --force-with-lease för att kontrollera att en funktionsgren inte har flyttats och bygga sitt paket enbart mot huvudgrenen. Alla som har den huvudgrenens historik kan sedan hämta grenen med samma paket.
Flera av dessa förbättringar finns redan i Gits huvudprojekt, och många andra finns i vårt offentliga förvar. Vi fortsätter att arbeta med Git-communityn för att göra förbättringarna användbara även utanför Pando. Du kan följa arbetet i vår offentliga patchöversikt(öppnas i ett nytt fönster).
Genom att ompröva vedertagna metoder för att skala Git-förvar gör Pando det möjligt att skala vårt monorepo utan att ändra de Git-arbetsflöden som våra utvecklare och agenter redan använder. I vårt monorepo har Pando möjliggjort mer parallellt arbete, gjort sammanslagningar mer tillförlitliga och minskat tiden det tar att publicera kodändringar. Utöver vårt monorepo utforskar vi andra användningsområden för Pando inom OpenAI, bland annat arbetsbelastningar där många agenter behöver samarbeta i ett gemensamt Git-förvar.
När utvecklare och agenter producerar mer programvara kommer fler team utanför OpenAI att stöta på de begränsningar vi mötte i versionshanteringen. Vi undersöker nu hur vi kan dela tekniken bakom Pando med våra användare.
Pando bygger på två decenniers erfarenhet från dem som använder och driver Git-förvar. Vi fortsätter att utveckla Pando och samarbeta med Gits open source-community för att dela förbättringar som gynnar Git-användare i stort.


