OpenAI

2 de novembre del 2026

Enginyeria

Escalar Git a OpenAI

Crear un control de versions a l'escala dels agents i continuar invertint en l'ecosistema Git.

Per Aaron Friel, Taylor Blau i Ted Nyman, membres de l'equip tècnic

S'està carregant…

Durant l'últim any, la mida del nostre monorepositori i les exigències que suporta han crescut a un ritme vertiginós. Era evident que havíem de renovar el sistema de control de versions per escalar-lo al nivell de treball simultani que fa possible Codex.

Vam crear Pando, un nou backend per a repositoris Git, sense interrompre la feina d'enginyeria ni canviar la manera com el nostre equip utilitza Git. Ara Pando sustenta tot el desenvolupament al monorepositori d'OpenAI. Pando està dissenyat per assolir dos objectius ambiciosos: primer, oferir escriptures escalables horitzontalment, optimitzades per a push simultanis a moltes branques. I segon, maximitzar el rendiment de lectura perquè els clients puguin baixar dades prou de pressa per saturar tota l'amplada de banda de xarxa disponible.

Vam assolir aquests objectius mantenint la compatibilitat amb el protocol i el client Git, cosa que va permetre als nostres enginyers i agents incorporar Pando als seus fluxos de treball sense dificultats. En producció, Pando va reduir la mediana de la latència dels push a gairebé una cinquena part. Ara serveix gairebé un petabyte de dades de Git al dia.

Aquí explicarem què ens va motivar a crear un nou sistema de control de versions i per què vam decidir mantenir la compatibilitat amb Git. Compararem el nostre sistema amb els enfocaments habituals per escalar Git i explicarem què fa que Pando sigui especialment capaç de satisfer les necessitats d'escalabilitat d'OpenAI. També parlarem dels pròxims passos i de com continuarem contribuint a Git per donar suport al treball a l'escala dels agents.

Per què necessitàvem un nou sistema de control de versions

Codex ha canviat l'escala i el ritme del desenvolupament a OpenAI, i permet als enginyers fer molta més feina en paral·lel. Això vol dir més branques i push, més fusions i més fetch i clonacions des dels entorns de desenvolupament, les tasques de recerca i la integració contínua (CI).

Aquestes exigències es multipliquen. Ara un sol enginyer té diversos canvis a punt per a la CI alhora, i cada push pot activar diverses tasques que fan fetch del mateix commit.

Cap a finals de l'any passat, els sistemes que sustentaven el nostre monorepositori van començar a tenir dificultats per seguir el ritme. Patíem fetch i push lents, a més de fusions fallides, amb una fiabilitat que de vegades queia per sota del 90 %. Havíem arribat a un límit de Git mateix o de la manera com servíem el nostre monorepositori? Git va néixer d'una comunitat que intercanviava pedaços per al nucli per correu electrònic. Durant més de dues dècades, els desenvolupadors han adaptat les seves eines al voltant de Git a mesura que canviava la manera de crear programari. Quan vam començar a trobar dificultats al nostre monorepositori, per a nosaltres era important poder continuar aprofitant tota aquella feina.

Vam veure com mantenir aquell flux de treball conegut i, alhora, reconstruir el backend per gestionar push a branques diferents en paral·lel i servir el mateix historial a molts lectors simultàniament. Fer servir Git com a base del nostre flux de control de versions significava que les millores al client podien ser útils més enllà d'OpenAI. Volíem aportar aquestes millores de la nostra còpia de Git⁠(s'obre en una finestra nova) al projecte original i compartir-les amb tota la comunitat.

Enginyeria per a Git a gran escala

Escalar un sistema de control de versions té dues dimensions essencials: molts escriptors que fan push a branques diferents i molts lectors que fan fetch del mateix historial. Pando optimitza totes dues.

En primer lloc, Pando coordina les actualitzacions de cada branca de manera independent. Les actualitzacions de branques independents poden avançar en paral·lel, i un lector que fa fetch d'una branca obté l'historial necessari per a aquella revisió. La contrapartida és que un lector que consulta diverses branques les pot veure en moments diferents.

En segon lloc, Pando reutilitza fitxers de paquets (el format de Git per comprimir diversos objectes junts) entre operacions de fetch, en lloc de generar un fitxer individual per a cada client. Un client pot baixar alguns objectes que ja té, però el servidor pot servir els mateixos bytes a molts clients amb poc càlcul.

Aquestes decisions ens permeten escalar les escriptures entre branques i servir les lectures des de memòries cau. Per veure com encaixen, comencem per com Git emmagatzema i transfereix l'historial.

