OpenAI

2 noiembrie 2026

Inginerie

Scalarea Git la OpenAI

Construim un sistem de control al versiunilor adaptat volumului de lucru al agenților, investind totodată în ecosistemul Git.

De Aaron Friel, Taylor Blau și Ted Nyman, membri ai echipei tehnice

Se încarcă…

În ultimul an, dimensiunea monodepozitului nostru și cerințele impuse acestuia au crescut într-un ritm uluitor. Era clar că trebuia să ne regândim sistemul de control al versiunilor codului sursă pentru a susține volumul de lucru simultan pe care îl face posibil Codex.

Am construit Pando, un nou backend pentru depozitele Git, fără a întrerupe activitatea de inginerie sau a schimba modul în care echipa noastră folosește Git. Pando susține acum întreaga activitate de dezvoltare din monodepozitul OpenAI. Pando este conceput pentru două obiective ambițioase: în primul rând, scrieri scalabile orizontal, optimizate pentru operații push simultane pe multe ramuri. În al doilea rând, maximizarea randamentului de citire, astfel încât clienții să poată descărca date suficient de rapid pentru a utiliza integral lățimea de bandă disponibilă.

Am atins aceste obiective păstrând compatibilitatea cu protocolul și clientul Git, astfel încât atât inginerii, cât și agenții noștri să poată integra fără dificultăți Pando în fluxurile de lucru existente. În producție, Pando a redus de aproape 5 ori latența mediană a operațiilor push. Acum livrează aproape un petabyte de date Git pe zi.

În acest articol, vom prezenta motivele pentru care am construit un nou sistem de control al versiunilor codului sursă și am ales să păstrăm compatibilitatea cu Git. Vom compara sistemul nostru cu abordările uzuale de scalare a Git și vom explica de ce Pando poate răspunde nevoilor de scalabilitate ale OpenAI într-un mod unic. Vom discuta și despre pașii următori și despre cum vom continua să contribuim la Git pentru a susține volumul de lucru generat de agenți.

De ce aveam nevoie de un nou sistem de control al versiunilor codului sursă

Codex a schimbat amploarea și ritmul dezvoltării la OpenAI, permițând inginerilor să lucreze mult mai mult în paralel. Asta înseamnă mai multe ramuri și operații push, mai multe îmbinări și mai multe preluări și clonări din mediile de dezvoltare, sarcinile de cercetare și integrarea continuă (CI).

Aceste cerințe se amplifică reciproc. Un singur inginer are acum mai multe modificări pregătite simultan pentru CI, iar fiecare push poate declanșa mai multe sarcini care preiau același commit.

Spre sfârșitul anului trecut, sistemele care susțineau monodepozitul nostru au început să nu mai facă față. Ne confruntam cu preluări și operații push lente, precum și cu îmbinări eșuate, fiabilitatea coborând uneori sub 90%. Ajunseserăm la o limită a Git în sine sau a modului în care puneam monodepozitul la dispoziția clienților? Git s-a născut într-o comunitate care făcea schimb de patch-uri pentru nucleul sistemului de operare prin e-mail. De peste două decenii, dezvoltatorii și-au adaptat instrumentele în jurul Git, pe măsură ce modul de creare a software-ului s-a schimbat. Când am început să întâmpinăm dificultăți în propriul monodepozit, era important pentru noi să putem construi în continuare pe baza acestei munci.

Am găsit o cale de a păstra fluxul de lucru familiar, reconstruind backendul pentru a procesa în paralel operații push pe ramuri diferite și a furniza simultan același istoric multor clienți. Folosirea Git ca bază a fluxului nostru de control al versiunilor însemna că îmbunătățirile aduse clientului puteau fi utile și în afara OpenAI. Voiam să contribuim la proiectul principal cu îmbunătățirile din copia noastră de Git⁠(se deschide într-o fereastră nouă), punându-le la dispoziția întregii comunități.

Proiectarea Git pentru utilizare la scară mare

Scalarea unui sistem de control al versiunilor are două dimensiuni esențiale: mulți clienți care fac push pe ramuri diferite și mulți clienți care preiau același istoric. Pando le optimizează pe ambele.

În primul rând, Pando coordonează independent actualizările fiecărei ramuri. Actualizările ramurilor independente pot avea loc în paralel, iar un client care preia o ramură primește istoricul necesar pentru acea revizie. Compromisul este că un client care citește mai multe ramuri le poate vedea în stări corespunzătoare unor momente diferite.

În al doilea rând, Pando reutilizează fișierele pack (formatul Git pentru comprimarea mai multor obiecte împreună) între preluări, în loc să genereze un fișier pack separat pentru fiecare client. Un client poate descărca unele obiecte pe care le are deja, dar serverul poate furniza aceiași octeți multor clienți cu un efort de calcul redus.

Aceste alegeri ne permit să scalăm scrierile pe mai multe ramuri și să deservim citirile din cache. Pentru a vedea cum se îmbină aceste elemente, să începem cu modul în care Git stochează și transferă istoricul.

