OpenAI

2 novembre 2026

Ingénierie

Faire évoluer Git chez OpenAI

Créer une gestion de code source à l’échelle des agents tout en investissant dans l’écosystème Git.

Par Aaron Friel, Taylor Blau et Ted Nyman, membres du personnel technique

Chargement…

Au cours de la dernière année, la taille de notre monodépôt et les demandes auxquelles il doit répondre ont augmenté à un rythme fulgurant. Il était clair que nous devions remanier notre système de gestion de code source pour prendre en charge le niveau de travail simultané que Codex rend possible.

Nous avons créé Pando, un nouveau système dorsal pour les dépôts Git, sans interrompre les travaux d’ingénierie ni changer la façon dont notre équipe utilise Git. Pando prend désormais en charge tout le développement dans le monodépôt d’OpenAI. Pando est conçu pour atteindre deux objectifs ambitieux : d’abord, des écritures extensibles horizontalement, optimisées pour des envois simultanés vers de nombreuses branches. Ensuite, maximiser le débit de lecture, afin que les clients puissent télécharger les données assez vite pour saturer entièrement leur bande passante réseau disponible.

Nous avons atteint ces objectifs tout en conservant la compatibilité avec le protocole et le client Git, permettant à nos ingénieurs comme à nos agents d’adopter Pando sans heurts dans leurs flux de travail existants. En production, Pando a divisé la latence médiane des envois par près de cinq. Il fournit maintenant près d’un pétaoctet de données Git par jour.

Nous présenterons ici les raisons qui nous ont poussés à créer un nouveau système de gestion de code source et à conserver la compatibilité avec Git. Nous comparerons notre système aux approches courantes de mise à l’échelle de Git, en expliquant pourquoi Pando est particulièrement apte à répondre aux besoins d’évolutivité d’OpenAI. Nous aborderons aussi la suite et la façon dont nous continuerons à contribuer à Git pour soutenir le travail à l’échelle des agents.

Pourquoi il nous fallait un nouveau système de gestion de code source

Codex a changé l’échelle et le rythme du développement chez OpenAI, permettant aux ingénieurs d’effectuer beaucoup plus de travail en parallèle. Cela signifie plus de branches et d’envois, plus de fusions, ainsi que plus de récupérations et de clonages depuis les environnements de développement, les tâches de recherche et l’intégration continue.

Ces demandes se cumulent. Un ingénieur a désormais plusieurs modifications prêtes pour l’intégration continue en même temps, et chaque envoi peut déclencher plusieurs tâches qui récupèrent le même commit.

Vers la fin de l’année dernière, les systèmes soutenant notre monodépôt ont commencé à peiner à suivre le rythme. Nous avons subi des récupérations et des envois lents, ainsi que des échecs de fusion, la fiabilité passant parfois sous les 90 %. Avions-nous atteint une limite de Git lui-même ou de la façon dont nous desservions notre monodépôt? Git est né d’une communauté qui échangeait par courriel des correctifs pour le noyau. Depuis plus de deux décennies, les développeurs adaptent leurs outils autour de Git à mesure que leur façon de créer des logiciels évolue. Lorsque des difficultés sont apparues dans notre propre monodépôt, il était important pour nous de pouvoir continuer à tirer parti de ce travail.