Introducció a l'emmagatzematge d'objectes de Git

Git ha de poder mantenir sincronitzades dues parts d'un repositori: els objectes i les referències.

Un repositori Git registra instantànies de fitxers i l'historial que les connecta en forma de graf d'objectes. Un objecte pot ser un blob, un arbre, un commit o una etiqueta. Els blobs contenen el contingut d'un fitxer individual, i els arbres associen noms a blobs o a altres arbres per formar una estructura de directoris. Els commits apunten a un arbre arrel i a zero o més commits pare, i formen l'historial del repositori.

Els objectes s'identifiquen amb un hash del seu tipus i contingut. Els objectes també són immutables: registrar un fitxer modificat crea un blob nou, mentre que els fitxers i directoris que no han canviat poden reutilitzar objectes existents.

Les branques i les etiquetes són referències, o "refs", que apunten a commits. Fer un commit en una branca crea un commit nou i actualitza la referència de la branca perquè hi apunti.

La branca refs/heads/main apunta al commit C2, que té C1 com a pare. C1 apunta a l'arbre arrel T1, on parser.py fa referència al blob B1. C2 apunta a l'arbre arrel T2, on parser.py fa referència al blob B2. En tots dos arbres arrel, docs/ fa referència al mateix arbre, T3. La seva entrada README.md apunta al blob B3, i usage.md apunta al blob B4.

Els objectes de Git es referencien entre si mitjançant l'ID: els commits apunten a arbres, i els arbres, a altres arbres i a blobs. La branca refs/heads/main apunta al commit C2, que té C1 com a pare. C1 apunta a l'arbre arrel T1, on parser.py fa referència al blob B1. C2 apunta a l'arbre arrel T2, on parser.py fa referència al blob B2. En tots dos arbres arrel, docs/ fa referència al mateix arbre, T3. La seva entrada README.md apunta al blob B3, i usage.md apunta al blob B4.

Git emmagatzema els objectes en estat "individual" o "empaquetat". Els objectes individuals es desen en fitxers separats identificats pel seu ID d'objecte. Els objectes empaquetats es desen junts en un fitxer de paquets, cosa que permet emmagatzemar objectes similars com a deltes els uns respecte dels altres.

Apareixen dues versions de parser.py a $GIT_DIR/objects. Com a objectes individuals, el blob A conté parse(), i el blob B conté tant parse() com parse_file(). El fitxer de paquets desa A íntegrament i representa B com a instruccions per copiar tot A i afegir-hi parse_file(). El fitxer *.idx associat localitza tots dos blobs dins del fitxer de paquets mitjançant l'ID d'objecte.

Git pot desar els blobs en fitxers separats o empaquetar-los junts, codificant un blob com un delta respecte d'un altre.

A Git, un objecte és accessible des d'un commit si Git el pot trobar seguint els enllaços des d'aquell commit a través dels seus pares, arbres i blobs. Durant un fetch, el client indica al servidor quins commits vol i quins ja té. El servidor troba els objectes accessibles des dels commits sol·licitats, exclou els que sap que el client ja té i envia la resta en un fitxer de paquets amb el mateix format que Git utilitza al disc. Una clonació completa parteix de zero objectes, de manera que necessita tots els objectes accessibles des de l'historial que es clona.

Un push transfereix objectes en sentit contrari i demana al servidor que actualitzi una o més referències. El client utilitza el conjunt de referències anunciades pel servidor per evitar enviar objectes que el servidor ja té. Per tant, el paquet enviat pot dependre d'objectes existents: rebre només el paquet no és suficient per reconstruir el nou historial.

Replicar un repositori Git implica mantenir disponibles tant els objectes com les referències en diversos servidors. Abans que una rèplica pugui servir el nou valor d'una referència, ha de poder proporcionar els objectes accessibles des d'aquell valor. El servei també ha de coordinar les actualitzacions de referències que competeixen entre si, és a dir, els push que intenten moure la mateixa branca des del mateix extrem anterior cap a commits nous diferents. Els sistemes que imposen un ordre únic a totes les escriptures d'un repositori poden fer que els push a branques independents s'hagin d'esperar els uns als altres.

Patrons existents de serveis Git distribuïts

Durant el disseny de Pando, vam avaluar enfocaments convencionals per escalar el control de versions amb Git: un que coordina les actualitzacions de referències entre rèpliques del repositori mitjançant la confirmació en tres fases (3PC, un protocol per coordinar una confirmació entre diversos nodes), i un altre que ordena les actualitzacions mitjançant un registre d'escriptura anticipada compartit (WAL, un registre dels canvis que s'escriu abans d'aplicar-los) en un emmagatzematge de blobs.