Noțiuni de bază despre stocarea obiectelor Git

Git trebuie să poată menține sincronizate două componente ale unui depozit: obiectele și referințele.

Un depozit Git înregistrează instantanee ale fișierelor și istoricul care le leagă sub forma unui graf de obiecte. Un obiect poate fi un blob, un arbore, un commit sau o etichetă. Bloburile conțin datele unui singur fișier, iar arborii asociază nume cu bloburi sau cu alți arbori pentru a forma o structură de directoare. Commiturile indică un arbore rădăcină și zero sau mai multe commituri părinte, formând istoricul depozitului.

Obiectele sunt identificate printr-un hash al tipului și conținutului lor. Obiectele sunt și imuabile: înregistrarea unui fișier modificat creează un blob nou, în timp ce fișierele și directoarele nemodificate pot reutiliza obiectele existente.

Ramurile și etichetele sunt referințe, sau „refs”, care indică commituri. Înregistrarea modificărilor pe o ramură creează un commit nou și actualizează referința ramurii pentru a indica acel commit.

Ramura refs/heads/main indică commitul C2, al cărui părinte este C1. C1 indică arborele rădăcină T1, în care parser.py face referire la blobul B1. C2 indică arborele rădăcină T2, în care parser.py face referire la blobul B2. În ambii arbori rădăcină, docs/ face referire la același arbore, T3. Intrarea README.md a acestuia indică blobul B3, iar usage.md indică blobul B4.

Obiectele Git fac referire unele la altele prin ID: commiturile indică arbori, iar arborii indică alți arbori și bloburi. Ramura refs/heads/main indică commitul C2, al cărui părinte este C1. C1 indică arborele rădăcină T1, în care parser.py face referire la blobul B1. C2 indică arborele rădăcină T2, în care parser.py face referire la blobul B2. În ambii arbori rădăcină, docs/ face referire la același arbore, T3. Intrarea README.md a acestuia indică blobul B3, iar usage.md indică blobul B4.

Git stochează obiectele fie „individual”, fie „împachetate”. Obiectele stocate individual se află în fișiere separate, identificate prin ID-ul obiectului. Obiectele împachetate sunt stocate împreună într-un fișier pack, ceea ce permite stocarea obiectelor similare sub formă de diferențe față de alte obiecte.

Două versiuni ale parser.py apar în $GIT_DIR/objects . Ca obiecte stocate individual, blobul A conține parse(), iar blobul B conține atât parse(), cât și parse_file(). Fișierul pack stochează integral A și reprezintă B prin instrucțiuni de copiere a întregului A și de adăugare a parse_file(). Fișierul *.idx asociat localizează ambele bloburi în fișierul pack după ID-ul obiectului.

Git poate stoca bloburile ca fișiere separate sau le poate împacheta împreună, codificând un blob ca diferență față de altul.

În Git, un obiect este accesibil dintr-un commit dacă Git îl poate găsi urmând legăturile de la acel commit prin părinții, arborii și bloburile sale. În timpul unei preluări, clientul îi spune serverului ce commituri dorește și pe care le are deja. Serverul găsește obiectele accesibile din commiturile solicitate, le exclude pe cele despre care știe că sunt deja la client și trimite restul într-un fișier pack, în același format pe care Git îl folosește pe disc. O clonare completă pornește fără niciun obiect, deci are nevoie de toate obiectele accesibile din istoricul clonat.

O operațiune push transferă obiecte în sens invers, cerându-i serverului să actualizeze una sau mai multe referințe. Clientul folosește setul de referințe anunțate de server pentru a evita trimiterea obiectelor pe care serverul le are deja. Prin urmare, pachetul trimis poate depinde de obiecte existente: primirea pachetului singur nu este suficientă pentru a reconstrui noul istoric.

Replicarea unui depozit Git înseamnă menținerea disponibilității atât a obiectelor, cât și a referințelor pe mai multe servere. Înainte ca o replică să poată furniza noua valoare a unei referințe, trebuie să poată furniza obiectele accesibile din acea valoare. Serviciul trebuie să coordoneze și actualizările concurente ale referințelor, adică operațiunile push care încearcă să mute aceeași ramură de la același vârf vechi la commituri noi diferite. Sistemele care impun o singură ordine pentru toate scrierile într-un depozit pot face ca operațiunile push pe ramuri independente să aștepte una după alta.

Modele existente de servicii Git distribuite

În proiectarea Pando, am evaluat abordări convenționale pentru scalarea controlului versiunilor cu Git: una care coordonează actualizările referințelor între replicile depozitului prin confirmare în trei faze (3PC, un protocol pentru coordonarea confirmării pe mai multe noduri) și una care ordonează actualizările printr-un jurnal comun de scriere anticipată (WAL, o evidență a modificărilor înregistrate înainte de a fi aplicate), aflat în sistemul de stocare a bloburilor.

