Des articles approfondis sur les technologies qui façonnent l'avenir.

Quand Git craque : leçons de monorepo à 30 000 merges par jour

Le passage à l'échelle d'un monorepo Git échoue de façons prévisibles à fort volume de merges. Leçons tirées de clients toujours en ligne et des commits côté serveur.

Illustration de coursiers robots s'agglutinant devant un comptoir de caisse, dans une vaste archive de boîtes de fichiers.
Git suppose que chaque client possède l'archive entière. Cette hypothèse tient très bien, jusqu'au moment où des milliers de clients se présentent chaque seconde.

Si vous exploitez un monorepo git, il existe quelque part dans votre avenir un chiffre où tout part en vrille. Pour Anthropic, ce chiffre était d'environ 30 000 merges par jour, un historique de 100 Go sur la branche principale et de l'ordre de 25 millions de checkouts CI par jour, ce qui, récupéré naïvement, représente plus de 600 pétaoctets de trafic git. Chaque jour. Leur solution a été d'écrire leur propre système de contrôle de version en cinq mois. La vôtre ne devrait pas être celle-là. Mais les erreurs qu'ils ont commises en chemin sont les mêmes que je vois chez des équipes à un dixième de cette échelle, alors parcourons-les.

Erreur numéro un : traiter un workflow centralisé comme un workflow distribué

Le pari fondamental de git est la décentralisation. Chaque clone est un dépôt complet : historique complet, base d'objets complète, entièrement fonctionnel hors ligne. Ce pari a fait le succès de git, et pour la plupart des dépôts il reste pertinent. Mais voici ce que presque personne n'admet : presque personne n'utilise git de cette façon. Votre organisation dispose d'un seul remote faisant autorité sur GitHub ou GitLab. Vos développeurs poussent et tirent en permanence. Vos runners CI sont, par définition, en ligne. Vous faites tourner un système centralisé déguisé en système distribué, et vous payez les coûts du distribué : chaque job CI qui traîne un historique qu'il ne lira jamais, chaque sandbox d'agent qui matérialise des packfiles dont il n'a pas besoin.

C'est la première vraie leçon de la refonte d'Anthropic. En prenant du recul, ils ont remarqué que leurs clients étaient des « agents et jobs toujours en ligne par définition ». Ils ont donc conçu l'architecture autour d'un serveur toujours joignable. Le client est volontairement devenu simple : un cache local borné, une copie de travail sur disque, un surveillant du système de fichiers. Le serveur répond aux requêtes de blame, calcule les diffs et effectue les merges. C'est la même conclusion à laquelle sont arrivés Google avec Piper et Meta avec Sapling, et ce n'est pas une position anti-git. C'est la reconnaissance que l'architecture doit correspondre à la charge de travail réelle, et non à celle pour laquelle l'outil a été conçu il y a vingt ans. J'ai vu ce schéma aussi dans les outils de sécurité : des équipes déploient une architecture d'agents capable de fonctionner hors ligne, puis l'exécutent dans un environnement où le mode hors ligne n'arrive jamais, et s'étonnent que leur couche de synchronisation soit un cauchemar.

La conclusion pour votre équipe n'est pas « construisez un VCS ». C'est : auditez vos hypothèses. Notez les propriétés de git dont vous dépendez réellement. Travail hors ligne ? Forks réels ? Signature cryptographique des commits ? Si la réponse honnête est « un seul remote, toujours en ligne, la CI fait tout », alors arrêtez d'optimiser pour un workflow distribué que vous n'avez pas, et commencez à optimiser les coûts de checkout et de requêtes.

Erreur numéro deux : masquer le problème d'échelle avec des miroirs et des seeds

Chaque équipe travaillant sur un grand dépôt traverse les mêmes étapes de deuil. D'abord les clones superficiels. Ensuite les checkouts sparse et les remotes promisor. Puis les miroirs git internes, et enfin des « seeds » de snapshots dans un stockage d'objets, pour que les jobs CI puissent s'amorcer depuis quelque chose proche de la pointe de main. Anthropic a utilisé tout cela : au pic, plusieurs centaines de miroirs internes. Et le mode de défaillance est toujours le même : les contournements ne plantent pas bruyamment, ils pourrissent. Les seeds s'éloignent de la pointe entre deux rafraîchissements. Les clones partiels cassent git blame et git log --follow de façons déroutantes. Le bisect de quelqu'un renvoie silencieusement de mauvais résultats parce que l'historique est tronqué. Personne ne s'en rend compte avant la revue d'incident.