Confirmació en tres fases (3PC)

En el disseny de confirmació en tres fases que vam modelitzar, el servei Git manté una còpia completa del repositori en diverses rèpliques. Quan qualsevol servidor rep un push, el fitxer de paquets entrant es copia i s'envia a totes les rèpliques en paral·lel. Per publicar el push, una majoria de rèpliques ha d'acceptar l'actualització de la referència. El servidor que ho gestiona és el coordinador, i el procés requereix diverses rondes de comunicació entre aquest i cadascuna de les altres rèpliques.

Per acceptar una actualització de referència, cada rèplica bloqueja la seva còpia de la referència i comprova que encara apunti al commit que el client espera. A continuació, el coordinador envia un missatge de preconfirmació a les rèpliques que l'accepten. Quan una majoria acusa recepció del missatge de preconfirmació, el coordinador els indica que facin l'actualització. Aleshores, qualsevol rèplica actualitzada pot anunciar el nou extrem de la branca des de la seva còpia local del repositori.

Mantenir rèpliques completes permet servir lectures localment i garanteix que els clients obtinguin una visió consistent de totes les referències, independentment de la rèplica que triïn. En conseqüència, les referències s'actualitzen d'una en una, i cada push requereix diverses comunicacions d'anada i tornada per coordinar l'actualització entre les rèpliques.

L'espera d'un quòrum fa que el rendiment dels discos de les rèpliques i la latència de comunicació afectin directament el push. A mesura que creix el volum d'escriptures, les rèpliques han de seguir el ritme dels paquets entrants i del trànsit de lectura.

Registre d'escriptura anticipada (WAL)

En el disseny basat en WAL que vam modelitzar, el servei Git utilitza un manifest compartit en l'emmagatzematge de blobs per ordenar les actualitzacions de tot el repositori. Quan un servidor rep un push, puja el fitxer de paquets entrant a un magatzem de blobs i prepara l'actualització de la referència a la seva còpia local del repositori. Per publicar el push, afegeix una entrada a un manifest compartit desat al magatzem de blobs, que registra l'actualització de la referència i apunta al fitxer de paquets. A continuació, el servidor Git actualitza el manifest mitjançant una operació de comparar i intercanviar. Aquesta operació substitueix el manifest de manera atòmica o, si no és possible, rebutja el canvi. Si un altre escriptor l'ha actualitzat mentrestant, el servidor llegeix les entrades afegides, comprova que la seva actualització de referència encara sigui vàlida i ho torna a provar amb el fitxer de paquets que ja havia pujat.

Els escriptors poden pujar els seus fitxers de paquets en paral·lel, però fins i tot els push a branques independents requereixen actualitzar el mateix manifest. El temps d'anada i tornada d'una operació reeixida de comparar i intercanviar limita la freqüència amb què el repositori pot publicar un nou manifest. Un magatzem de blobs de baixa latència fa que aquest pas sigui ràpid. L'agrupació per lots permet que un manifest actualitzi diverses referències alhora, a canvi de posar les actualitzacions en cua i augmentar la latència.

El magatzem de blobs pot rebre fitxers de paquets en paral·lel, però les actualitzacions del manifest compartit continuen competint entre si. Augmentar només la capacitat del magatzem de blobs no resol aquest coll d'ampolla. Cada rèplica encara ha de llegir i indexar els paquets del magatzem de blobs abans de poder-los servir localment. Si les escriptures entrants superen el ritme d'aquesta feina, les rèpliques s'endarrereixen i els clients han d'esperar més per fer fetch dels commits acabats d'enviar.

L'arquitectura de Pando

I això ens porta a Pando. Pando és una nova arquitectura per servir repositoris Git, amb l'objectiu d'escalar per atendre molts clients que fan push simultàniament. Així és com funciona:

Repensar les restriccions

Abans de desenvolupar Pando, intuïem que podríem satisfer els nostres requisits sense renunciar a Git, però sí que calia renunciar a algunes de les condicions que la majoria de sistemes Git donen per imprescindibles:

  • Consistència de tot el repositori: Oferir una instantània consistent de totes les referències en un moment determinat requereix coordinar tot el repositori. En un sistema que ordena conjuntament totes les actualitzacions de referències, els push a branques independents competeixen per un únic punt de coordinació.

  • Fitxers de paquets específics per a cada client: Si dos clients amb historials locals diferents fan un fetch de la mateixa branca, el servidor pot haver de generar un paquet diferent per a cadascun per evitar enviar-los objectes que ja tenen. Generar aquestes respostes consumeix CPU i E/S de disc, cosa que dificulta escalar el sistema per atendre un gran nombre de lectors.