Confirmare în trei faze (3PC)

În arhitectura cu confirmare în trei faze pe care am modelat-o, serviciul Git păstrează o copie completă a depozitului pe mai multe replici. Când un server primește un push, fișierul pack primit este copiat și trimis în paralel către toate replicile. Pentru a publica modificările trimise prin push, majoritatea replicilor trebuie să accepte actualizarea referinței. Serverul care gestionează acest proces este coordonatorul, iar procesul necesită mai multe runde de comunicare între coordonator și fiecare dintre celelalte replici.

Pentru a accepta actualizarea unei referințe, fiecare replică își blochează copia referinței și verifică dacă aceasta indică în continuare commitul așteptat de client. Coordonatorul trimite apoi un mesaj de preconfirmare replicilor care acceptă actualizarea. După ce majoritatea confirmă primirea mesajului de preconfirmare, coordonatorul le cere să efectueze actualizarea. Orice replică actualizată poate apoi să anunțe noul vârf al ramurii din copia sa locală a depozitului.

Păstrarea unor replici complete permite deservirea locală a citirilor și garantează că clienții obțin o imagine consistentă a tuturor referințelor, indiferent de replica aleasă. Prin urmare, referințele sunt actualizate pe rând, iar fiecare push necesită mai multe schimburi dus-întors pentru a coordona actualizarea referinței între replici.

Așteptarea formării unui cvorum face ca performanța discurilor replicilor și latența comunicării să influențeze direct durata operației push. Pe măsură ce volumul scrierilor crește, replicile trebuie să țină pasul atât cu pachetele primite, cât și cu traficul de citire.

Jurnal de scriere anticipată (WAL)

În arhitectura bazată pe WAL pe care am modelat-o, serviciul Git folosește un manifest comun în sistemul de stocare a bloburilor pentru a ordona actualizările din întregul depozit. Când un server primește un push, încarcă fișierul pack primit într-un sistem de stocare a bloburilor și pregătește actualizarea referinței în copia sa locală a depozitului. Pentru a publica modificările, adaugă în manifestul comun din sistemul de stocare a bloburilor o intrare care înregistrează actualizarea referinței și indică fișierul pack. Serverul Git actualizează apoi manifestul printr-o operație de comparare și înlocuire. Aceasta fie înlocuiește atomic manifestul, fie respinge modificarea. Dacă un alt client l-a actualizat între timp, serverul citește intrările adăugate, verifică dacă actualizarea propriei referințe este încă validă și reîncearcă folosind fișierul pack deja încărcat.

Clienții care scriu pot încărca fișierele pack în paralel, dar chiar și operațiunile push pe ramuri independente necesită actualizarea aceluiași manifest. Timpul dus-întors al unei operații reușite de comparare și înlocuire limitează frecvența cu care depozitul poate publica un manifest nou. Un sistem de stocare a bloburilor cu latență mică face ca acest pas să fie rapid. Procesarea în loturi permite unui manifest să actualizeze mai multe referințe simultan, cu prețul punerii actualizărilor în așteptare și al creșterii latenței.

Sistemul de stocare a bloburilor poate primi fișiere pack în paralel, dar actualizările manifestului comun continuă să concureze între ele. Simpla creștere a capacității sistemului de stocare a bloburilor nu elimină acest blocaj. Fiecare replică trebuie în continuare să citească și să indexeze pachetele din sistemul de stocare a bloburilor înainte de a le putea furniza local. Dacă scrierile sosesc mai repede decât pot fi procesate, replicile rămân în urmă, iar clienții așteaptă mai mult pentru a prelua commiturile nou trimise prin push.

Proiectarea arhitecturii Pando

Așa ajungem la Pando. Pando este o nouă arhitectură pentru furnizarea depozitelor Git, concepută să scaleze pentru un număr mare de clienți care fac push simultan. Iată cum funcționează:

Regândirea constrângerilor

Înainte de a dezvolta Pando, intuiam că ne putem îndeplini cerințele fără compromisuri în privința Git, dar trebuia să renunțăm la unele lucruri pe care majoritatea sistemelor Git le consideră cerințe obligatorii:

  • Consecvență la nivelul întregului depozit: O imagine consecventă asupra tuturor referințelor la un anumit moment necesită coordonare la nivelul întregului depozit. Într-un sistem care ordonează global toate actualizările referințelor, operațiunile push pe ramuri independente concurează pentru un singur punct de coordonare.

  • Fișiere pack distincte pentru fiecare client: Dacă doi clienți cu istorice locale diferite preiau aceeași ramură, serverul poate fi nevoit să genereze câte un pachet diferit pentru fiecare, pentru a evita trimiterea obiectelor pe care le au deja. Generarea acestor răspunsuri consumă resurse CPU și operațiuni de intrare/ieșire pe disc, ceea ce îngreunează scalarea la un număr mare de clienți care citesc date.