Ce dernier point est celui qui me empêche de dormir. Les bugs de cohérence de l'historique partiel sont exactement le genre de faux subtil qui vit dans l'écart entre ce que vous croyez garanti par le système et ce qu'il fait réellement. Un clone superficiel ne renvoie pas d'erreur quand vous demandez un blame au-delà de sa profondeur ; il vous donne simplement une réponse partielle. Si votre pipeline de revert automatique fait confiance à cette réponse, vous avez construit une machine qui annule avec assurance le mauvais changement.

  • Les clones superficiels cassent les outils dépendant de l'historique (blame, bisect, merge-base) de façon silencieuse, et non bruyante.
  • Les seeds de snapshot s'écartent de la pointe de main ; plus votre taux de merges est élevé, plus la dérive est rapide, donc le contournement se dégrade exactement quand la charge augmente.
  • Les flottes de miroirs multiplient la surface opérationnelle : des centaines de copies avec état, qui peuvent prendre du retard, se corrompre ou manquer de disque indépendamment les unes des autres.
  • Chaque contournement qui touche à la sémantique de l'historique nécessite un test de non-régression, sinon il vous mordra pendant un incident.

À l'échelle d'Anthropic, la solution était structurelle : aucune copie entièrement matérialisée du dépôt n'existe nulle part, même côté serveur, et tout est adressé par contenu et mettable en cache horizontalement. À votre échelle, la solution est plus modeste : mesurez combien de machines détiennent une copie complète ou presque complète de votre dépôt, et soyez honnête sur la dérive et les modes de défaillance de chacune.

Erreur numéro trois : faire maintenir une copie du dépôt par chaque job CI

Une question qui mérite d'être posée sur votre flotte CI : combien de ces jobs ont réellement besoin d'une copie de travail ? La réponse d'Anthropic, après avoir construit leur système, a été « bien moins que nous ne le pensions ». Un job qui a besoin du diff entre un commit et sa base de merge, du blame d'un fichier ou de la liste des commits touchant un chemin peut interroger directement le serveur via une API. Pas de clone, pas de fetch, pas de disque. Leurs pipelines automatisés de bisect et de revert « ne seraient pas praticables à l'échelle » sans cet accès programmatique bon marché. Et je le crois, parce que j'ai vu des équipes essayer de faire tourner ces mêmes pipelines sur des clones locaux et se noyer dans les temps de fetch.

Ce principe dépasse largement la gestion de sources. Considérez le checkout comme l'exception coûteuse, et non comme le comportement par défaut. Quand un job a réellement besoin de fichiers, les checkouts sparse et virtualisés, qui ne matérialisent que les répertoires demandés, voire seulement les fichiers effectivement lus, réduisent le coût d'un ordre de grandeur ou plus. Le protocole de fetch d'Anthropic a complètement abandonné la négociation de git : le client demande des arbres et des blobs immuables précis, n'importe quel cache peut répondre, et l'ensemble fonctionne comme un CDN à plusieurs niveaux plutôt que comme un serveur intelligent qui calcule un packfile par client. Si vous avez déjà profilé un serveur git qui fond sous la charge CI, vous comprendrez pourquoi c'est important : la génération de packfiles consomme beaucoup de CPU précisément parce que le protocole demande au serveur de calculer un delta par rapport à ce que chaque client possède déjà.

Si vos clients sont toujours en ligne et qu'il existe une seule copie faisant autorité, chaque clone local que vous maintenez est un cache que vous payez au prix fort, tout en obtenant de toute façon une sémantique de cache.

Erreur numéro quatre : laisser le coût d'un commit croître avec la taille du dépôt

La décision d'ingénierie la plus intéressante de leur conception est le changeset. Un commit git pointe vers un arbre représentant l'état entier du dépôt ; leur changeset enregistre uniquement la liste des fichiers modifiés par rapport à son premier parent, ainsi que des pointeurs adressés par contenu vers le stockage de blobs, ajoutés à un write-ahead log. Pas de packfiles à accepter, pas de verrouillage, pas de coordination entre serveurs. Les refs (branches, tags) sont volontairement reléguées au second plan, car « un état global sur lequel chaque serveur doit s'accorder est un problème de passage à l'échelle qui se profile ». Déplacer une ref est un compare-and-swap sur une seule ligne Spanner, découplé de la création du commit.

Illustration stylisée de colis sur un tapis roulant alimentant un journal lumineux et des structures de données dérivées.
Enregistrez uniquement ce qui a changé dans un write-ahead log ; dérivez tout le reste (blame, historique de chemin, index de graphe) de manière paresseuse à partir de lui.

L'idée complémentaire est celle des « données dérivées » : index de blame, historique de chemins, skip lists du graphe de commits, tous calculés récursivement à partir des parents de chaque changeset, via un mécanisme générique unique. Calcul eager ou paresseux selon le type. Ajout de nouveaux types d'index sans toucher au client. C'est un très bon pattern pour tout système event-sourcé : j'ai recommandé la même forme pour des pipelines de journaux d'audit, où le log en ajout seul fait foi et chaque structure de requête est une projection reconstructible. Dès que quelqu'un maintient à la main une projection en dehors de ce mécanisme, vous avez de la dérive.