En canvi, Pando ofereix un model de consistència menys estricte (consistència causal) per escalar les escriptures, i reutilitza fitxers de paquets per escalar les lectures.

Consistència causal (CC)

Quan un client fa un push a una branca, Pando desa el fitxer de paquets entrant com un blob immutable en un magatzem de blobs. Cada vegada que la branca avança, Pando registra en un magatzem de metadades una versió de referència que conté el nou extrem de la branca, el paquet que l'ha proporcionat i les versions de referència anteriors de les quals depèn el paquet. Tant el magatzem de blobs com el de metadades es poden escalar horitzontalment.

Fer un fetch d'un commit o una referència de Pando requereix reproduir al client un conjunt de versions de referència (fitxers de paquets), reconstruint l'estat del client que va crear la versió de referència sol·licitada. Això ofereix una consistència menys estricta en el conjunt del repositori, però garanteix la serialitzabilitat de les actualitzacions de cada referència. Els clients només entren en conflicte quan fan push simultàniament a la mateixa referència. Un client que llegeix diverses branques no pot assumir que aquestes lectures representin un mateix instant en tot el repositori.

A l'esquerra, tres paquets successius de trunk contenen els commits A, B i C; D i E; i F. A la dreta, trunk i topic divergeixen a C. Trunk avança fins a G, mentre que topic avança per D, E i F. L'últim paquet de topic conté D, E i F. Un contorn discontinu indica que D i E també són presents al paquet anterior de topic. La fletxa blava indica la dependència del nou paquet respecte de trunk a G. El pare de F a Git és E.

Un push a la branca principal afegeix un paquet incremental que conté un o més commits. Cada push a una branca secundària torna a incloure els commits anteriors d'aquesta branca, de manera que un client amb la versió anunciada de la branca principal la pot obtenir en un sol paquet.

Aquesta és una forma de consistència causal. Per fer un fetch d'una referència, el lector observa l'historial de l'escriptor que l'ha generat. Durant un push, Pando controla quin historial compartit acorden el client i el servidor, per garantir que els clients generin fitxers de paquets més fàcils de compartir.

Per tant, Pando no ofereix un estat de totes les referències corresponent a un mateix instant durant les operacions de Git, ni un ordre de les actualitzacions de referències dins d'una clonació o un fetch. En concret, suposem que les branques foo i bar s'actualitzen durant la negociació d'una clonació completa. Durant la negociació, un client pot observar foo en la versió 1 i bar en la versió 2, mentre que un altre observa foo en la versió 2 i bar en la versió 1. A la pràctica, els usuaris i els agents no depenen de l'ordre global de les actualitzacions de referències, sinó de l'ordre de les actualitzacions d'una sola referència.

Comparació d'enfocaments

En aquesta simulació dels tres models de consistència, configurem 1.000 clients perquè cadascun seleccioni una branca diferent i hi faci push repetidament de commits únics. Mesurem el temps real total transcorregut i la latència entre el push i el fetch. Aquesta última és una mètrica important per a les càrregues de treball, ja que mesura el temps d'anada i tornada perquè usuaris i agents col·laborin o utilitzin altres sistemes que depenen del control de versions. La simulació modelitza maquinari que va des de màquines virtuals i sistemes d'emmagatzematge de blobs estàndard fins a opcions d'alta gamma. En aquesta simulació, hem relaxat la consistència de tot el repositori per a la confirmació en tres fases.

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.

Executa aquesta simulació per comparar Pando amb dos altres enfocaments. Tria la infraestructura, des de maquinari estàndard fins a opcions d'alta gamma, el nombre de clients i altres paràmetres per comparar els resultats.

El sistema modelitzat de confirmació en tres fases mostra una latència d'escriptura significativament més alta sota càrrega a mesura que se saturen els discos. En canvi, els sistemes de registre d'escriptura anticipada mostren una latència de lectura significativament més alta pel mateix motiu, ja que els servidors tenen dificultats per llegir el registre al ritme necessari. Tant en les proves reals al nostre monorepositori com en aquesta simulació, Pando admet molts milers de clients que poden fer push simultàniament sense afectar el rendiment sota càrrega.

En processar un push, Pando valida el fitxer de paquets entrant mentre el transmet a l'emmagatzematge de blobs, sense desempaquetar-lo en un repositori local. Quan un client inicia un fetch, Pando selecciona els paquets emmagatzemats que necessita el lector, i el lector els baixa. Cap servidor no ha d'aplicar fitxers de paquets a un repositori local ni fer operacions costoses de desempaquetatge i empaquetatge. Com que aquests servidors es dediquen principalment a negociar i seleccionar fitxers de paquets, els servidors Pando poden funcionar amb maquinari estàndard.