În schimb, Pando oferă un model relaxat de consecvență (consecvență cauzală) pentru a scala scrierile și reutilizează fișierele pack pentru a scala citirile.

Consecvență cauzală (CC)

Când un client face push pe o ramură, Pando stochează fișierul pack primit ca blob imuabil într-un sistem de stocare a bloburilor. De fiecare dată când ramura avansează, Pando înregistrează într-un sistem de stocare a metadatelor o versiune a referinței care conține noul vârf al ramurii, pachetul care l-a furnizat și toate versiunile anterioare ale referințelor de care depinde pachetul. Atât sistemul de stocare a bloburilor, cât și cel de stocare a metadatelor se pot scala orizontal.

Preluarea unui commit sau a unei referințe din Pando necesită retransmiterea către client a unui set de versiuni ale referințelor (fișiere pack), reconstruind starea clientului care a creat versiunea preluată. Astfel se asigură o formă mai slabă de consistență la nivelul întregului depozit, dar se păstrează serializabilitatea actualizărilor fiecărei referințe. Conflictele între clienți apar doar când aceștia fac push simultan către aceeași referință. Un client care citește mai multe ramuri nu poate presupune că acele citiri reflectă același moment la nivelul întregului depozit.

În stânga, trei pachete succesive ale ramurii principale conțin commiturile A, B și C; D și E; respectiv F. În dreapta, ramura principală și cea tematică se separă la C. Ramura principală avansează la G, iar ramura tematică avansează prin D, E și F. Cel mai recent pachet al ramurii tematice conține D, E și F. Un contur întrerupt indică faptul că D și E sunt prezente și în pachetul anterior al ramurii tematice. Săgeata albastră indică dependența noului pachet de ramura principală la G. Părintele lui F în Git este E.

Un push pe ramura principală adaugă un pachet incremental, care conține unul sau mai multe commituri. Fiecare push pe o ramură secundară include din nou commiturile anterioare ale acesteia, astfel încât un client care are versiunea anunțată a ramurii principale să poată prelua ramura secundară într-un singur pachet.

Aceasta este o formă de consecvență cauzală. Pentru a prelua o referință, clientul care citește vede istoricul clientului care a creat-o. În timpul unui push, Pando controlează istoricul comun asupra căruia clientul și serverul se pun de acord, asigurându-se că clienții produc fișiere pack mai ușor de partajat.

Prin urmare, Pando nu oferă o stare a tuturor referințelor corespunzătoare unui singur moment în timpul operațiunilor Git și nici o ordine a actualizărilor referințelor în cadrul unei clonări sau preluări. Concret, să presupunem că ramurile foo și bar sunt ambele actualizate în timpul negocierii unei clonări complete. În timpul negocierii, un client poate vedea foo la versiunea 1 și bar la versiunea 2, iar alt client poate vedea foo la versiunea 2 și bar la versiunea 1. În practică, utilizatorii și agenții nu depind de ordinea globală a actualizărilor referințelor, ci mai degrabă de ordinea actualizărilor unei singure referințe.

Compararea abordărilor

În această simulare a celor trei modele de consistență, configurăm 1.000 de clienți astfel încât fiecare să aleagă o ramură distinctă și apoi să trimită repetat commituri unice prin push, măsurând timpul total scurs și latența dintre operațiunile push și fetch. Aceasta din urmă este un indicator important pentru sarcinile de lucru, deoarece măsoară timpul unui ciclu complet în care utilizatorii și agenții colaborează sau folosesc alte sisteme bazate pe controlul versiunilor. Simularea modelează infrastructuri de la mașini virtuale și sisteme de stocare a bloburilor standard până la variante premium. În această simulare, am relaxat consecvența la nivelul întregului depozit pentru protocolul de confirmare în trei faze.

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.

Rulează această simulare care compară Pando cu alte două abordări. Alege infrastructura, de la hardware standard la premium, numărul de clienți și alte setări pentru a vedea cum se compară.

Sistemul modelat cu confirmare în trei faze prezintă o latență de scriere semnificativ mai mare sub sarcină, pe măsură ce discurile ajung la saturație. În schimb, sistemele cu jurnal de scriere anticipată prezintă o latență de citire semnificativ mai mare din același motiv, deoarece serverele nu reușesc să țină pasul cu citirea jurnalului. Atât în testele reale pe monodepozitul nostru, cât și în această simulare, Pando permite multor mii de clienți să facă push simultan, fără a afecta performanța sub sarcină.

Când procesează un push, Pando validează fișierul pack primit în timp ce îl transmite în flux către sistemul de stocare a bloburilor, fără a-l despacheta într-un depozit local. Când un client începe o preluare, Pando selectează pachetele stocate de care acesta are nevoie, iar clientul le descarcă. Niciun server nu trebuie să aplice fișiere pack într-un depozit local sau să efectueze operațiuni costisitoare de despachetare și împachetare. Deoarece se ocupă în principal de negociere și de selectarea fișierelor pack, serverele Pando pot rula pe hardware standard.

