Faire évoluer rapidement le stockage en ligne pour plus d’un milliard d’utilisateurs de ChatGPT
Comment nous avons adapté en Python notre plateforme de stockage applicatif Habitat à une croissance sans précédent.
Par Jon Lee, Chaomin Yu et Ben Ries, membres de l’équipe technique
Tous les produits OpenAI dépendent d’un accès rapide et fiable aux données, qu’il s’agisse de se connecter, de vérifier ses paramètres Codex ou de lancer une nouvelle conversation dans ChatGPT. Chacune de ces actions peut nécessiter de nombreuses recherches de données distinctes avant que le produit puisse répondre. Si ces requêtes sont lentes, le produit paraît lent. Si elles échouent, le produit cesse complètement de fonctionner.
Habitat est la plateforme de stockage en ligne que nous avons créée pour permettre aux produits OpenAI d’accéder rapidement et fiablement aux informations nécessaires. Habitat traite désormais plus de 70 millions de requêtes par seconde et prend en charge des produits utilisés chaque semaine par plus d’un milliard de personnes, dans près de 40 régions géographiques. Habitat a été lancé pour prendre en charge les GPT lors du DevDay 2023, initialement sous la forme d’une simple bibliothèque Python côté client connectée à une seule base de données. Aujourd’hui, c’est un système distribué complexe qui traite plus de 500 pétaoctets de données.
Figure 01 · Qu’est-ce qu’Habitat ?
Plateforme de stockage en ligne
Habitat est la plateforme de stockage en ligne que nous avons créée pour permettre aux produits OpenAI d’accéder rapidement et fiablement aux informations nécessaires.
- Requête
- Réponse
- Modifications (CDC)
Construire et exploiter une infrastructure à cette échelle n’est pas une mince affaire, sans être particulièrement difficile pour autant. Ce qui rendait notre situation unique, c’était la vitesse sans précédent à laquelle nous devions évoluer pour répondre à une croissance vertigineuse du nombre d’utilisateurs et de la demande produit, tout en développant une plateforme mature. Souvent, les ingénieurs système conçoivent pour une échelle dix fois supérieure et espèrent qu’elle suffira quelques années, tout en préparant le prochain facteur 10. Dans notre cas, notre croissance annuelle a dépassé un facteur 10 au cours des trois dernières années. La construction et l’exploitation d’Habitat ont donc reposé sur une succession et un ordonnancement de décisions tactiques : comprendre chaque composant au niveau le plus bas pour tirer le maximum de notre pile existante, tout en repoussant les pénuries de capacité de stockage et de calcul afin de gagner du temps pour les investissements fondamentaux.
- 70 M+
requêtes par seconde
- 1 Md+
personnes chaque semaine
- 500 Po+
de données
À mesure qu’OpenAI grandissait, Habitat devait suivre : devenir d’abord assez fiable pour le trafic critique des produits, puis assez rapide pour les utilisateurs du monde entier et, enfin, fonctionner efficacement à très grande échelle. Cet article est le premier d’une série en deux parties sur la mise à l’échelle de notre stockage en ligne. Nous expliquons ici comment Habitat a évolué, pourquoi nous avons transformé cette bibliothèque en service et comment nous avons poussé un service écrit dans un langage peu courant pour ce type d’usage, Python, jusqu’à en faire une couche de plateforme de stockage fiable.
Dans un prochain article, nous détaillerons la manière dont nous avons fiabilisé la mutualisation à grande échelle, notre stratégie par couches pour optimiser les performances de lecture et l’évolution de notre partenariat avec Azure Cosmos DB pour traiter fiablement une demande sans précédent.
Habitat est né d’une idée simple : les ingénieurs produit ne devraient pas avoir à se préoccuper de la gestion des bases de données. Habitat a été lancé pour prendre en charge les GPT lors du DevDay 2023, sous la forme d’une petite bibliothèque Python communiquant avec le serveur principal de ChatGPT. Elle prenait en charge un petit ensemble d’opérations qui correspondaient, en coulisses, à l’application de base de données Azure Cosmos DB.
La bibliothèque devait offrir aux équipes produit un moyen simple de stocker et de récupérer des données sans avoir à maîtriser les détails sous-jacents. Habitat se chargeait du travail nécessaire : déterminer le type de données concerné, leur provenance ou leur destination, si la requête était autorisée, etc.
Les ingénieurs produit n’ont pas à se préoccuper de la recherche de schéma, du routage, de l’autorisation, du chiffrement, de la sérialisation, de la mise en forme des requêtes ni du regroupement des connexions. Ils n’avaient même pas à tenir compte de la provenance des données : Azure Cosmos DB, caches ou autres types de stockage.
Figure 02 · Service Habitat
Flux simplifié d’une requête Habitat
En découplant la logique de stockage dans un service autonome, nous avons créé un point de contrôle unique pour les déploiements, l’observabilité et les améliorations de la plateforme.
- Requête
- Réponse
Cette bibliothèque Python fonctionnait bien et Habitat a été rapidement adopté par les ingénieurs produit d’OpenAI, malgré l’absence d’initiative centrale concertée visant à délaisser Postgres et Azure Cosmos DB en libre-service.
À mesure que les besoins produit évoluaient, les développeurs pouvaient même facilement ajouter à la bibliothèque partagée la prise en charge de fonctionnalités comme la mise en cache côté client, la compression ou le chiffrement.
Mi-2025, Habitat avait atteint les limites d’une mise en œuvre côté client. À mesure que la couche Habitat se complexifiait et que le nombre de services d’OpenAI augmentait, les modifications de protocole rétrocompatibles devenaient irréalisables.
Dans un cas, nous voulions réduire la portée d’une panne régionale sur nos jeux de données les plus critiques en les migrant vers plusieurs comptes Azure Cosmos DB distribués entre les régions. Cette modification nécessitait d’ajouter au client une logique de routage désactivée derrière un indicateur de fonctionnalité, de vérifier son déploiement chez tous les clients, puis d’activer l’indicateur.
Coordonner les déploiements dans des dizaines de services et travailler avec chaque équipe pour les effectuer a pris plusieurs jours. Avant l’activation, nous avons compris qu’il fallait ajouter une duplication des requêtes pour vérifier la logique de partitionnement. Son déploiement a pris deux jours supplémentaires. Corriger un bogue dans un élément qui s’est révélé incorrect ? Encore deux jours. Nous étions enfin prêts à activer l’indicateur, mais l’une des équipes a restauré son service, pour des raisons indépendantes, vers un client antérieur défectueux, provoquant précisément la panne que nous avions tant cherché à éviter.
Les modifications de la bibliothèque cliente exigeaient une coordination complexe entre des dizaines de services, un processus de plus en plus fragile, inefficace et exposé aux défaillances opérationnelles. Pour réduire cette dispersion opérationnelle lors des futurs déploiements, nous avons décidé de faire d’Habitat un service autonome.
En découplant la logique de stockage dans un service autonome, nous avons créé un point de contrôle unique pour les déploiements, l’observabilité et les améliorations de la plateforme. Au lieu de gérer des mises à jour fragmentées, nous pouvions appliquer les améliorations de façon centralisée et en faire immédiatement profiter tous les produits OpenAI.
Un service centralisé nous offre aussi un point de passage unique permettant de fournir les mécanismes les plus robustes de sécurité et de confidentialité des données. Le service Habitat nous permet d’appliquer centralement les politiques de contrôle d’accès, de journaliser les audits et de limiter l’accès aux ressources de stockage sous-jacentes telles qu’Azure Cosmos DB. Habitat joue un rôle essentiel dans la protection des données utilisateur et la prévention des accès non autorisés par des acteurs externes, internes ou des agents.
Nous savions qu’il nous fallait un service, mais nous ne voulions pas encore abandonner Python, malgré la surcharge qu’il entraîne dans ce contexte. Utiliser Python pour un service à haut débit a accru la latence réseau et considérablement augmenté les coûts de mise à l’échelle du CPU et de la mémoire par rapport à l’exécution d’une bibliothèque locale. Nous savions en outre que les inefficacités de Python seraient inacceptables à une échelle 100 fois supérieure, rendant une réécriture ultérieure presque certaine.
Nous considérions toutefois cela comme une prise de dette technique stratégique. Notre objectif principal n’était alors pas d’optimiser les coûts ou les ressources, mais de débloquer les développeurs produit et de stabiliser la plateforme. En acceptant à court terme les compromis de performance d’un service Python, nous avons pu donner la priorité aux difficultés les plus immédiates, établir nos API principales et bâtir une infrastructure robuste.
Nous avons également parié, de manière réfléchie, que les progrès rapides de nos propres modèles de codage simplifieraient le parcours technique à l’avenir. Nous avons parié que, lorsqu’une migration complète hors de Python deviendrait nécessaire, Codex et GPT la rendraient réalisable. Ce pari s’est finalement révélé juste.
Exécuter Habitat comme service Python serait sous-optimal en matière de performances, mais nécessaire. Python nous permet d’avancer vite, mais cela ne signifie pas que nous pouvions ignorer toute prudence et accepter des latences nettement plus élevées. Lorsque la requête moyenne d’un utilisateur entraîne des centaines d’appels à la base de données, c’est l’appel le plus lent que l’utilisateur ressent. Nous avons constaté que le principal défi d’un service Python à cette échelle consiste à gérer ces latences de queue.
Asyncio aide Python à exécuter simultanément les charges liées aux E/S, mais ne permet ni de contourner le GIL de Python ni d’assurer le parallélisme CPU. Outre le proxy de requêtes à forte intensité d’E/S, Habitat assume de nombreuses responsabilités et tâches d’arrière-plan gourmandes en CPU : routage, compression, chiffrement, calcul de sommes de contrôle, vérification de l’état des services en aval, duplication et couverture des requêtes.
Avec autant de charges gourmandes en CPU et de tâches d’arrière-plan dans notre service, le retard de planification d’asyncio peut facilement dominer la latence de queue des requêtes. Avant les réglages précédant le lancement initial, les traces des requêtes ayant une latence p99 ou supérieure montraient que, malgré la réponse rapide du stockage en aval, les requêtes restaient souvent bloquées en attendant la replanification de la coroutine chargée d’analyser la réponse.
Figure 03 · Suivi du retard d’asyncio
La concurrence n’est pas le parallélisme CPU
Asyncio de Python permet de traiter les requêtes simultanément, mais une seule requête s’exécute à la fois sur le thread CPU. Cela affecte fortement la latence des requêtes lorsqu’une importante charge CPU doit être traitée.
Faible charge CPU
Étapes Python brèves ; les attentes d’E/S se chevauchentCharge CPU élevée
Les longues étapes Python font attendre les réponses prêtesPour les services Python d’OpenAI, nous estimons qu’en plus des métriques standard d’utilisation et de saturation de la mémoire, du CPU, du réseau et du disque, il est essentiel de surveiller la boucle asyncio et sa charge, puis d’ajuster le système en conséquence.
En planifiant périodiquement des tâches d’arrière-plan et en enregistrant l’écart entre les heures d’exécution attendue et réelle, nous pouvons mesurer empiriquement et en temps réel le retard de planification de la boucle d’événements. Avec une utilisation élevée et de nombreuses tâches coûteuses, même un nombre modeste de requêtes simultanées par processus suffit à produire une forte gigue de planification, jusqu’à plusieurs centaines de millisecondes et, dans certains cas limites, plusieurs secondes.
Nous limitons donc chaque processus à un petit nombre de requêtes simultanées et augmentons massivement le nombre de processus de travail Python.
Lors du lancement initial, le profilage CPU du service en production nous a permis d’identifier une cause fondamentale du retard élevé d’asyncio, et donc des fortes latences de queue : l’analyse JSON périodique de nos configurations de fonctionnalités via Statsig, un outil qui gère ces fonctionnalités et permet notamment d’exécuter des tests A/B.
Par défaut, Statsig était configuré pour rechercher chaque minute, sans gigue, des configurations actualisées, lesquelles incluaient toutes les règles de production de tous les services. Par ailleurs, une décision d’architecture avait fixé jusqu’à huit processus Python par pod afin d’accroître l’utilisation du CPU et de réduire les latences. Ainsi, chaque minute, tous les processus de travail d’un pod finissaient simultanément par interrompre le traitement des requêtes en cours pour consacrer leurs cycles CPU à l’analyse d’un énorme fichier de configuration.
Une fois le problème identifié grâce au profilage CPU, la correction était simple : déployer une configuration ciblée plus petite, allonger l’intervalle d’actualisation et ajouter de la gigue à ce type de tâches d’arrière-plan.
Pour maintenir un faible retard d’asyncio, il est également essentiel de bien équilibrer les requêtes entre les processus serveur ; sans réglage, le regroupement des connexions peut aussi aller à l’encontre de cet objectif.
Avec un pool de connexions côté client, un même processus client effectuant de nombreuses requêtes simultanées peut n’établir qu’une poignée de connexions serveur et, par conséquent, envoyer toute sa charge à une poignée de processus seulement. Avant d’ajuster notre équilibrage de charge, l’utilisation de notre service variait fortement : certains processus de queue traitaient 5 à 10 fois plus de requêtes simultanées que la moyenne.
Nous l’avons découvert fortuitement lors d’un incident : malgré l’arrêt du client qui surchargeait une partie du service, un sous-ensemble de processus est resté dégradé bien après la rafale de trafic. Nous avons même constaté une dégradation incontrôlée de ces processus, qui recevaient toujours plus de requêtes jusqu’à leur redémarrage. Une fois un pod surchargé, un comportement dirigeait davantage de trafic vers celui-ci. Certains collègues connaissaient bien cette catégorie de défaillances grâce à leurs expériences antérieures : la défaillance métastable(ouverture dans une nouvelle fenêtre).
Nous soupçonnions le pool de connexions et avons testé cette hypothèse en plafonnant la durée maximale de réutilisation des connexions ; cela a effectivement limité la dégradation et confirmé l’orientation de notre enquête. Une enquête approfondie a révélé que le TCPConnector d’aiohttp pour Python utilise par défaut la réutilisation LIFO : la connexion la plus récemment renvoyée est sélectionnée pour la requête suivante. C’est généralement un choix par défaut raisonnable : la réutilisation des connexions récentes permet aux connexions supplémentaires créées pour absorber les rafales d’expirer après leur période d’inactivité, ce qui réduit le coût de leur maintien. Dans notre cas, cela a créé une défaillance métastable. Pendant une rafale, les requêtes envoyées aux serveurs surchargés plus lents renvoyaient leurs connexions au pool plus tard ; elles étaient donc plus souvent choisies pour les requêtes suivantes, concentrant progressivement davantage de trafic sur les pods déjà en difficulté. Modifier le pool de connexions pour utiliser la réutilisation FIFO a rompu cette boucle de rétroaction et même réduit la variance des requêtes en régime stable.
Figure 04A · Regroupement des connexions côté client
LIFO renvoie le nouveau travail au processus lent
Après une rafale de requêtes, les serveurs plus lents renvoient leurs connexions au pool en dernier. LIFO tend à concentrer davantage de travail sur ces mêmes serveurs plus lents.
Une rafale initiale atteint A, B et le processus C, plus lent.
Figure 04B · Regroupement des connexions côté client
FIFO rompt la boucle de rétroaction de réutilisation des connexions
FIFO conserve davantage de connexions actives après une rafale, mais répartit équitablement les charges entre tous les serveurs.
Une rafale initiale atteint A, B et le processus C, plus lent.
Aujourd’hui, nous nous appuyons principalement sur Istio et Envoy pour assurer le regroupement des connexions et de meilleures stratégies d’équilibrage tenant compte de la charge des serveurs dans toute l’infrastructure OpenAI, ce qui évite entièrement ce problème.
Le réglage visant un faible retard d’asyncio et le grand nombre de processus Python ont un effet secondaire : il devient très facile de submerger les dépendances en aval avec un nombre considérable de connexions, phénomène appelé « troupeau affolé ».
Un déploiement quotidien ordinaire, s’il n’est pas réglé pour être lent, peut provoquer une forte agitation du CPU due au renouvellement des connexions. Une fuite de connexions peut également mettre le réseau hors service en saturant la passerelle NAT. Ces problèmes ne sont pas rares dans d’autres services, mais leur seuil de déclenchement baisse fortement avec un nombre de processus supérieur d’un ordre de grandeur, qui sature souvent des ressources réseau que les clients ne prévoient pas de devoir gérer en régime stable sur la seule base du débit.
Nous nous appuyons également sur Envoy pour maximiser la concentration de nos connexions. Nous l’utilisons pour faire passer les connexions HTTP/1 de Python à HTTP/2 afin de bénéficier du multiplexage, puis pour regrouper ces connexions et prolonger leur durée de vie. Envoy nous offre aussi un emplacement central pour mettre en œuvre des limites de débit et des disjoncteurs, qui seraient moins efficaces dans chaque processus Python autonome.
Figure 05 · Concentration des connexions
Les mêmes requêtes, moins de connexions
Le regroupement des connexions et le multiplexage des connexions HTTP/2 réduisent la charge de connexion sur les services en aval.
Si nous avons pu faire évoluer Python aussi loin, c’est notamment grâce à l’API restreinte d’Habitat, qui rend le coût des requêtes prévisible. Au lieu de permettre aux clients de créer des requêtes SQL arbitraires susceptibles de provoquer l’analyse de grandes tables ou des jointures entre de nombreuses tables, Habitat propose une API NoSQL simple. L’absence d’une API puissante est un compromis explicite de la conception d’Habitat.
Nous cherchons à optimiser les requêtes simples, prévisibles et à charge constante. D’après notre expérience, ces systèmes sont bien plus faciles à mettre à l’échelle et difficiles à mal concevoir ou à détourner de leur usage. Les requêtes dont la dispersion est imprévisible sont dangereuses sur le plan opérationnel : elles compliquent l’isolation et l’équilibrage de charge, et créent des seuils de latence difficiles à gérer à grande échelle pour le service comme pour ses clients.
Avant notre passage à Habitat et Azure Cosmos DB, la plupart des données en ligne d’OpenAI étaient stockées dans Postgres. À l’époque, il était facile d’examiner toutes les modifications des requêtes et du schéma afin de vérifier leur bon comportement et leur exécution sur des données indexées avant leur mise en production. Avec la croissance de l’équipe et des produits, cette tâche est rapidement devenue ingérable et a fréquemment provoqué des pannes lorsqu’une seule nouvelle requête coûteuse sur un chemin critique mettait la base de données hors service.
Le problème tient ici au déséquilibre des coûts : il est facile et peu coûteux d’écrire des requêtes SQL dont l’exécution est difficile et coûteuse. Dans Habitat, nous évitons ce problème et rendons les requêtes coûteuses extrêmement évidentes côté client. Aucune requête non bornée ne peut surcharger Habitat. Les jointures complexes et les parcours de graphe imposent aux équipes produit une partie du travail lourd, ce qui favorise globalement des conceptions plus efficaces.
Habitat propose une API NoSQL modélisée autour de types d’objets et d’arêtes définis par le client, inspirée de TAO(ouverture dans une nouvelle fenêtre). Les clients prédéfinissent les objets, les arêtes et leurs relations, mais pas le contenu de chaque type. Les relations obtenues ressemblent à un graphe, mais Habitat ne prend pas en charge les requêtes classiques de parcours de graphe, sauf pour interroger les arêtes directes d’un objet donné.
Nous partitionnons ce graphe afin que chaque objet et ses arêtes soient colocalisés dans une partition de stockage, mais nous ne cherchons pas délibérément, au niveau de la base de données, à colocaliser les objets et les objets distants vers lesquels pointent leurs arêtes. Le modèle se partitionne ainsi facilement pour une mise à l’échelle horizontale, mais les parcours de graphe sont inefficaces, car chaque saut entre objets peut nécessiter des lectures dans deux comptes Azure Cosmos DB entièrement distincts, stockés dans des régions différentes.
Pour les clients ayant des besoins d’interrogation plus complexes, nous proposons une vue secondaire hors ligne d’Habitat, accessible via Rockset. Nous utilisons la capture des données modifiées (CDC) pour diffuser presque en temps réel les modifications du stockage en ligne vers des instances Rockset isolées. Chaque équipe cliente doit dimensionner sa propre instance Rockset selon ses besoins de requêtes complexes.
Ce provisionnement de Rockset crée des contraintes supplémentaires pour nos clients, mais nous pensons qu’il s’agit actuellement du bon compromis : faire des requêtes simples le choix par défaut, tout en offrant une solution de repli à ceux qui ont besoin de requêtes complexes. Cette conception isole notre stockage en ligne des charges analytiques et de recherche exigeant beaucoup de lectures.
Reporter d’un an la réécriture de Python nous a permis de nous concentrer sur des difficultés plus urgentes et plus importantes pendant notre hypercroissance. La plateforme gagnant en maturité et notre croissance continuant de s’accélérer, alors que ce service était le deuxième d’OpenAI en nombre de cœurs et le quatrième par son empreinte Envoy, il était enfin temps de dépasser Python. À son apogée, Python nous a permis de traiter plus de 20 millions de requêtes par seconde.
Au deuxième trimestre 2026, avec seulement deux ingénieurs, Codex et GPT‑5.5, nous avons pu réécrire l’intégralité du service en Rust. Ce nouveau service Rust traite désormais 95 % de nos requêtes de production ; nous abandonnerons complètement Python dans les semaines à venir. Nos données montrent que le service Rust est six fois plus efficace en CPU et quinze fois plus efficace en mémoire que la version Python, avec des latences moyennes et de queue nettement inférieures. Nous publierons d’autres enseignements dans un prochain article.
Le service Python, désormais en Rust, n’est qu’une facette d’Habitat. Dans la deuxième partie de cette série consacrée à la mise à l’échelle rapide de notre stockage en ligne pour servir plus d’un milliard d’utilisateurs de ChatGPT, nous présenterons la couche de stockage et expliquerons comment Habitat traite plus de 500 pétaoctets et plus de 70 millions de requêtes par seconde.
Si vous souhaitez travailler sur des systèmes OLTP à une échelle de pointe et que ce type d’ingénierie vous intéresse, découvrez ce poste à pourvoir dans notre équipe.