Això permet a Pando escalar fins als límits dels clients, de la xarxa entre els clients i Pando, i del servei d'emmagatzematge de blobs. Amb prou capacitat, els clients que treballen en branques independents poden transferir paquets tan de pressa com els permeti la seva connexió. Per atendre més clients a aquesta velocitat, podem afegir servidors Pando i escalar alhora els magatzems de metadades i de blobs.

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 edita fitxers, crea un commit localment i executa git push, que genera un fitxer de paquets amb aquests canvis per actualitzar una branca. Pando rep el paquet i el transmet a l'emmagatzematge de blobs. Després de validar el push entrant amb una descodificació d'una sola passada, Pando utilitza una transacció amb diverses claus per registrar una versió de referència al nostre magatzem de metadades i actualitzar atòmicament el punter de la referència. Això permet a Pando oferir consistència de "lectura de les pròpies escriptures". Tota aquesta operació d'escriptura requereix O(1)O(1) lectures i escriptures de metadades.

Quan un altre client fa fetch de la branca, Pando selecciona un conjunt de paquets emmagatzemats a partir de l'historial que el client ja té. Aquesta operació requereix O(log⁡Δ)O(\log \Delta) lectures de metadades, on Δ\Delta és la distància més curta, en versions de referència, entre el que el client té i el que vol. El servidor retorna els URI dels fitxers de paquets, i el client els baixa a través d'un servei, Express, que desa els fitxers en memòries cau regionals.

Aquest disseny es basa en els tres serveis centrals de l'arquitectura de Pando: push, fetch i compactació.

Escalar els push

Quan un client fa un push, un servidor Git convencional actua com un jugador de pòquer que mostra totes les cartes: revela al client totes les branques que té. A continuació, el client reuneix els commits, arbres i blobs per crear un fitxer de paquets que exclou els objectes que sap que el servidor ja té.

Anunciar més referències pot reduir la mida del push. També pot fer que el paquet sigui més difícil de reutilitzar, perquè a un altre client li poden faltar objectes que l'escriptor ha omès.

Pando adopta un enfocament diferent. En lloc de mostrar totes les cartes, només anuncia al client un conjunt seleccionat de branques. Ho fa per dos motius:

  1. Oferir una visió consistent de totes les referències és costós. Amb milions de branques, només l'anunci de referències pot superar els 100 MiB. Per oferir-lo, cada servidor ha de mantenir un mapa actualitzat, amb un cost de coordinació considerable, o fer una consulta a la base de dades que creix amb el nombre de referències.

  2. Si només es proporciona al client un subconjunt de les referències, aquest genera un paquet que depèn únicament del que el servidor li permet excloure explícitament. El seguiment de les dependències permet a Pando reutilitzar el fitxer de paquets que un client genera en un push i servir-lo com a resposta a un fetch posterior.

Com més referències anuncia un servidor Git, més fonts tenen els clients que fan push per excloure'n objectes, cosa que els permet generar paquets més petits. Tanmateix, per reutilitzar aquests fitxers de paquets, el servidor ha de poder comprovar que els clients que fan fetch tenen totes les dependències. Si no és així, el fitxer de paquets pot ometre objectes que el client necessita.

En canvi, si un servidor Git no anuncia cap referència, el client generaria un fitxer de paquets amb tot l'historial del repositori. Qualsevol altre client pot utilitzar aquest fitxer, però amb el cost considerable de convertir, a la pràctica, el fetch de qualsevol branca en una clonació completa.

Corba que mostra com les referències anunciades afecten la mida del fitxer de paquets: des de l'historial complet quan no se n'anuncia cap, passant per l'anunci de la branca principal, fins a només els objectes nous quan s'anuncien totes les referències.

Anunciar més referències dona lloc a fitxers de paquets més petits. La major part de la reducció de mida es pot aconseguir anunciant-ne només una.

Per defecte, quan un client fa push d'una nova branca de funcionalitat, Pando només anuncia com a base una branca designada com a principal. Això manté constant la mida de l'anunci a mesura que el repositori acumula branques, però té contrapartides. Quan un enginyer fa push moltes vegades a una mateixa branca o crea una branca de funcionalitat sobre una altra, el client ha de reenviar tot el que no és a la branca principal.