Astfel, Pando poate scala până la limitele clienților, ale rețelei dintre clienți și Pando și ale serviciului de stocare a bloburilor. Având suficientă capacitate, clienții care lucrează pe ramuri independente pot transfera fiecare pachete la viteza maximă permisă de propria conexiune. Pentru a deservi mai mulți clienți la această viteză, putem adăuga servere Pando și scala în paralel sistemele de stocare a metadatelor și a bloburilor.

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

Un agent editează fișiere, creează un commit local și rulează git push, care creează un fișier pack cu acele modificări pentru a actualiza o ramură. Pando primește pachetul și îl transmite în flux către sistemul de stocare a bloburilor. După ce validează operațiunea push primită printr-o decodare într-o singură trecere, Pando folosește o tranzacție cu mai multe chei pentru a înregistra o versiune a referinței în sistemul nostru de stocare a metadatelor și a actualiza atomic pointerul referinței. Astfel, Pando poate asigura consistența de tip „citirea propriilor scrieri”. Întreaga operație de scriere necesită O(1)O(1) citiri și scrieri de metadate.

Când un alt client preia ramura, Pando selectează un set de pachete stocate pe baza istoricului pe care clientul îl are deja. Această operațiune necesită O(log⁡Δ)O(\log \Delta) citiri de metadate, unde Δ\Delta este cea mai scurtă distanță, în versiuni ale referințelor, între ceea ce are clientul și ceea ce solicită. Serverul returnează URI-uri pentru fișierele pack, iar clientul le descarcă printr-un serviciu, Express, care stochează fișierele pack în cache-uri regionale.

Această soluție se bazează pe cele trei servicii centrale ale arhitecturii Pando: push, fetch și compactare.

Scalarea operațiunilor push

Când un client face push, un server Git convențional se comportă ca un jucător de poker care își arată toate cărțile: îi dezvăluie clientului fiecare ramură pe care o are. Clientul adună apoi commiturile, arborii și bloburile pentru a crea un fișier pack care exclude obiectele despre care știe că există deja pe server.

Anunțarea mai multor referințe poate reduce volumul transferat prin push. Dar poate și îngreuna reutilizarea pachetului, deoarece altui client îi pot lipsi obiectele omise de clientul care a scris datele.

Pando adoptă o abordare diferită. În loc să-și arate toate cărțile, îi comunică clientului doar un set selectat de ramuri. Face acest lucru din două motive:

  1. Oferirea unei imagini consecvente asupra tuturor referințelor este costisitoare. Având milioane de ramuri, doar comunicarea referințelor poate depăși 100 MiB. Pentru aceasta, fiecare server trebuie fie să mențină o mapare actualizată, cu un cost semnificativ de coordonare, fie să interogheze baza de date, costul interogării crescând odată cu numărul de referințe.

  2. Dacă primește doar un subset de referințe, clientul va genera un pachet care depinde exclusiv de ceea ce serverul îi permite explicit să excludă. Urmărirea dependențelor permite ca Pando să reutilizeze fișierul pack generat de un client la un push, furnizându-l ca răspuns la un fetch ulterior.

Cu cât un server Git anunță mai multe referințe, cu atât clienții care fac push au mai multe surse din care să excludă obiecte, putând produce pachete mai mici. Totuși, pentru a reutiliza aceste fișiere pack, serverul trebuie să poată demonstra că fiecare client care le preia deține toate dependențele. Altfel, fișierul pack poate omite obiecte de care clientul are nevoie.

Pe de altă parte, dacă un server Git nu anunță nicio referință, clientul ar genera un fișier pack care conține întregul istoric al depozitului. Acel fișier pack poate fi folosit de orice alt client, dar cu un cost semnificativ: preluarea oricărei ramuri devine, practic, o clonare completă.

Curbă care arată cum influențează referințele anunțate dimensiunea fișierului pack: de la întregul istoric, când nu se anunță nicio referință, trecând prin anunțarea ramurii principale, până la doar obiectele noi, când se anunță toate referințele.

Anunțarea mai multor referințe duce la fișiere pack mai mici. Cea mai mare parte a reducerii dimensiunii poate fi obținută prin anunțarea unei singure referințe.

În mod implicit, când un client face push pe o ramură nouă de funcționalitate, Pando anunță ca bază doar ramura desemnată drept principală. Astfel, dimensiunea anunțului rămâne constantă pe măsură ce depozitul acumulează ramuri, dar există și compromisuri. Când un inginer face push de multe ori pe aceeași ramură sau creează o ramură de funcționalitate pornind de la alta, clientul trebuie să retrimită tot ce nu se află pe ramura principală.