Nous avons trouvé un moyen de conserver ce flux de travail familier tout en reconstruisant le système dorsal pour traiter en parallèle les envois vers différentes branches et fournir simultanément le même historique à de nombreux lecteurs. En fondant notre flux de gestion de code source sur Git, nous pouvions apporter au client des améliorations utiles au-delà d’OpenAI. Nous voulions proposer au projet d’origine ces améliorations issues de notre copie de Git⁠(s'ouvre dans une nouvelle fenêtre), pour les partager avec l’ensemble de la communauté.

Concevoir Git à grande échelle

La mise à l’échelle d’un système de gestion de code source comporte deux dimensions essentielles : de nombreux clients qui envoient vers différentes branches et de nombreux lecteurs qui récupèrent le même historique. Pando optimise les deux.

Premièrement, Pando coordonne les mises à jour de chaque branche de façon indépendante. Les mises à jour de branches indépendantes peuvent se dérouler en parallèle, et un lecteur qui récupère une branche obtient l’historique nécessaire pour cette révision. En contrepartie, un lecteur qui consulte plusieurs branches peut les voir à différents instants.

Deuxièmement, Pando réutilise les fichiers pack (le format de Git pour compresser plusieurs objets ensemble) d’une récupération à l’autre, plutôt que de générer un fichier pack distinct pour chaque client. Un client peut télécharger des objets qu’il possède déjà, mais le serveur peut fournir les mêmes octets à de nombreux clients avec peu de calculs.

Ces choix nous permettent de faire évoluer les écritures entre les branches et de servir les lectures depuis des caches. Pour comprendre comment ces choix s’articulent, commençons par la façon dont Git stocke et transfère l’historique.

Introduction au stockage d’objets Git

Git doit pouvoir synchroniser deux parties d’un dépôt : les objets et les références.

Un dépôt Git enregistre des instantanés de fichiers et l’historique qui les relie sous forme de graphe d’objets. Un objet peut être un blob, un arbre, un commit ou une étiquette. Les blobs contiennent le contenu d’un fichier individuel, et les arbres associent des noms à des blobs ou à d’autres arbres pour former une structure de répertoires. Les commits pointent vers un arbre racine et zéro ou plusieurs commits parents, formant ainsi l’historique de votre dépôt.

Les objets sont identifiés par un hachage de leur type et de leur contenu. Les objets sont aussi immuables : enregistrer un fichier modifié crée un nouveau blob, tandis que les fichiers et répertoires inchangés peuvent réutiliser des objets existants.

Les branches et les étiquettes sont des références, ou « réfs », qui pointent vers des commits. Effectuer un commit sur une branche crée un nouveau commit et met à jour la référence de la branche pour qu’elle pointe vers celui-ci.

La branche refs/heads/main pointe vers le commit C2, dont le parent est C1. C1 pointe vers l’arbre racine T1, où parser.py renvoie au blob B1. C2 pointe vers l’arbre racine T2, où parser.py renvoie au blob B2. Dans les deux arbres racines, docs/ renvoie au même arbre, T3. Son entrée README.md pointe vers le blob B3, et usage.md pointe vers le blob B4.

Les objets Git se référencent entre eux par leur identifiant : les commits pointent vers des arbres, et les arbres vers d’autres arbres et des blobs. La branche refs/heads/main pointe vers le commit C2, dont le parent est C1. C1 pointe vers l’arbre racine T1, où parser.py renvoie au blob B1. C2 pointe vers l’arbre racine T2, où parser.py renvoie au blob B2. Dans les deux arbres racines, docs/ renvoie au même arbre, T3. Son entrée README.md pointe vers le blob B3, et usage.md pointe vers le blob B4.

Git stocke les objets à l’état « libre » ou « compacté ». Les objets libres sont stockés dans des fichiers individuels identifiés par leur identifiant d’objet. Les objets compactés sont stockés ensemble dans un fichier pack, ce qui permet de stocker les objets similaires sous forme de deltas les uns par rapport aux autres.

Deux versions de parser.py apparaissent sous $GIT_DIR/objects. À l’état d’objets libres, le blob A contient parse(), et le blob B contient à la fois parse() et parse_file(). Le fichier pack stocke A en entier et représente B par des instructions permettant de copier tout A et d’y ajouter parse_file(). Le fichier *.idx associé localise les deux blobs dans le fichier pack à l’aide de leur identifiant d’objet.

Git peut stocker les blobs dans des fichiers distincts ou les regrouper dans un pack, en encodant un blob comme un delta par rapport à un autre.

Dans Git, un objet est accessible depuis un commit si Git peut le trouver en suivant les liens de ce commit à travers ses parents, ses arbres et ses blobs. Lors d’une récupération, le client indique au serveur les commits qu’il souhaite et ceux qu’il possède déjà. Le serveur trouve les objets accessibles depuis les commits souhaités, exclut ceux dont il sait que le client dispose déjà et envoie le reste sous forme de fichier pack, au même format que celui utilisé par Git sur disque. Un clonage complet démarre sans aucun objet; il nécessite donc tous les objets accessibles depuis l’historique à cloner.

Un envoi transfère les objets dans l’autre sens en demandant au serveur de mettre à jour une ou plusieurs références. Le client utilise l’ensemble des références annoncées par le serveur pour éviter d’envoyer des objets que celui-ci possède déjà. Le pack envoyé peut donc dépendre d’objets existants : recevoir le pack seul ne suffit pas à reconstituer le nouvel historique.

Répliquer un dépôt Git signifie garder à la fois les objets et les références disponibles sur plusieurs serveurs. Avant qu’une réplique puisse fournir la nouvelle valeur d’une référence, elle doit pouvoir fournir les objets accessibles depuis cette valeur. Le service doit aussi coordonner les mises à jour de références concurrentes, c’est-à-dire les envois qui tentent de faire passer la même branche d’une même ancienne tête à de nouveaux commits différents. Les systèmes qui imposent un ordre unique à toutes les écritures d’un dépôt peuvent obliger les envois vers des branches indépendantes à s’attendre mutuellement.

Modèles existants de services Git distribués

Pendant la conception de Pando, nous avons évalué des approches classiques de mise à l’échelle de la gestion de code source avec Git : l’une coordonne les mises à jour de références entre les répliques du dépôt par validation à trois phases (3PC, un protocole de coordination d’une validation entre plusieurs nœuds); l’autre ordonne les mises à jour au moyen d’un journal d’écriture anticipée partagé (WAL, un registre des modifications écrites avant leur application) dans un stockage de blobs.

Validation à trois phases (3PC)

Dans l’architecture à validation à trois phases que nous avons modélisée, le service Git conserve une copie complète du dépôt sur plusieurs répliques. Lorsqu’un serveur reçoit un envoi, le fichier pack entrant est copié et transmis à toutes les répliques en parallèle. Pour publier l’envoi, une majorité de répliques doit accepter la mise à jour de référence. Le serveur qui gère cette opération est le coordonnateur, et plusieurs cycles de communication sont nécessaires entre lui et chacune des autres répliques.

Pour accepter une mise à jour de référence, chaque réplique verrouille sa copie de la référence et vérifie qu’elle pointe toujours vers le commit attendu par le client. Le coordonnateur envoie ensuite un message de prévalidation aux répliques qui acceptent. Une fois qu’une majorité accuse réception du message de prévalidation, le coordonnateur leur demande d’effectuer la mise à jour. Toute réplique à jour peut alors annoncer la nouvelle tête de branche depuis sa copie locale du dépôt.

Conserver des répliques complètes permet de servir les lectures localement et garantit aux clients une vue cohérente de toutes les références, quelle que soit la réplique choisie. Par conséquent, les références sont mises à jour une à la fois, et chaque envoi nécessite plusieurs allers-retours pour coordonner la mise à jour de référence entre les répliques.

L’attente d’un quorum fait dépendre le traitement de l’envoi des performances disque des répliques et de la latence de communication. À mesure que le volume d’écriture augmente, les répliques doivent suivre le rythme des packs entrants et du trafic de lecture.

Journal d’écriture anticipée (WAL)

Dans l’architecture fondée sur le WAL que nous avons modélisée, le service Git utilise un manifeste partagé dans le stockage de blobs pour ordonner les mises à jour à l’échelle du dépôt. Lorsqu’un serveur reçoit un envoi, il téléverse le fichier pack entrant vers un stockage de blobs et prépare la mise à jour de référence dans sa copie locale du dépôt. Pour publier l’envoi, il ajoute à un manifeste partagé conservé dans le stockage de blobs une entrée qui enregistre la mise à jour de référence et pointe vers le fichier pack. Le serveur Git met ensuite le manifeste à jour au moyen d’une opération de comparaison et échange. Celle-ci remplace le manifeste de façon atomique ou rejette la modification. Si une autre entité l’a mis à jour entre-temps, le serveur lit les entrées ajoutées, vérifie que sa mise à jour de référence est toujours valide et réessaie avec le fichier pack déjà téléversé.

Les entités qui effectuent les écritures peuvent téléverser leurs fichiers pack en parallèle, mais même les envois vers des branches indépendantes exigent de mettre à jour le même manifeste. Le temps aller-retour d’une opération réussie de comparaison et échange limite la fréquence à laquelle le dépôt peut publier un nouveau manifeste. Un stockage de blobs à faible latence rend cette étape rapide. Le traitement par lots permet à un manifeste de mettre à jour plusieurs références à la fois, au prix d’une mise en file d’attente des mises à jour et d’une latence accrue.

Le stockage de blobs peut recevoir des fichiers pack en parallèle, mais les mises à jour du manifeste partagé restent en concurrence. Augmenter uniquement la capacité du stockage de blobs ne résout pas ce goulot d’étranglement. Chaque réplique doit toujours lire et indexer les packs du stockage de blobs avant de pouvoir les fournir localement. Si les écritures entrantes dépassent le rythme de ce travail, les répliques prennent du retard et les clients attendent plus longtemps pour récupérer les commits nouvellement envoyés.

Conception de l’architecture de Pando

Cela nous amène à Pando. Pando est une nouvelle architecture pour desservir les dépôts Git, conçue pour prendre en charge de nombreux clients effectuant des envois simultanés. Voici comment elle fonctionne :

Repenser les contraintes

Avant de développer Pando, nous pensions pouvoir répondre à nos exigences sans faire de compromis sur Git. Il fallait toutefois revoir certains éléments que la plupart des systèmes Git considèrent comme des exigences :

  • Cohérence à l’échelle du dépôt : Fournir un instantané cohérent de toutes les références à un moment précis exige une coordination à l’échelle du dépôt. Dans un système qui ordonne ensemble toutes les mises à jour de références, les envois vers des branches indépendantes se disputent un point de coordination unique.

  • Fichiers pack uniques pour chaque client : Si deux clients ayant des historiques locaux différents récupèrent la même branche, le serveur peut devoir générer un pack différent pour chacun afin d’éviter de leur envoyer des objets qu’ils possèdent déjà. La création de ces réponses consomme des ressources processeur et des entrées-sorties disque, ce qui complique le passage à un grand nombre de lecteurs.

Pando offre plutôt un modèle de cohérence assoupli (la cohérence causale) pour faire évoluer les écritures, et réutilise les fichiers pack pour faire évoluer les lectures.

Cohérence causale (CC)

Lorsqu’un client effectue un envoi vers une branche, Pando stocke le fichier pack entrant sous forme de blob immuable dans un stockage de blobs. Chaque fois que la branche évolue, Pando enregistre dans un stockage de métadonnées une version de référence contenant la nouvelle tête, le pack qui l’a fournie et toutes les versions de référence antérieures dont dépend le pack. Le stockage de blobs et le stockage de métadonnées sont tous deux extensibles horizontalement.

Récupérer un commit ou une référence depuis Pando exige de rejouer un ensemble de versions de référence (fichiers pack) sur le client, pour reconstituer l’état du client qui a créé la version de référence récupérée. Cela offre une forme de cohérence plus faible à l’échelle du dépôt, tout en assurant la sérialisabilité des mises à jour de chaque référence. Les clients n’entrent en conflit que lorsqu’ils effectuent simultanément un envoi vers la même référence. Un client qui lit plusieurs branches ne peut pas supposer que ces lectures décrivent un seul instant à l’échelle du dépôt.

À gauche, trois packs successifs du tronc contiennent les commits A, B et C; D et E; puis F. À droite, le tronc et la branche thématique divergent à C. Le tronc avance jusqu’à G, tandis que la branche thématique progresse par D, E et F. Le dernier pack de la branche thématique contient D, E et F. Un contour en pointillés indique que D et E figurent aussi dans le pack précédent de la branche thématique. La flèche bleue indique la dépendance du nouveau pack envers le tronc à G. Le parent Git de F est E.

Un envoi vers le tronc ajoute un pack incrémental contenant un ou plusieurs commits. Chaque envoi vers une branche secondaire inclut de nouveau les commits précédents de la branche thématique, afin qu’un client disposant de la version annoncée du tronc puisse récupérer la branche en un seul pack.

Il s’agit d’une forme de cohérence causale. Pour récupérer une référence, le lecteur observe l’historique du client qui l’a produite. Lors d’un envoi, Pando contrôle l’historique commun sur lequel le client et le serveur s’entendent, afin que les clients produisent des fichiers pack plus faciles à partager.

Par conséquent, Pando ne fournit ni état de toutes les références à un instant précis pendant les opérations Git, ni ordre des mises à jour de références au sein d’un clonage ou d’une récupération. Concrètement, supposons que les branches foo et bar soient toutes deux mises à jour pendant la négociation d’un clonage complet. Pendant la négociation, un client peut observer foo à la version 1 et bar à la version 2, tandis qu’un autre observe foo à la version 2 et bar à la version 1. En pratique, les utilisateurs et les agents ne dépendent pas de l’ordre global des mises à jour de références, mais plutôt de l’ordre des mises à jour d’une même référence.

Comparaison des approches

Dans cette simulation des trois modèles de cohérence, nous configurons 1 000 clients pour qu’ils sélectionnent chacun une branche distincte, puis y envoient à répétition des commits uniques. Nous mesurons la durée réelle totale et la latence entre l’envoi et la récupération. Cette dernière mesure est importante pour les charges de travail, car elle indique le temps aller-retour nécessaire aux utilisateurs et aux agents pour collaborer ou utiliser d’autres systèmes qui reposent sur la gestion de versions. La simulation modélise des machines virtuelles et des systèmes de stockage de blobs allant de solutions standard à haut de gamme. Dans cette simulation, nous avons assoupli la cohérence à l’échelle du dépôt pour la validation à trois phases.

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.

Exécutez cette simulation comparant Pando à deux autres approches. Choisissez l’infrastructure, du matériel standard au haut de gamme, le nombre de clients et d’autres paramètres pour comparer les résultats.

Le système de validation à trois phases modélisé présente une latence d’écriture nettement plus élevée sous charge lorsque ses disques saturent. Les systèmes à journal d’écriture anticipée présentent quant à eux une latence de lecture nettement plus élevée pour la même raison, les serveurs peinant à suivre le rythme de lecture du journal. Tant dans les tests réels sur notre monodépôt que dans cette simulation, Pando prend en charge plusieurs milliers de clients capables d’effectuer des envois simultanés sans nuire aux performances sous charge.

Lors du traitement d’un envoi, Pando valide le fichier pack entrant tout en le transférant en continu vers le stockage de blobs, sans le décompacter dans un dépôt local. Lorsqu’un client commence une récupération, Pando sélectionne les packs stockés dont le lecteur a besoin, puis le lecteur les télécharge. Aucun serveur n’a besoin d’appliquer les fichiers pack à un dépôt local ni d’effectuer de coûteuses opérations de décompactage et de compactage. Comme les serveurs Pando effectuent principalement la négociation et la sélection des fichiers pack, ils peuvent fonctionner sur du matériel standard.

Cela permet à Pando de monter en charge jusqu’aux limites des clients, du réseau entre les clients et Pando, et du service de stockage de blobs. Avec une capacité suffisante, les clients travaillant sur des branches indépendantes peuvent chacun transférer des packs aussi vite que leur connexion le permet. Pour servir davantage de clients à ce débit, nous pouvons ajouter des serveurs Pando et augmenter en parallèle la capacité des stockages de métadonnées et 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 modifie des fichiers, crée un commit localement et exécute git push, ce qui crée un fichier pack de ces modifications pour mettre à jour une branche. Pando reçoit le pack et le transfère en continu vers le stockage de blobs. Après avoir validé l’envoi entrant par un décodage en un seul passage, Pando utilise une transaction multiclés pour enregistrer une version de référence dans notre stockage de métadonnées et mettre à jour le pointeur de référence de façon atomique. Pando peut ainsi offrir une cohérence de type « lecture de ses propres écritures ». Cette opération d’écriture complète nécessite O(1)O(1) lectures et écritures de métadonnées.

Lorsqu’un autre client récupère la branche, Pando sélectionne un ensemble de packs stockés en fonction de l’historique que le client possède déjà. Cette opération nécessite O(log⁡Δ)O(\log \Delta) lectures de métadonnées, Δ\Delta étant la plus courte distance, en versions de référence, entre ce que le client possède et ce qu’il souhaite. Le serveur renvoie des URI de fichiers pack, et le client les télécharge par l’intermédiaire d’Express, un service qui stocke les fichiers pack dans des caches régionaux.

Cette conception repose sur les trois services centraux de l’architecture de Pando : les envois, les récupérations et le compactage.

Mise à l’échelle des envois

Lorsqu’un client effectue un envoi, un serveur Git classique agit comme un joueur de poker qui montre toutes ses cartes : il révèle au client chacune des branches qu’il possède. Le client rassemble ensuite les commits, les arbres et les blobs pour créer un fichier pack qui exclut les objets dont il sait que le serveur dispose déjà.

Annoncer davantage de références peut réduire la taille de l’envoi. Cela peut aussi rendre le pack plus difficile à réutiliser, car il peut manquer à un autre client des objets que l’expéditeur a omis.

Pando adopte une approche différente. Au lieu de dévoiler tout son jeu, il n’annonce au client qu’un ensemble choisi de branches. Il le fait pour deux raisons :

  1. Fournir une vue cohérente de chaque référence est coûteux. Avec des millions de branches, la seule annonce des références peut dépasser 100 Mio. Pour la fournir, chaque serveur doit soit maintenir une table de correspondance à jour, ce qui entraîne un coût de coordination important, soit exécuter une requête de base de données dont le coût augmente avec le nombre de références.

  2. En ne fournissant qu’un sous-ensemble des références au client, on l’amène à générer un pack qui dépend uniquement de ce que le serveur l’autorise explicitement à exclure. Le suivi des dépendances permet à Pando de réutiliser le fichier pack généré par un client lors d’un envoi en le fournissant comme réponse à une récupération ultérieure.

Plus un serveur Git annonce de références, plus les clients qui effectuent des envois disposent de sources dont ils peuvent exclure les objets, ce qui leur permet de produire des packs plus petits. Toutefois, pour réutiliser ces fichiers pack, le serveur doit pouvoir prouver que les clients qui les récupèrent possèdent chacune des dépendances. Sinon, le fichier pack risque d’omettre des objets dont le client a besoin.

À l’inverse, si un serveur Git n’annonce aucune référence, le client génère un fichier pack contenant l’historique complet du dépôt. Ce fichier pack peut être utilisé par tout autre client, mais au prix important de transformer la récupération de n’importe quelle branche en un clonage complet.

Courbe montrant l’effet des références annoncées sur la taille des fichiers pack : l’historique complet si aucune référence n’est annoncée, puis l’annonce du tronc, jusqu’aux seuls nouveaux objets si toutes les références sont annoncées.

Annoncer davantage de références produit des fichiers pack plus petits. L’essentiel de la réduction de taille peut provenir de l’annonce d’une seule référence.

Par défaut, lorsqu’un client envoie une nouvelle branche de fonctionnalité, Pando n’annonce comme base qu’une branche désignée comme tronc. La taille de l’annonce reste ainsi constante à mesure que le dépôt gagne des branches, mais cela implique des compromis. Lorsqu’un ingénieur effectue de nombreux envois vers une même branche ou empile une branche de fonctionnalité sur une autre, le client doit renvoyer tout ce qui se trouve à l’extérieur du tronc.

Les envois successifs vers une branche deviennent donc plus volumineux, contiennent des objets potentiellement redondants et consomment davantage de bande passante côté client. Dans notre monodépôt, la taille médiane des fichiers pack produits ainsi était inférieure à 20 Kio, et le 99e centile était inférieur à 3 Mio. Pour nous, c’était un compromis judicieux, car un lecteur disposant déjà de l’historique annoncé du tronc peut récupérer la branche avec ce seul fichier pack.

Après avoir choisi les références à annoncer au client, Pando accepte l’envoi et met à jour son stockage de métadonnées. Pando divise la mise à jour des métadonnées en parties synchrones et asynchrones. La mise à jour synchrone des métadonnées garantit que les clients peuvent récupérer immédiatement le commit nouvellement mis à jour. Elle écrit une nouvelle version de référence et fait pointer la référence vers cette version, en stockant la nouvelle tête, les dépendances utilisées pour négocier le fichier pack et un lien vers le fichier pack envoyé.

En parallèle, les travaux asynchrones, comme la planification du compactage, sont enregistrés dans la même transaction. Pour les envois très volumineux, seul un sous-ensemble des commits est écrit de façon synchrone. Cela nous permet de n’exiger qu’un nombre constant de lectures et d’écritures dans le stockage de métadonnées, quelle que soit la taille de l’envoi.

Point important : les serveurs Pando peuvent accepter indépendamment les envois vers des références sans lien entre elles. La seule clé sujette à contention est le pointeur de référence, que nous protégeons au moyen d’une opération atomique de type comparaison et échange. Nous pouvons augmenter le débit d’écriture en ajoutant des serveurs Pando, des unités de capacité de stockage de métadonnées ou des stockages de blobs.

Récupération et clonage au débit maximal du réseau

Lorsqu’un client effectue une récupération, il fournit au serveur deux ensembles d’identifiants de commit, désignés dans le protocole par have et want. Un serveur Git classique calcule la différence entre l’ensemble des objets accessibles depuis les éléments possédés et celui des objets accessibles depuis les éléments souhaités, compresse les objets résultants et les envoie au client. Bien que le serveur Git à code source ouvert dispose de divers index locaux pour accélérer cette opération, ces fichiers pack peuvent être coûteux à calculer et se prêtent généralement mal à la mise en cache.

Pando utilise plutôt les correspondances de commits enregistrées lors des envois pour rechercher les éléments have et want du client et les associer à un ensemble de versions de référence contenant ces commits. Chacune de ces versions de référence renvoie à un fichier pack déjà reçu par Pando. Le serveur s’en sert pour déterminer ce dont le client a besoin, en sélectionnant les fichiers pack qui couvrent l’ensemble des objets nécessaires au client.

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 associe les éléments possédés et souhaités du protocole Git à une séquence de versions de référence. Demander une branche secondaire implique de demander aussi ses dépendances dans le tronc.

Le protocole Git ne permet d’envoyer qu’un seul fichier pack dans la réponse HTTP lors d’un clonage ou d’une récupération. Toutefois, une fonctionnalité méconnue du protocole Git, packfile-uri, permet au serveur de renvoyer des liens pour télécharger séparément des packs supplémentaires. Pando utilise exclusivement cette fonctionnalité et transmet un fichier pack vide dans la réponse. Pour prendre en charge cette fonctionnalité, OpenAI a proposé au projet d’origine plusieurs améliorations de packfile-uri.

Les systèmes d’OpenAI fonctionnent dans de nombreuses régions géographiques; nous maintenons donc des grappes Pando dotées de caches locaux dans chacune d’elles. Express, un service de distribution de packs, met en cache notre stockage de blobs, tandis qu’un service distinct met en cache le stockage de métadonnées. Ces caches permettent à Pando de répondre aux requêtes portant sur un identifiant de commit ou sur une lecture spéciale de référence entièrement à partir de l’état mis en cache dans cette grappe. Nous utilisons le hachage cohérent pour acheminer les requêtes de fichiers pack vers les serveurs Express, avec des taux de succès du cache supérieurs à 99 %.

Ce système de caches offre un débit extrêmement élevé, capable de saturer la capacité réseau entre les clients et Pando. Avec ces URI de fichiers pack et ces caches locaux, nous avons confirmé que les opérations de clonage et de récupération saturent des liaisons réseau de 10 Gbit/s, en utilisant uniquement des machines virtuelles standard de fournisseurs infonuagiques.

Compactage et délai avant le premier octet

Pour la plupart des serveurs Git, le recompactage et le nettoyage des objets inutilisés sont nécessaires pour maintenir les performances d’un dépôt en croissance. Ces deux étapes récupèrent de l’espace, mais exigent beaucoup de calculs et d’entrées-sorties.

Dans un dépôt qui évolue rapidement comme le monodépôt d’OpenAI, il est irréaliste d’envoyer un URI de fichier pack individuel pour chaque envoi effectué depuis la dernière récupération. Chaque jour, un client devrait télécharger des dizaines de milliers de fichiers pack pour mettre son tronc à jour.

Pando combine plutôt les packs en arrière-plan pour réduire ce surcoût, selon une conception semblable au compactage des arbres de fusion structurés en journal. Chaque envoi produit ce que Pando appelle un fichier pack « L0 », soit la plus petite unité de données que Pando peut transmettre. Au fil du temps, des processus d’arrière-plan regroupent ces fichiers pack en couvertures englobant une séquence de versions de référence, dont la taille augmente géométriquement à partir de L0. Aujourd’hui, notre monodépôt utilise des couvertures de fichiers pack allant jusqu’à L4.

Ces couvertures remplissent deux fonctions. Une fois qu’une couverture est suffisamment remplie, un processus de compactage la prend en charge et produit un seul fichier pack. Cela améliore le taux de compression en réorganisant les objets à l’intérieur du fichier pack. Cela améliore aussi les performances de négociation des récupérations en agissant comme une liste à sauts sur les versions de référence. En parcourant simultanément les index à chaque niveau, Pando peut sélectionner en temps logarithmique un ensemble de packs à envoyer à un client. Ces couvertures de fichiers pack, recompactées ou non, permettent de planifier les récupérations plus efficacement, réduisant à la fois les exigences de débit et de latence pour Pando.

Compaction in Pando

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

Compaction in progressPublished cover

Le compactage combine en arrière-plan les fichiers pack de nombreuses versions de référence, permettant des récupérations plus efficaces avec moins de fichiers.

Contrairement à de nombreux serveurs Git, les fichiers pack sont conservés à tous les niveaux, sans être supprimés ni éliminés par le nettoyage des objets inutilisés. Les packs de tous les niveaux servent continuellement les clients, selon les éléments have et want indiqués dans leur requête de récupération. Pour les clients qui utilisent Pando comme API, lire un objet quelconque dans un fichier pack exige généralement de lire moins de données dans un L0 que dans un L4, car l’objet est moins susceptible d’être compressé.

Le compactage dans Pando réduit considérablement le volume total transféré aux clients et permet au coût de planification des packs de croître de façon sous-linéaire par rapport au retard du client. C’est particulièrement important pour les dépôts volumineux qui évoluent rapidement, lesquels devraient selon nous devenir la nouvelle norme en développement logiciel.

Création de Pando

Nous avons créé Pando avec Codex, passant du lancement du projet à la production en seulement 10 semaines. Le cœur de Pando a été conçu par un seul ingénieur, implémenté par des équipes d’agents et déployé par une poignée d’ingénieurs. Pando a commencé à traiter le trafic de production en avril de cette année et fait partie d’un ensemble plus vaste d’outils de gestion de code source utilisés chez OpenAI.

Nous avons employé une grande variété d’approches pour valider la conception de Pando. Nous avons appliqué nos principes pour bâtir des fondations logicielles plus sûres⁠. Comme le projet partait de zéro, nous avons choisi d’implémenter Pando en Rust, sans dépendances héritées. Nous avons écrit une suite de tests de conformité pour que les agents puissent vérifier leurs modifications par rapport au comportement standard de Git. Nous avons aussi développé des harnais de tests par simulation déterministe pour vérifier les invariants et les propriétés du système. Ils nous ont permis de détecter et d’atténuer des conditions de concurrence subtiles et des régressions dans la négociation et la planification des packs.

De nombreux modèles ont contribué à ce système; à mesure que de nouveaux modèles devenaient disponibles, nous les utilisions pour améliorer les performances et la fiabilité de Pando. À l’aide de GPT‑6 Astra, des agents ont écrit du Lean et du TLA+ pour formaliser une « théorie de Pando », que nous avons utilisée pour trouver et corriger des bogues dans l’implémentation et démontrer des propriétés du système. Plusieurs sessions continues se sont déroulées pendant des centaines d’heures pour détecter et corriger des cas limites dans Pando. Nous faisons aussi fonctionner des agents jour et nuit pour mener des tests adversariaux sur Git et Pando à l’aide de modèles dotés des capacités de Daybreak.

Déploiement de Pando

Nous avons d’abord déployé Pando comme mandataire relayant les lectures et les écritures vers notre système de gestion de code source existant. Cela nous a permis de garder Pando et le système précédent synchronisés, afin de transférer le trafic vers Pando en toute sécurité, sans interruption de service.

Dans le cadre de sa conception de mise en cache, Pando peut aussi dédupliquer et combiner les requêtes avant de les relayer. Nous avons ainsi pu maintenir le bon fonctionnement de nos outils existants pendant que nous déplacions les systèmes vers Pando un à un. À mesure que nous migrions davantage de charges de travail vers Pando, leurs envois alimentaient son cache et amélioraient la fiabilité de notre ancien système dorsal.

Pour optimiser les performances avec Pando, nous avons commencé à déployer notre distribution publique de Git sur les machines des développeurs et dans l’application de bureau ChatGPT. Pour migrer les clients vers Pando par étapes, nous avons utilisé une surcouche interne à l’interface en ligne de commande de Git. Nous avons créé de nombreux autres outils pour gérer les demandes de modification et les revues de code, ainsi que des API d’automatisation. En parallèle du trafic Git, nous avons migré des centaines d’intégrations qui dépendaient d’anciennes API, de webhooks et de téléchargements de dépôts.

Codex lui-même a aidé les développeurs et les équipes à effectuer la transition. Nous avons créé des compétences pour préparer les copies de travail existantes et guider les agents dans l’utilisation du nouveau système. Les développeurs pouvaient ainsi simplement demander à Codex de migrer leurs environnements de développement et leurs services.

Le passage à Pando a amélioré la fiabilité des fusions et réduit la latence médiane des envois de près de 80 %.

Pando peut fournir un pétaoctet de données Git par jour avec seulement une poignée de cœurs de processeur. Pando a fourni plus de 57 téraoctets de packs Git en une seule heure, dont plus de 99 % des octets provenaient du cache disque local.

La réutilisation des packs permet à de nombreux clients de télécharger le même historique de dépôt avec peu de calculs sur les nœuds qui les servent. Cet exemple porte sur un seul dépôt, mais Pando est conçu pour en prendre en charge beaucoup plus. À mesure que la demande augmente, nous pouvons faire évoluer indépendamment la distribution des données et la négociation Git.

Contribuer en retour au projet Git

Bâtir Pando sur le protocole et le client Git nous a aidés à repérer des bogues et des possibilités d’améliorer les performances de Git lui-même.

Par exemple, Pando fournit des packs réutilisables directement aux clients. Le téléchargement et l’indexation de ces packs sont donc devenus essentiels à la rapidité et à la fiabilité du système. Ce travail a révélé des cas limites dans les téléchargements simultanés, le nettoyage après des transferts interrompus et la reprise de téléchargements déjà terminés. Nous avons corrigé ces chemins d’exécution, ajouté des validations lors de l’acceptation d’un pack existant pour une récupération, et proposé ces travaux au projet d’origine.

Nous devions aussi comprendre où passaient le temps et la bande passante. Nous avons donc ajouté le traçage des téléchargements packfile-uri et le comptage des octets pour la génération des packs, afin de mieux distinguer si une récupération lente consacrait son temps à générer les packs ou à les transférer.

Dans la version de Git d’OpenAI⁠(s'ouvre dans une nouvelle fenêtre), le client peut télécharger et indexer des packs indépendants en parallèle. Nous avons ajouté le concept d’« éléments possédés explicites » pour qu’un serveur puisse annoncer la tête actuelle d’une branche séparément de l’historique qu’un client effectuant un envoi peut omettre de son pack. Sur Pando, cela permet aux clients d’utiliser --force-with-lease pour vérifier qu’une branche de fonctionnalité n’a pas changé et construire leur pack par rapport au seul tronc. Tout client disposant de cet historique du tronc peut ensuite récupérer la branche avec le même pack.

Plusieurs de ces améliorations sont déjà intégrées au projet Git d’origine, et de nombreuses autres figurent dans notre dépôt public. Nous continuons à travailler avec la communauté Git pour rendre ces améliorations utiles au-delà de Pando. Vous pouvez suivre ces travaux dans notre outil public de suivi des correctifs⁠(s'ouvre dans une nouvelle fenêtre).

La suite

En repensant les idées reçues sur la mise à l’échelle des dépôts Git, Pando nous permet de faire évoluer notre monodépôt sans modifier les flux de travail Git que nos ingénieurs et nos agents utilisent déjà. Dans notre monodépôt, Pando a accru le parallélisme, amélioré la fiabilité des fusions et réduit le temps nécessaire à la publication des modifications de code. Au-delà de notre monodépôt, nous explorons aussi d’autres utilisations de Pando au sein d’OpenAI, notamment pour des charges de travail où de nombreux agents doivent collaborer sur un dépôt Git partagé.

À mesure que les ingénieurs et les agents produiront davantage de logiciels, d’autres équipes à l’extérieur d’OpenAI rencontreront les limites que nous avons atteintes en matière de gestion de code source. Nous explorons actuellement des façons de partager avec nos utilisateurs la technologie qui rend Pando possible.

Pando s’appuie sur deux décennies d’expérience des utilisateurs et des exploitants de dépôts Git. Nous continuerons de développer Pando tout en travaillant avec la communauté du logiciel libre Git pour partager les améliorations qui profitent plus largement aux utilisateurs de Git.

Auteur

OpenAI