Això fa que els push successius a la branca siguin més grans, amb objectes potencialment redundants, i consumeix més amplada de banda del client. Al nostre monorepositori, la mediana de la mida dels fitxers de paquets generats així era inferior a 20 KiB, i el percentil 99 era inferior a 3 MiB. Per a nosaltres, era una contrapartida raonable, perquè un lector que ja té l'historial anunciat de la branca principal pot fer fetch de la branca amb aquell únic fitxer de paquets.

Després de triar el conjunt de referències que anunciarà al client, Pando accepta el push i actualitza el seu magatzem de metadades. Pando divideix l'actualització de metadades en una part síncrona i una d'asíncrona. L'actualització síncrona de metadades garanteix que els clients puguin fer fetch immediatament del commit acabat d'actualitzar. Aquesta operació escriu una nova versió de referència i actualitza el punter perquè hi apunti. Desa el nou extrem de la branca, les dependències utilitzades per negociar el fitxer de paquets i un enllaç al fitxer enviat amb el push.

Alhora, la mateixa transacció registra feina asíncrona, com ara la programació de la compactació. En els push molt grans, només s'escriu síncronament un subconjunt dels commits. Això ens permet requerir només un nombre constant de lectures i escriptures al magatzem de metadades, independentment de la mida del push.

És important destacar que els servidors Pando poden acceptar de manera independent push a referències no relacionades. L'única clau per la qual competeixen és el punter de la referència, que protegim amb una operació atòmica semblant a comparar i intercanviar. Podem escalar el rendiment d'escriptura afegint servidors Pando, unitats d'escalat del magatzem de metadades o magatzems de blobs.

Fetch i clonacions a la màxima velocitat de la xarxa

Quan un client fa un fetch, proporciona al servidor dos conjunts d'ID de commit, coneguts al protocol com a have i want. En un servidor Git convencional, el servidor calcula la diferència de conjunts entre els objectes accessibles des dels haves i els accessibles des dels wants, els comprimeix i envia el resultat al client. Tot i que el servidor Git de codi obert disposa de diversos índexs locals per accelerar aquesta operació, generar aquests fitxers de paquets pot ser costós i, en general, són difícils d'aprofitar en memòria cau.

En canvi, Pando utilitza les correspondències de commits registrades durant els push per cercar els have i want del client i resoldre'ls en un conjunt de versions de referència que contenen aquests commits. Cadascuna d'aquestes versions de referència enllaça amb un fitxer de paquets rebut anteriorment per Pando. El servidor aprofita aquesta informació per determinar què necessita el client i selecciona els fitxers de paquets que cobreixen el conjunt d'objectes necessaris.

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 associa els haves i wants del protocol Git a una seqüència de versions de referència. Sol·licitar una branca secundària implica sol·licitar també les seves dependències de la branca principal.

El protocol Git només permet enviar un fitxer de paquets a la resposta HTTP d'una clonació o un fetch. Tanmateix, una funció poc valorada del protocol Git, packfile-uri, permet al servidor retornar enllaços per baixar paquets addicionals per separat. Pando utilitza exclusivament aquest mecanisme i transmet un fitxer de paquets buit dins de la resposta. Per donar suport a aquesta funció, OpenAI va aportar diverses millores a packfile-uri al projecte original.

Els sistemes d'OpenAI operen en moltes regions geogràfiques, per la qual cosa mantenim clústers de Pando amb memòries cau locals a cadascuna. Un servei de distribució de paquets, Express, manté una memòria cau del nostre magatzem de blobs, i un altre servei en manté una del magatzem de metadades. Aquestes memòries cau permeten a Pando atendre peticions d'un ID de commit o lectures especials d'una referència íntegrament des de l'estat desat a la memòria cau del clúster. Utilitzem hash consistent per encaminar les peticions de fitxers de paquets als servidors Express, amb taxes d'encert de memòria cau superiors al 99 %.

Aquest sistema de memòries cau ofereix un rendiment extremament alt, capaç de saturar la capacitat de la xarxa entre els clients i Pando. Amb aquests URI de fitxers de paquets i les memòries cau locals, hem comprovat que les operacions de clonació i fetch saturen enllaços de xarxa de 10 Gbps utilitzant només màquines virtuals estàndard de proveïdors de núvol.

Compactació i temps fins al primer byte

En la majoria de servidors Git, el reempaquetatge i la recollida de brossa són necessaris per mantenir el rendiment d'un repositori en creixement. Tots dos processos recuperen espai, però consumeixen molts recursos de càlcul i d'E/S.