Prin urmare, operațiunile push succesive pe ramură transferă mai multe date, pot conține obiecte redundante și consumă mai multă lățime de bandă a clientului. În monodepozitul nostru, dimensiunea mediană a fișierelor pack produse astfel a fost sub 20 KiB, iar percentila 99 a fost sub 3 MiB. Pentru noi, acesta a fost un compromis rezonabil, deoarece un client care deține deja istoricul anunțat al ramurii principale poate prelua ramura cu acel singur fișier pack.

După ce alege setul de referințe de anunțat clientului, Pando acceptă operațiunea push și își actualizează sistemul de stocare a metadatelor. Pando împarte actualizarea metadatelor într-o parte sincronă și una asincronă. Actualizarea sincronă a metadatelor asigură faptul că clienții pot prelua imediat commitul nou actualizat. Aceasta scrie o nouă versiune a referinței și actualizează maparea pointerului către noua versiune, stocând noul vârf, dependențele folosite la negocierea fișierului pack și un link către fișierul pack trimis prin push.

Totodată, în aceeași tranzacție sunt înregistrate activitățile asincrone, cum ar fi programarea compactării. Pentru operațiunile push foarte mari, doar un subset de commituri este scris sincron. Astfel, este necesar doar un număr constant de citiri și scrieri în sistemul de stocare a metadatelor, indiferent de dimensiunea operației push.

Un aspect important este că serverele Pando pot accepta independent operațiuni push către referințe fără legătură între ele. Singura cheie pentru care există concurență este pointerul referinței, pe care îl protejăm printr-o operație atomică de tip comparare și înlocuire. Putem crește randamentul de scriere adăugând servere Pando, unități de scalare pentru stocarea metadatelor sau sisteme de stocare a bloburilor.

Preluări și clonări la viteza maximă a rețelei

Când un client efectuează o preluare, furnizează serverului două seturi de ID-uri de commituri, cunoscute în protocol drept have și want. Un server Git convențional calculează diferența dintre mulțimile obiectelor accesibile din commiturile deținute și din cele solicitate, comprimă obiectele necesare și trimite rezultatul clientului. Deși serverul Git open-source are local o varietate de indexuri pentru a accelera această operațiune, generarea acestor fișiere pack poate fi costisitoare, iar reutilizarea lor prin cache este, în general, dificilă.

În schimb, Pando folosește mapările de commituri înregistrate la operațiunile push pentru a căuta elementele have și want ale clientului și a le asocia cu un set de versiuni ale referințelor care conțin acele commituri. Fiecare dintre aceste versiuni ale referințelor indică un fișier pack primit anterior de Pando. Serverul folosește aceste informații pentru a determina ce îi trebuie clientului, selectând fișierele pack care acoperă setul de obiecte necesare.

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 asociază obiectele deținute și cele solicitate din protocolul Git cu o secvență de versiuni ale referințelor. Solicitarea unei ramuri secundare presupune și solicitarea istoricului ramurii principale până la dependențele acelei ramuri.

Protocolul Git permite trimiterea unui singur fișier pack în răspunsul HTTP la o clonare sau preluare. Totuși, o funcționalitate subapreciată a protocolului Git, fișierele pack, îi permite serverului să returneze linkuri pentru descărcarea separată a unor pachete suplimentare. Pando folosește exclusiv această funcționalitate și transmite un fișier pack gol direct în răspuns. Pentru a susține această funcționalitate, OpenAI a contribuit la proiectul principal cu mai multe îmbunătățiri pentru fișierele pack.

Sistemele OpenAI funcționează în multe regiuni geografice, așa că menținem clustere Pando cu cache-uri locale în fiecare dintre ele. Un serviciu de livrare a pachetelor, Express, păstrează în cache date din sistemul de stocare a bloburilor, iar un serviciu separat face același lucru pentru metadate. Aceste cache-uri permit c Pando să răspundă cererilor pentru un ID de commit sau unei citiri speciale a unei referințe folosind exclusiv starea păstrată în cache-ul acelui cluster. Folosim un hashing consistent pentru a direcționa cererile pentru un fișier pack către serverele Express, cu rate de regăsire în cache de peste 99%.

Acest sistem de cache-uri oferă un randament extrem de ridicat, capabil să utilizeze integral capacitatea rețelei dintre clienți și Pando. Folosind aceste URI-uri de fișiere pack și cache-uri locale, am confirmat că operațiunile de clonare și preluare pot satura conexiuni de rețea de 10 Gbps, chiar și cu mașini virtuale standard de la furnizorii cloud.

Compactarea și timpul până la primul octet

Pentru majoritatea serverelor Git, reîmpachetarea și colectarea obiectelor neutilizate sunt necesare pentru a menține performanța unui depozit în creștere. Ambele etape recuperează spațiu, dar solicită intens resursele de calcul și operațiunile de intrare/ieșire.

Într-un depozit care se schimbă rapid, precum monodepozitul OpenAI, nu este fezabil să trimitem câte un URI de fișier pack pentru fiecare push efectuat de la ultima preluare. În fiecare zi, un client ar trebui să descarce zeci de mii de fișiere pack pentru a-și aduce la zi ramura principală.