Une réserve du point de vue sécurité : ceux qui s'inquiètent de la centralisation ont un point qui mérite d'être pris au sérieux. Un système unique côté serveur, où vivent les commits, les merges et les requêtes d'historique, est une cible de grande valeur, et des agents IA disposant d'un accès en écriture à « l'unique dépôt qui gouverne tout » concentrent très mal le risque. Anthropic a construit en parallèle un « système IAM de contrôle de source riche », et les détails comptent davantage que les structures de données. Si vous centralisez, votre calcul du rayon d'explosion change : prévoyez l'isolation, les identifiants par agent et l'audit dès le premier jour, pas au troisième trimestre. Les équipes qui font tourner des agents avec état sur une infrastructure partagée connaissent déjà cette douleur.

Erreur numéro cinq : croire que 30 000 merges par jour signifient 30 000 bonnes décisions par jour

L'ingénierie est impressionnante. La question ouverte est ce qui circule à travers elle. Un praticien l'a dit sans détour : chaque fois qu'il arrête de relire le code généré par IA, il « trouve plus tard de la merde bancale. Toutes les relectures ne trouvent pas de la merde bancale, mais assez souvent pour que je ne puisse pas arrêter de tout relire ». Multipliez ce taux de détection par 30 000 merges par jour, et vous comprenez pourquoi Anthropic a aussi dû construire « de nouvelles façons de gérer et d'intégrer de grandes quantités de code relu » : la surface de revue, la file d'atterrissage, les pipelines de revert automatique. Le VCS n'était pas le produit. Le pipeline d'intégration l'était. Le gestionnaire de sources n'est devenu le goulot d'étranglement qu'après que la capacité de relecture est devenue le goulot d'étranglement en amont.

Il y a aussi un détail franc enfoui dans leurs récits : les ingénieurs du projet ont décrit une grande partie de leur rôle comme celui de dire « non » à l'agent de code. Le pipeline d'ingestion prototype qu'ils avaient écrit à la main était correct, jusqu'à ce qu'un modèle plus récent le réécrive, avec 20 fois moins de mémoire et 4 fois plus rapide, mais y parvenir a exigé des humains dotés de goût pour décider quoi accepter. Cela correspond à tout ce que j'ai observé : l'IA déplace le travail de l'ingénieur de l'écriture vers la relecture, et le débit de relecture est désormais la ressource rare. Nous avons déjà écrit sur la façon dont chaque couche de revue ralentit une équipe ; le plus dur, avec le code généré par agents, est que supprimer des couches de revue n'est pas plus sûr. Il faut de meilleurs outils de revue, pas moins de revue.

Ce qu'il faut vraiment faire

Vous n'êtes pas Anthropic. Vous n'avez pas leur budget de tokens, ce qu'ils admettent eux-mêmes, et vous n'avez pas 25 millions de checkouts quotidiens. Mais les outils de code IA font grimper le taux de merges de tout le monde, et les points de pression arrivent dans un ordre prévisible. Voici l'ordre des opérations que je suivrais, dans la loi du moindre regret de Priya :

  1. Mesurez d'abord. Comptez les checkouts quotidiens, les octets récupérés par job CI et le nombre de machines qui détiennent des copies complètes du dépôt. Vous avez probablement un problème de gaspillage avant d'avoir un problème d'échelle.
  2. Supprimez les checkouts inutiles. Déplacez les questions de métadonnées (diffs, blame, ascendance) vers des API (API GitHub/GitLab, ou un service interne léger) avant d'acheter la moindre infrastructure.
  3. Rendez les clones sparse et partiels sûrs. Testez explicitement vos outils de bisect, blame et revert sur des clones superficiels, ou interdisez-les dans l'automatisation.
  4. Faites de la CI un fetch côté serveur d'objets immuables chaque fois que c'est possible, et traitez les flottes de miroirs comme de la dette technique à intérêts composés.
  5. Investissez dans le pipeline d'intégration, pas dans le format du dépôt. La capacité de revue, les files de merge et le revert automatique sont les points où le développement à l'échelle des agents casse en premier.
  6. Ce n'est qu'ensuite qu'il faut envisager des architectures VCS alternatives : Sapling est open source et vous offre la plupart des bénéfices client-serveur sans avoir à écrire le vôtre.

La leçon la plus profonde n'a rien à voir avec git. Chaque outil encode des hypothèses sur sa charge de travail : humains hors ligne, un commit à la fois, du code écrit moins vite qu'il n'est relu. Or ces hypothèses sont aujourd'hui mises à mal par les agents, dans l'infrastructure, dans la revue et aux frontières de la sécurité. Les équipes qui s'en sortent le mieux ne seront pas celles qui reconstruisent tout de zéro. Ce seront celles qui auront repéré quelles hypothèses ont cédé en premier, et corrigé exactement celles-là.