En un repositori que canvia tan de pressa com el monorepositori d'OpenAI, no és viable enviar un URI individual de fitxer de paquets per cada push fet des de l'últim fetch. Cada dia, un client hauria de baixar desenes de milers de fitxers de paquets per posar al dia la seva branca principal.

En lloc d'això, Pando combina paquets en segon pla per reduir aquest cost, amb un disseny similar a la compactació dels arbres de fusió estructurats en registre. Cada push genera el que Pando anomena un fitxer de paquets "L0", que representa la unitat mínima de dades que Pando pot transmetre. Amb el temps, els processos en segon pla agrupen aquests fitxers de paquets en cobertures que abasten una seqüència de versions de referència i creixen geomètricament a partir de L0. Actualment, el nostre monorepositori utilitza cobertures de fitxers de paquets de fins al nivell L4.

Aquestes cobertures tenen una doble funció. Quan una cobertura és prou plena, un procés de compactació se'n fa càrrec i genera un únic fitxer de paquets. Això millora la relació de compressió reorganitzant els objectes dins del fitxer de paquets. Però també millora el rendiment de la negociació del fetch, ja que actua com una llista de salts entre versions de referència. En recórrer simultàniament els índexs de cada nivell, Pando pot seleccionar en temps logarítmic un conjunt de paquets per enviar a un client. Aquestes cobertures de fitxers de paquets, reempaquetades o no, permeten planificar els fetch de manera més eficient i redueixen les exigències tant de rendiment com de latència per a Pando.

Compaction in Pando

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

Compaction in progressPublished cover

La compactació combina en segon pla fitxers de paquets de moltes versions de referència, cosa que permet fer fetch més eficients amb menys fitxers.

A diferència de molts servidors Git, els fitxers de paquets es conserven a tots els nivells i no es descarten ni s'eliminen amb la recollida de brossa. Els paquets de tots els nivells s'utilitzen contínuament per atendre els clients, segons els have i want que indiquen en un fetch. I, per als clients que utilitzen Pando com a API, llegir un objecte qualsevol d'un fitxer de paquets sol requerir llegir menys dades d'un L0 que d'un L4, perquè és menys probable que estigui comprimit.

La compactació de Pando redueix significativament el volum total de dades transferides als clients i permet que la planificació de paquets creixi de manera sublineal respecte de l'endarreriment del client. Això és especialment important per als repositoris grans i amb canvis ràpids, que preveiem que esdevindran habituals en el desenvolupament de programari.

La creació de Pando

Vam crear Pando amb Codex i vam passar de l'inici del projecte a la producció en només 10 setmanes. El nucli de Pando el va dissenyar un sol enginyer, el van implementar equips d'agents i el van desplegar uns quants enginyers. Pando va començar a servir trànsit de producció l'abril d'aquest any i forma part d'un sistema més ampli d'eines de gestió del control de versions utilitzat a OpenAI.

Vam utilitzar una gran varietat d'enfocaments per validar el disseny de Pando. Vam aplicar els nostres principis per crear bases de programari més segures⁠. Com que era un projecte nou, vam decidir implementar Pando en Rust i sense dependències heretades. Vam escriure una bateria de proves de conformitat perquè els agents poguessin contrastar els seus canvis amb el comportament estàndard de Git. També vam desenvolupar entorns d'execució de proves de simulació determinista per comprovar els invariants i les propietats del sistema. Això ens va permetre detectar i mitigar condicions de cursa subtils i regressions en la negociació i la planificació de paquets.

Molts models van contribuir a aquest sistema; a mesura que n'hi havia de nous disponibles, els utilitzàvem per millorar el rendiment i la fiabilitat de Pando. Amb GPT‑6 Astra, els agents van escriure codi Lean i TLA+ per formalitzar una "teoria de Pando" que vam utilitzar per trobar i corregir errors d'implementació i demostrar propietats del sistema. Diverses sessions ininterrompudes van funcionar durant centenars d'hores per trobar i corregir casos límit a Pando. També executem agents les 24 hores del dia per sotmetre Git i Pando a proves adversàries amb models amb capacitats Daybreak.

El desplegament de Pando

Inicialment vam desplegar Pando com a servidor intermediari de lectura i escriptura davant del nostre sistema de control de versions existent. Això ens va permetre mantenir Pando i el sistema anterior sincronitzats, de manera que podíem traslladar el trànsit a Pando amb seguretat i sense interrupcions.

Com a part del seu disseny de memòria cau, Pando també pot eliminar peticions duplicades i combinar-les abans de reenviar-les. Això ens va permetre mantenir el bon funcionament de les eines existents mentre traslladàvem els sistemes a Pando d'un en un. A mesura que migràvem més càrregues de treball a Pando, els seus push omplien la memòria cau de Pando i milloraven la fiabilitat del backend anterior.