În schimb, Pando combină pachetele în fundal pentru a reduce acest cost suplimentar, adoptând o soluție similară compactării din arborii de îmbinare structurați pe jurnal (LSM). Fiecare push produce ceea ce Pando numește un fișier pack „L0”, cea mai mică unitate de date pe care Pando o poate transmite. În timp, procesele de lucru din fundal reunesc aceste fișiere pack în grupuri care acoperă o secvență de versiuni ale referințelor, dimensiunea lor crescând geometric de la L0. În prezent, monodepozitul nostru folosește grupuri de fișiere pack până la nivelul L4.

Aceste grupuri au un rol dublu. După ce un grup este suficient de plin, un proces de compactare îl preia și produce un singur fișier pack. Astfel, se îmbunătățește raportul de compresie prin reorganizarea obiectelor din fișierul pack. Dar se îmbunătățește și performanța negocierii preluărilor, deoarece grupurile funcționează ca o listă cu salturi între versiunile referințelor. Parcurgând simultan indexurile de la fiecare nivel, Pando poate selecta în timp logaritmic un set de pachete de trimis unui client. Aceste grupuri de fișiere pack, reîmpachetate sau nu, oferă o soluție mai eficientă pentru planificarea preluărilor, reducând atât cerințele de randament, cât și pe cele de latență pentru Pando.

Compaction in Pando

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

Compaction in progressPublished cover

Compactarea combină în fundal fișiere pack din multe versiuni ale referințelor, permițând preluări mai eficiente, cu mai puține fișiere.

Spre deosebire de multe servere Git, fișierele pack sunt păstrate la fiecare nivel, fără a fi eliminate sau șterse prin colectarea obiectelor neutilizate. Pachetele de la fiecare nivel sunt folosite continuu pentru a deservi clienții, pe baza elementelor have și want specificate la preluare. Iar pentru clienții care folosesc Pando ca API, citirea unui obiect arbitrar dintr-un fișier pack tinde să necesite mai puține date citite dintr-un L0 decât dintr-un L4, deoarece este mai puțin probabil ca obiectul să fie comprimat.

Compactarea în Pando reduce semnificativ volumul total de date transferate clienților și permite ca efortul de planificare a pachetelor să crească subliniar în raport cu cât de mult a rămas clientul în urmă. Acest lucru este deosebit de important pentru depozitele mari care se schimbă rapid și despre care ne așteptăm să devină noua normalitate în dezvoltarea software.

Construirea Pando

Am construit Pando cu Codex, trecând de la lansarea proiectului la producție în doar 10 săptămâni. Nucleul Pando a fost proiectat de un singur inginer, implementat de echipe de agenți și pus în producție de câțiva ingineri. Pando a început să deservească traficul de producție în aprilie anul acesta și face parte dintr-un sistem mai amplu de instrumente de gestionare a versiunilor codului sursă folosit la OpenAI.

Am folosit mare varietate de abordări pentru a valida proiectarea Pando. Am aplicat principiile noastre pentru construirea unor baze software mai sigure⁠. Fiind un proiect pornit de la zero, am ales să implementăm Pando în Rust, fără dependențe moștenite. Am scris o suită de teste de conformitate, astfel încât agenții să-și poată verifica modificările în raport cu comportamentul standard al Git. Am dezvoltat și cadre de testare prin simulări deterministe, care ne permit să verificăm invariantele și proprietățile sistemului și să identificăm și atenuăm condiții de cursă subtile și regresii în negociere și în planificarea pachetelor.

La acest sistem au contribuit multe modele au contribui. Pe măsură ce au devenit disponibile modele noi, le-am folosit pentru a îmbunătăți performanța și fiabilitatea Pando. Folosind GPT‑6 Astra, agenții au scris cod Lean și TLA+ pentru a formaliza o „teorie a lui Pando”, pe care am folosit-o pentru a găsi și remedia erori de implementare și a demonstra proprietăți ale sistemului. Mai multe sesiuni au rulat neîntrerupt sute de ore, identificând și remediind cazuri-limită în Pando. Rulăm, de asemenea, agenți non-stop pentru a testa adversarial Git și Pando, folosind modele cu capabilități Daybreak.

Punerea în producție a Pando

Inițial, am implementat Pando ca proxy care transmitea citirile și scrierile către sistemul nostru existent de control al versiunilor. Astfel, am putut menține Pando sincronizat cu sistemul anterior și muta traficul în siguranță pe Pando, fără întreruperi.

Ca parte a mecanismului de cache, Pando poate și să elimine cererile duplicate și să combine cererile înainte de a le transmite mai departe. Acest lucru ne-a permis să menținem buna funcționare a instrumentelor existente în timp ce mutam sistemele pe Pando, unul câte unul. Pe măsură ce migram mai multe sarcini de lucru pe Pando, operațiunile lor push populau cache-ul Pando și îmbunătățeau fiabilitatea backendului anterior.

Pentru a optimiza performanța la utilizarea Pando, am început să instalăm distribuția noastră publică de Git pe computerele dezvoltatorilor și s-o includem în aplicația desktop ChatGPT. Am migrat clienții pe Pando în etape, folosind un strat intern de intermediere peste interfața Git în linie de comandă. Am creat multe alte instrumente pentru gestionarea cererilor de modificare și revizuirea codului, precum și API-uri pentru automatizări. Odată cu traficul Git, am migrat sute de integrări care depindeau de API-uri vechi, webhookuri și descărcări de depozite.

Codex însuși a ajutat dezvoltatorii și echipele să facă tranziția. Am creat abilități pentru pregătirea copiilor de lucru existente și pentru a ghida agenții în utilizarea noului sistem. Astfel, dezvoltatorii puteau pur și simplu să îi ceară lui Codex să le migreze mediile de dezvoltare și serviciile.

Trecerea la Pando a îmbunătățit fiabilitatea îmbinărilor și a redus latența mediană a operațiunilor push cu aproape 80%.

Pando poate livra un petabyte de date Git pe zi folosind doar câteva nuclee CPU. Pando a livrat peste 57 de terabytes de pachete Git într-o singură oră, peste 99% dintre acești octeți provenind din cache-ul local de pe disc.

Reutilizarea pachetelor le permite multor clienți să descarce același istoric al depozitului, cu un efort de calcul redus pe nodurile care îl livrează. Acest exemplu prezintă un singur depozit, dar Pando este conceput să susțină mult mai multe. Pe măsură ce cererea crește, putem scala independent livrarea datelor și negocierea Git.

Contribuții la proiectul Git

Construirea Pando pe baza protocolului și clientului Git ne-a ajutat să identificăm erori și oportunități de îmbunătățire a performanței Git în sine.

De exemplu, Pando livrează pachete reutilizabile direct clienților, astfel că descărcarea și indexarea acestor pachete au devenit esențiale pentru viteza și fiabilitatea sistemului. Această muncă a scos la iveală cazuri-limită legate de descărcările simultane, curățarea după transferuri întrerupte și reluarea descărcărilor deja finalizate. Am remediat aceste situații, am adăugat validări la acceptarea unui pachet existent pentru o preluare și am contribuit cu aceste modificări la proiectul principal.

Trebuia să vedem și unde se consumau timpul și lățimea de bandă, așa că am adăugat urmărirea descărcărilor fișiere pack și numărarea octeților la generarea pachetelor. Astfel, putem determina mai ușor dacă o preluare lentă își consumă timpul generând pachete sau transferându-le.

În versiunea Git a OpenAI⁠(se deschide într-o fereastră nouă), clientul poate descărca și indexa în paralel pachete independente. Am adăugat conceptul de „obiecte deținute declarate explicit”, astfel încât un server să poată anunța vârful curent al unei ramuri separat de istoricul pe care un client care face push îl poate omite din pachet. În Pando, acest lucru permite clienților să folosească --force-with-lease pentru a verifica dacă o ramură de funcționalitate nu s-a modificat și să construiască pachetul doar în raport cu ramura principală. Oricine deține acel istoric al ramurii principale poate apoi să preia ramura folosind același pachet.

Câteva dintre aceste îmbunătățiri sunt deja în proiectul principal Git, iar multe altele se află în depozitul nostru public. Continuăm să colaborăm cu comunitatea Git pentru ca aceste îmbunătățiri să fie utile și dincolo de Pando. Poți urmări această activitate în sistemul nostru public de urmărire a patch-urilor⁠(se deschide într-o fereastră nouă).

Ce urmează

Regândind ideile consacrate despre scalarea depozitelor Git, Pando ne permite să scalăm monodepozitul fără a schimba fluxurile de lucru Git pe care inginerii și agenții noștri le folosesc deja. În monodepozitul nostru, Pando a permis un grad mai mare de paralelism, a îmbunătățit fiabilitatea îmbinărilor și a redus timpul necesar publicării modificărilor de cod. Dincolo de monodepozit, explorăm și alte utilizări ale Pando în cadrul OpenAI, inclusiv sarcini de lucru în care un număr mare de agenți trebuie să colaboreze într-un depozit Git comun.

Pe măsură ce inginerii și agenții produc mai mult software, tot mai multe echipe din afara OpenAI vor întâmpina limitele de care ne-am lovit noi în controlul versiunilor codului sursă. În prezent, explorăm modalități de a pune la dispoziția utilizatorilor noștri tehnologia care face posibil Pando.

Pando se bazează pe două decenii de experiență a utilizatorilor și administratorilor de depozite Git. Vom continua să dezvoltăm Pando și să colaborăm cu comunitatea open-source Git pentru a împărtăși îmbunătățirile de care să poată beneficia un număr mai mare de utilizatori Git.

Autor

OpenAI