Per optimitzar el rendiment amb Pando, vam començar a distribuir la nostra versió pública de Git als equips dels desenvolupadors i dins de l'aplicació d'escriptori de ChatGPT. Per migrar els clients a Pando per fases, vam utilitzar una capa interna al voltant de la interfície de línia d'ordres de Git. Vam crear moltes altres eines per gestionar sol·licituds de canvi i revisions de codi, així com API per a automatitzacions. Juntament amb el trànsit de Git, vam migrar centenars d'integracions que depenien d'API heretades, webhooks i baixades de repositoris.

El mateix Codex va ajudar els desenvolupadors i els equips a fer el canvi. Vam crear habilitats per preparar les còpies de treball existents i orientar els agents en l'ús del nou sistema. Així, els desenvolupadors només havien de demanar a Codex que migrés els seus entorns de desenvolupament i serveis.

El canvi a Pando va millorar la fiabilitat de les fusions i va reduir la mediana de la latència dels push gairebé un 80 %.

Pando pot servir un petabyte de dades de Git al dia amb només uns quants nuclis de CPU. Pando ha servit més de 57 terabytes de paquets de Git en una sola hora, amb més del 99 % d'aquests bytes provinents de la memòria cau del disc local.

Reutilitzar paquets permet que molts clients baixin el mateix historial del repositori amb poc càlcul als nodes que el serveixen. Aquest exemple mostra un sol repositori, però Pando està dissenyat per admetre'n molts més. A mesura que creix la demanda, podem escalar la distribució de dades i la negociació de Git de manera independent.

Contribucions al projecte Git

Construir Pando sobre el protocol i el client Git ens va ajudar a identificar errors i oportunitats per millorar el rendiment del mateix Git.

Per exemple, Pando serveix paquets reutilitzables directament als clients, de manera que baixar i indexar aquests paquets es va convertir en un element clau per fer el sistema ràpid i fiable. Aquesta feina va posar al descobert casos límit en les baixades simultànies, la neteja després de transferències interrompudes i la represa de baixades que ja s'havien completat. Vam corregir aquests casos, vam afegir validacions en acceptar un paquet existent per a un fetch i vam aportar aquesta feina al projecte original.

També necessitàvem saber en què es consumien el temps i l'amplada de banda. Per això vam afegir traçabilitat a les baixades de packfile-uri i recomptes de bytes a la generació de paquets, per distingir més fàcilment si un fetch lent dedicava el temps a generar paquets o a transferir-los.

A la versió de Git d'OpenAI⁠(s'obre en una finestra nova), el client pot baixar i indexar paquets independents en paral·lel. Vam afegir el concepte de "haves explícits" perquè un servidor pugui anunciar l'extrem actual d'una branca per separat de l'historial que un client que fa push pot ometre del seu paquet. A Pando, això permet als clients utilitzar --force-with-lease per comprovar que una branca de funcionalitat no ha avançat i generar el paquet prenent només la branca principal com a base. Qualsevol client que tingui aquest historial de la branca principal pot fer fetch de la branca amb el mateix paquet.

Algunes d'aquestes millores ja formen part del Git original, i moltes altres són al nostre repositori públic. Continuem treballant amb la comunitat Git perquè aquestes millores siguin útils més enllà de Pando. Pots seguir aquesta feina al nostre sistema públic de seguiment de pedaços⁠(s'obre en una finestra nova).

Els pròxims passos

Repensant les idees establertes sobre com escalar els repositoris Git, Pando ens permet escalar el nostre monorepositori sense canviar els fluxos de treball de Git que ja fan servir els nostres enginyers i agents. Al nostre monorepositori, Pando ha fet possible un grau més alt de paral·lelisme, ha millorat la fiabilitat de les fusions i ha reduït el temps necessari per publicar canvis de codi. Més enllà del monorepositori, també explorem altres usos de Pando dins d'OpenAI, incloses càrregues de treball en què un gran nombre d'agents han de col·laborar en un repositori Git compartit.

A mesura que els enginyers i els agents produeixin més programari, més equips de fora d'OpenAI toparan amb els límits que hem trobat en el control de versions. Actualment explorem maneres de compartir amb els nostres usuaris la tecnologia que fa possible Pando.

Pando es basa en dues dècades d'experiència dels usuaris i administradors de repositoris Git. Continuarem desenvolupant Pando i treballant amb la comunitat de codi obert de Git per compartir les millores que puguin beneficiar el conjunt d'usuaris de Git.

Autor

OpenAI