Tous les produits d'OpenAI nécessitent un accès rapide et fiable aux données, assuré par notre plateforme de stockage, Habitat. Elle doit répondre en quelques millisecondes à plus de 1 milliard d'utilisateurs répartis dans 40 régions géographiques. Notre infrastructure a relevé un défi extraordinaire : soutenir une croissance explosive et de nouveaux cas d'usage tout en continuant d'améliorer la fiabilité, les performances et la vélocité des développeurs produits. Relever ce défi a exigé de repenser la conception et l'exploitation de notre plateforme de stockage en ligne. Nous avons constamment évalué les compromis, en suivant le schéma suivant : d'abord devenir suffisamment fiable pour du trafic produit critique, ensuite suffisamment rapide pour des utilisateurs mondiaux, et enfin, opérer habilement à très grande échelle.
Cette série en deux parties explique comment nous avons développé Habitat, d'une simple bibliothèque cliente et d'une base de données à une plateforme de stockage distribuée à l'échelle mondiale servant plus de 500 pétaoctets de données. La partie I portait sur l'évolution de Habitat, d'un client Python à un service. Elle couvrait également les techniques qui nous ont permis de pousser un service basé sur Python bien au-delà de ce que beaucoup considéreraient comme raisonnable.
Dans cet article, la partie II, nous détaillons comment nous avons fait évoluer le composant de stockage sous-jacent : Azure Cosmos DB. Grâce à une étroite collaboration avec l'équipe Azure, nous sommes parvenus à une approche de multi-tenantisme viable via des conteneurs co-gérés, avec d'excellentes performances de lecture à une échelle mondiale et massive.
Figure 01 · Qu'est-ce que Habitat ?
Plateforme de stockage en ligne
Habitat est la plateforme de stockage en ligne que nous avons conçue pour que les produits OpenAI puissent accéder rapidement et de manière fiable aux informations nécessaires.
- Requête
- Réponse
- Modifications (CDC)
Clients
Plateforme de stockage en ligne
Ressources de stockage
- ChatGPT
- API
- Codex
- Services internes
- Et plus encore
Habitat
- Mise en cacheCaches
- Politiques ACLAutorisation
- Placement & résidence des donnéesRésidence des données
- ChiffrementSécurité des données
- IsolationMulti-tenantisme
- Limitation de débitMise en forme des requêtes
- RoutageRecherche de schéma · Résidence des données
- Azure Cosmos DBStockage en ligne
- NanobaseStockage en ligne
- ValkeyCaches
- Stockage blobRessources de stockage
- Databricks
- Rockset
- Kafka
- Et plus encore
Rendre le multi-tenant fiable à grande échelle
À mesure que les entreprises grandissent, les équipes d’infrastructure évoluent souvent de systèmes mono-tenant vers des systèmes multi-tenant. Le mono-tenant, où chaque équipe de surface produit maintient son propre système, réduit le rayon d’impact et offre des limites de responsabilité et une isolation des charges de travail faciles à gérer. Mais à mesure que les équipes et les surfaces produits se multiplient, cela devient coûteux à exploiter. Le mono-tenant rend chaque équipe produit responsable de la planification de capacité et se traduit souvent par un sur-provisionnement significatif.
À l’inverse, le multi-tenant confie le contrôle à une équipe de base de données centralisée, disposant de meilleures connaissances et de plus d’expérience pour le provisionnement des ressources. Mais le multi-tenant crée ses propres problèmes, comme les problèmes de voisin bruyant (noisy neighbor), où un locataire consomme une part anormalement importante des ressources au détriment des autres locataires.
Dans Habitat, nous avons dû peser tous ces compromis pour concevoir un système de stockage fiable et efficace à grande échelle. Aujourd’hui, Habitat est une couche de base de données multi-tenant pour de nombreux produits OpenAI, mais nous avons parcouru un chemin circulaire : de l’utilisation d’un conteneur Azure Cosmos DB multi-tenant unique au provisionnement de conteneurs dorsaux mono-tenant, puis au retour à un conteneur multi-tenant par défaut. Nous en sommes encore au tout début de notre histoire multi-tenant sur Habitat : nous prenons en charge un large éventail de cas d’usage, mais nous avons pu différer les investissements dans ce domaine en tirant parti des primitives intégrées d’Azure Cosmos DB autant que possible.
Un primer simplifié sur les conteneurs Azure Cosmos DB
Avant de passer en revue notre parcours avec Azure Cosmos DB, quelques éléments de contexte de base :
Un compte Azure Cosmos DB définit un ensemble de conteneurs et une stratégie de réplication
Chaque conteneur est provisionné pour un certain débit, exprimé en unités de requête par seconde (RU/s)
Les données de chaque conteneur sont stockées selon un schéma classique de sharding par clé de partition logique spécifiée, avec des limites documentées sur le stockage et le débit par clé de partition logique
Azure Cosmos DB gère le sharding des clés de partition logique sur un nombre plus réduit de partitions physiques dorsales, en les scindant en plusieurs partitions physiques lorsque le stockage dépasse un seuil ou lorsque le conteneur nécessite plus de partitions physiques pour soutenir le débit provisionné
Phase I : S’appuyer sur les primitives d’Azure Cosmos DB
Nous laissions initialement passer toute charge client, en nous appuyant sur la limite déclarée de 10K RU/s par clé de partition logique pour fournir une contre-pression. À nos débuts, nous utilisions également un seul conteneur Azure Cosmos DB par défaut dans chaque compte. Cela fonctionnait assez bien, mais s’est avéré problématique pour plusieurs raisons :
Limites des clés de partition logique
En raison de choix d’API précoces, nous avons exposé directement aux clients la sélection des clés de partition logique. Par conséquent, nous avons fréquemment des collisions sur les clés de partition couramment utilisées, telles que user_id ou workspace_id, pour différents cas d’usage produits. Parce que ces cas d’usage partageaient les limites Azure Cosmos DB sur les mêmes clés de partition logique, cette conception contraignait le débit global pour un utilisateur donné.
Un problème courant que nous rencontrions lors du passage à l’échelle de Postgres avant de migrer vers Habitat et Azure Cosmos DB était la possibilité pour de nombreuses requêtes provenant d’une organisation très sollicitée ou d’une clé très sollicitée — un locataire générant une charge disproportionnée — de faire tomber toute la base de données. Un effort collectif important a été consacré à mettre ces clés en cache et à intégrer des baux dans les échecs de cache afin que la charge résultante des échecs de cache n’écrase pas la base de données sous-jacente.
Figure 02a · Limites des clés de partition logique
Des clients différents, la même clé de partition
Voisins bruyants
Bien que les limites documentées de taille de données et de débit s’appliquent aux clés de partition logique, c’est en réalité au niveau de la partition physique que les limites sont appliquées. En pratique, cela signifie que les partitions logiques ne peuvent pas atteindre 10,000 RU/s. Elles partagent plutôt les limites de la partition physique avec toutes les autres clés de partition logique colocalisées sur cette partition physique.
Cela permettait à des voisins bruyants d’impacter de nombreuses clés de partition logique sans rapport, des charges de travail opérant sur des données de fonctionnalités produits de niche perturbant les flux essentiels de connexion et d’inscription.
Figure 02b · Voisins bruyants
Des utilisateurs différents, la même partition physique
Partitions faibles
La limite de débit d’une partition physique donnée est également variable, dépendante du débit total attribué au conteneur Azure Cosmos DB. Le débit provisionné du conteneur est réparti à peu près également entre les partitions physiques de ce conteneur.
À mesure que le volume de données augmente, les partitions physiques se scindent, ce qui augmente le nombre total de partitions tandis que le débit total provisionné reste inchangé. Il en résulte que la limite de débit effective par partition diminue à mesure que la taille des données augmente.
Pour nos cas d'utilisation les plus gourmands en stockage, de type blob, cela crée un mode de défaillance dans lequel la seule croissance du stockage réduit la capacité de débit des charges de travail actives, même lorsque les taux de requêtes et l'utilisation restent stables.
Figure 02C · Partitions faibles
La limite tombe en dessous de la demande
Une partition se scinde lorsqu'elle atteint sa limite de stockage. Chaque partition enfant reçoit la moitié de la capacité de débit.
Avant la scission : capacité de 10K RU/s
Après la scission : capacité de 5K RU/s × 2 partitions
Phase II : Tentative d'isolation de la charge de travail et de mono-locataire
Des conteneurs séparés aident à résoudre partiellement ces limites de clé de partition et ces voisins bruyants. En utilisant des conteneurs séparés, chacun peut se voir attribuer ses propres partitions physiques indépendantes et seules les charges de travail variables sur ce conteneur lui-même pouvaient être des voisins bruyants ou se chevaucher dans l'espace des clés de partition. L'inconvénient est qu'il existe une surcharge non négligeable dans le provisionnement de nouveaux conteneurs—chaque conteneur nécessite une planification de capacité individualisée—nous voulons donc créer des conteneurs uniquement à une frontière de service ou de cas d'utilisation qui a du sens.
Nous avons commencé à utiliser des conteneurs Azure Cosmos DB séparés pour les nouveaux cas d'utilisation les plus critiques nécessitant une isolation forte. Combiné à un script de gestion des partitions faibles en synchronisant le débit total provisionné pour chaque conteneur avec son nombre de partitions physiques, nous avons pour l'essentiel évité tous les problèmes auxquels nous étions confrontés.
Cependant, puisque nous avions commencé avec un conteneur par défaut, de nombreux cas d'utilisation critiques hérités étaient malheureusement bloqués là sans migration de données. Pour protéger les services critiques qui dépendaient encore des données du conteneur par défaut partagé, nous conseillions souvent aux équipes de créer des conteneurs séparés pour les nouveaux services ou les fonctionnalités majeures de produit. À mesure que nous grandissions, la pression sur le conteneur par défaut augmentait et les incidents en production augmentaient également. Nous avons commencé à provisionner des conteneurs pour tout nouveau service ou grande fonctionnalité de produit juste pour protéger le conteneur par défaut de charge supplémentaire.
Cela nous a sauvés de nombreuses pannes à l'échelle du site, en limitant le rayon d'impact aux pannes de dégradation de fonctionnalité ou de service. Cependant, la surcharge du provisionnement de nouveaux conteneurs finit par nous rattraper :
Absence de croissance agrégée stable
Dans l'ancien conteneur par défaut partagé, un nouveau cas d'utilisation particulier passant de 0 QPS à 1 million de QPS était peu susceptible de modifier significativement l'utilisation agrégée totale. Sur un conteneur isolé en revanche, un nouveau cas d'utilisation passant de 0 à 1 million de QPS pouvait représenter 1,000x plus de charge que la ligne de base attribuée uniquement au dogfooding interne.
Avec des conteneurs dédiés, les équipes devaient désormais penser à pré-provisionner les conteneurs au niveau de débit attendu pour leurs lancements. Les équipes produit livraient souvent leurs fonctionnalités en production et oubliaient de pré-monter en charge, et nous nous retrouvions dans des salles SEV le jour du lancement à tenter de monter en charge les conteneurs à la dernière minute.
Il s'agit d'un avantage fondamental du mélange de charges de travail des systèmes multi-locataires que nous perdons au profit d'une meilleure isolation avec cette stratégie de conteneurs.
Figure 03 · Absence de croissance agrégée stable
La même charge de travail, une marge différente
1K RU/s pendant le test de charge → 1M RU/s au lancement
Conteneur partagé
Capacité de 100M RU/s · 80% utilisée
Test de charge
1K20M=0.005%
de marge
Lancement
1M20M=5%
de marge
Nouveau conteneur dédié
Capacité de 10K RU/s · aucune autre charge de travail
Test de charge
1K10K=10%
de marge
Lancement
1M10K=10,000%
de marge
Planification de la capacité
Au fil du temps, surveiller l'utilisation des conteneurs dédiés exigeait également une connaissance approfondie du produit pour savoir quand le trafic allait se déplacer ou croître.
Nous pouvons fournir un script de plan de contrôle de base pour augmenter le débit provisionné de chaque conteneur en fonction des tendances de croissance, mais sans croissance globale stable, une mise à l'échelle correcte a fini par impliquer pratiquement chaque équipe produit d'OpenAI dans la planification de la capacité.
Cela ne passait pas à l'échelle sur le plan organisationnel, avec une grande variance dans le niveau de rigueur que l'équipe propriétaire de chaque service consacrait au provisionnement de son Azure Cosmos DB.
Phase III : Adoption de la multi-location par défaut, avec capacité de déplacement des données
À mesure que l'organisation grandissait, ces problèmes opérationnels devenaient difficiles à faire évoluer. L'absence d'une équipe centrale unique dédiée à la planification a conduit de nombreuses équipes à mal planifier. Par conséquent, nous avons fini par revenir vers une solution multi-tenant et avons travaillé en étroite collaboration avec l'équipe Azure pour concevoir des correctifs à nos problèmes initiaux de multi-location, avec une conception à location partagée :
Limitation des clés chaudes
Limiter d'abord les clés les plus sollicitées empêche les voisins bruyants de consommer tout le débit disponible pour une partition physique donnée. Cela réduit l'effet de voisin bruyant, où une seule clé chaude peut avoir un rayon d'impact à l'échelle du site.
Qualité de service (QoS)
La QoS par hiérarchisation des requêtes empêche également les charges de travail hors ligne, telles que les insertions en masse ou les extractions de données, d'utiliser tout le débit d'une partition physique donnée. En attribuant à ces requêtes une priorité plus faible, elles sont limitées en premier, ce qui évite de limiter les requêtes interactives de priorité plus élevée qui servent les utilisateurs finaux.
Mode serverless
Azure Cosmos DB disposait déjà d'un mode serverless qui aurait résolu notre problème de partitions faibles avec le mode provisionné. Cependant, il y avait un inconvénient majeur : il s'adaptait uniquement en fonction de l'utilisation du stockage et limitait l'utilisation de RU à 5K RU/s par partition physique. Cela aurait résolu notre problème de partitions faibles, mais nous aurait empêchés de monter en puissance pour les cas d'utilisation gourmands en RU.
Un nouveau mode serverless amélioré, conçu pour nous par l'équipe Azure Cosmos DB, a permis à chaque partition physique de monter jusqu'à 10K RU/s et a introduit des fractionnements basés sur l'utilisation des RU en plus des fractionnements basés sur l'utilisation du stockage, ce qui nous a permis d'arrêter de surveiller et d'augmenter manuellement les conteneurs.
Grâce à ces changements, nous avons pu revenir à l'utilisation d'un conteneur partagé, ce qui a considérablement simplifié la nécessité pour les équipes produit d'effectuer une planification de capacité initiale ou continue, tout en préservant l'isolement entre les charges de travail. Nous n'avons eu aucun incident de provisionnement ni de voisin bruyant depuis des mois, malgré une accélération du développement produit.
Une meilleure fiabilité en tant que services cogérés
Lorsque les entreprises atteignent l'échelle d'OpenAI, les services gérés deviennent moins « gérés ». De nombreuses charges de travail deviennent des cas hors normes pour l'infrastructure du fournisseur cloud. Alors qu'OpenAI est devenu l'un des plus grands utilisateurs d'Azure Cosmos DB, son exploitation réussie a nécessité un modèle différent : des tests partagés, des tableaux de bord partagés, des canaux d'ingénierie directs et une propriété conjointe des modes de défaillance qui n'apparaissent qu'à notre échelle. Aujourd'hui, notre plus grand compte Azure Cosmos DB stocke 10s de pétaoctets de données et sert plus de 20M QPS. À notre connaissance, nous sommes la seule entreprise à avoir déployé Azure Cosmos DB à cette échelle.
Fin 2024, une panne régionale a montré pourquoi cette collaboration était nécessaire. À l'époque, une coupure de courant a frappé la région Azure principale de l'un de nos plus grands comptes Azure Cosmos DB. Lors de l'exercice de basculement qui en a résulté, nous avons réalisé que nos plus grands comptes mettraient plusieurs heures à basculer vers une région secondaire, ce que ni notre équipe ni l'équipe Azure Cosmos DB n'avaient testé à notre échelle.
Environ un mois plus tard, lors du retour de ces comptes vers la région d'origine, nous avons déclenché une autre panne sérieuse. Une ruée de connexions vers la région d'origine a submermé la passerelle Azure Cosmos DB de cette région. Une particularité dans la gestion des basculements par le client Azure Cosmos DB en mode passerelle a poussé tous les clients à établir immédiatement des connexions et à envoyer des requêtes vers la région d'origine, alors que seule une partie des partitions physiques était revenue dans cette région.
Lors de chaque panne, OpenAI et Azure ont tous deux appliqué leurs processus de gestion d'incident, identifiant un certain nombre de causes profondes et déployant des correctifs. Mais ces pannes étaient les symptômes d'un problème systémique plus vaste dans la manière dont notre équipe et l'équipe Azure Cosmos DB opéraient et testaient. Au début, l'utilisation de services gérés nous permettait d'avancer rapidement et de nous concentrer sur les objectifs commerciaux. À grande échelle, les services gérés deviennent moins gérés et nécessitent des intégrations approfondies avec le fournisseur de services à tous les niveaux, de la conception aux opérations.
Aujourd'hui, les équipes d'ingénierie de Habitat et d'Azure surveillent conjointement chaque couche de Habitat, de nos couches de service jusqu'aux couches de stockage physique d'Azure Cosmos DB. Nous opérons et testons les modifications sur un compte Azure Cosmos DB de taille de production mais non productif. Nous testons et itérons ensemble sur les SDK et déterminons les meilleures pratiques pour dimensionner et adapter nos comptes Azure Cosmos DB. Nous avons un accès d'ingénierie direct les uns aux autres via Teams et avons même exposé des moyens de nous envoyer directement des alertes de pagination sans passer par les niveaux de support.
Une quantité substantielle de travail conjoint a été réalisée au cours de l'année écoulée pour améliorer l'évolutivité du plan de contrôle et de la passerelle d'Azure Cosmos DB, en ajoutant une protection contre la charge et la mise en cache de diverses métadonnées Azure Cosmos DB dans leur passerelle pour protéger la partition de métadonnées Azure Cosmos DB, en réduisant la pression de notre flotte de processus Python habitat-service grâce au regroupement de connexions et à une meilleure réutilisation des connexions, en colocalisant les charges de travail lorsque c'est possible, et en isolant les charges de travail critiques des charges de travail hors ligne via différents VPC.
Ce niveau de co-ingénierie a été essentiel à la stabilité de notre Azure Cosmos DB au cours de l'année écoulée. Tout système suffisamment vaste ou critique nécessite ce type de travail d'équipe : abattre les cloisons de propriété et opérer au-delà des frontières organisationnelles.
Améliorer les performances à l'échelle mondiale
Comme mentionné dans la Partie I, notre philosophie fondamentale est : make it work, make it fast, make it cheap. D'abord la fiabilité, puis la performance, puis l'efficacité. Habitat était devenu une infrastructure critique pour OpenAI, la première priorité était donc de le rendre fiable. Alors que le système se stabilisait en 2025 et qu'OpenAI s'étendait à l'échelle mondiale, la latence est devenue la contrainte suivante pour les utilisateurs et les équipes produit.
Les capacités de calcul et de stockage étaient souvent situées dans des régions différentes, rendant inévitables les requêtes inter-régions.
Figure 04 · Améliorer la performance à l'échelle mondiale
Trafic mondial
Les capacités de calcul et de stockage étaient souvent situées dans des régions différentes, rendant inévitables les requêtes inter-régions.
Les emplacements géographiques du calcul et du stockage ne sont fournis qu'à titre illustratif.
La latence variait considérablement selon l'endroit où les données étaient stockées et où la requête était servie. Pour rendre les requêtes Habitat performantes, les développeurs produit s'appuyaient sur une mise en cache côté client supplémentaire, le regroupement de requêtes, la minimisation des lectures et écritures en base de données et le déplacement du travail hors du chemin critique autant que possible. Si cela a conduit à des conceptions de produits intéressantes, plus résilientes aux problèmes de base de données, cela a aussi rendu les produits plus complexes et ralenti l'itération produit : un échec par rapport à l'objectif de Habitat d'encourager un développement produit rapide sans avoir besoin de comprendre la base de données.
Cela a été un axe majeur pour nous en 2026, et nous avons développé une stratégie en couches pour optimiser les performances de lecture, qui représentent plus de 90% des requêtes de Habitat :
Stocker les données près des utilisateurs lorsque la capacité le permet
Nous disposons de plusieurs comptes Azure Cosmos DB dans différentes régions Azure à travers le monde pour les utilisateurs ayant des exigences de résidence des données. Même pour les utilisateurs sans exigences strictes de résidence des données, colocaliser les données près des utilisateurs améliore également leur expérience grâce à une latence plus faible.
En pratique, nous ne pouvons pas y parvenir largement en dehors des cas d'usage de résidence des données, car le placement est contraint par la capacité physique du cloud. En raison de la taille de nos jeux de données, de nombreuses régions ne peuvent pas accueillir l'empreinte de stockage massive requise pour leur part proportionnelle du trafic utilisateur.
Dans les régions disposant d'une capacité de centre de données, d'énergie ou de refroidissement limitée, nous sommes parfois confrontés à un compromis direct : réserver de la capacité pour un stockage colocalisé et réduire la latence de Habitat, ou utiliser cette capacité pour du calcul de service supplémentaire et acheminer les requêtes de stockage plus loin.
Par conséquent, les décisions de placement sont souvent un compromis produit entre : A) l'optimisation de la latence pour différents produits et sous-ensembles d'utilisateurs, et B) la capacité brute de stockage et de calcul.
Répliquer largement uniquement les plus petits ensembles de données les plus sensibles à la latence
Pour les charges de travail les plus critiques en matière de latence, comme les métadonnées d'authentification nécessaires à chaque requête, nous réduisons le RTT entre le calcul et Habitat en utilisant des comptes Azure Cosmos DB fortement répliqués avec de nombreuses régions secondaires. Certains comptes se répliquent vers les 40 régions Azure où nous servons du trafic ; d'autres se répliquent vers au moins une région par continent.
Nous avons dû intégrer à Habitat une couche transparente de déplacement et de routage des données plus tôt que nous ne l'aurions souhaité, afin de pouvoir déplacer des jeux de données entre des comptes ayant des topologies de réplication différentes.
Cependant, cette approche est également in fine limitée par la capacité et donc restreinte à quelques jeux de données sélectionnés à la main.
S'appuyer sur la mise en cache intégrée pour tout le reste
Les charges de travail de Habitat sont majoritairement dominées par les lectures, les applications récupérant constamment des paramètres, des états et d'autres propriétés ; une part disproportionnée du trafic est attribuable à un ensemble de travail relativement restreint de types fréquemment consultés. Cela rend le système particulièrement bien adapté à la mise en cache distribuée. Bien que la mise en cache améliore les performances dans une infrastructure mono-région, les améliorations se cumulent lorsque l'on travaille entre régions, où elle peut être utilisée pour « masquer » la latence inter-régions.
Nous avons intégré une couche de mise en cache distribuée lookaside dans Habitat. Elle stocke une copie des objets et arêtes récemment consultés sur des serveurs Valkey placés dans notre infrastructure à proximité de l'endroit où les requêtes des utilisateurs sont traitées. Typiquement, ce type de caches utilise un TTL qui fait un compromis entre taux de succès et fraîcheur. Des TTL courts réduisent la péremption mais atténuent l'impact positif de la mise en cache, car les éléments expirent rapidement du cache et doivent être récupérés plus fréquemment. Des TTL plus longs améliorent les taux de succès, mais servent des données périmées pendant de longues périodes, entraînant de mauvaises expériences utilisateur et un comportement imprévisible des produits.
Une autre approche consiste à exécuter des opérations d'invalidation explicites lorsque l'application effectue des opérations d'écriture sur les mêmes données sous-jacentes. Cela améliore considérablement la fraîcheur des données mises en cache, mais peut être difficile à mettre en œuvre correctement. Cela reste également soumis à un certain nombre de cas limites, comme les échecs partiels, qui laissent le cache périmé. Les déploiements multi-régions aggravent ces problèmes, avec des réseaux inter-régions moins fiables et le cache en concurrence avec la réplication du stockage sous-jacent. Compléter cela avec des TTL borne la péremption maximale, mais souffre toujours du même compromis de TTL évoqué ci-dessus.
Nous avons adopté une approche d'invalidation plus avancée, en suivant les traces de systèmes comme TAO de Meta. Le cache intégré de Habitat repose sur le change data capture (CDC) via le flux de modification d'Azure Cosmos DB pour propager les invalidations mondialement en quelques secondes, atteignant des taux de succès élevés tout en maintenant une faible péremption. Un service de notre infrastructure consomme ce flux de modifications et exécute les invalidations dans les caches locaux lorsque les données sous-jacentes sont mises à jour. Cela provoque un échec lors de la prochaine lecture des données, et le cache est rempli avec la dernière version des données.
Réduire la surcharge du proxy avec Rust
Nous avons également considérablement réduit la latence en supprimant la surcharge dans le service Habitat lui-même en réécrivant le code en Rust, plus performant que le Python d'origine.
Pris ensemble, ces changements ont réduit notre p50 mondial global des lectures d'environ 120 ms à environ 10 ms, soit une amélioration d'un facteur 12 depuis que nous avons commencé à nous concentrer sur les performances plus tôt cette année.
Figure 05 · Améliorer les performances à l'échelle mondiale
latence p50 des lectures
Trois changements de conception majeurs ont amélioré la latence des lectures
environ 120 ms → environ 10 ms
amélioration d'un facteur 12
- données critiques répliquées dans des géos proches
- mise en cache intra-région lancée
- réécriture en rust lancée
Bien que la mise en cache ait considérablement réduit la latence médiane, les requêtes les plus lentes nécessitent toujours la récupération de données non mises en cache depuis le stockage sous-jacent, souvent à travers des régions éloignées. À mesure que davantage de capacité devient disponible et que nous améliorons l'efficacité du stockage, nous prévoyons de relever un défi plus fondamental : placer les données plus près de l'endroit où elles seront consultées dès le départ.
Passer à l'échelle du Change Data Capture
Un CDC fiable et évolutif—la propagation des modifications de base de données vers les systèmes en aval—est une composante essentielle bien au-delà de la stratégie de mise en cache de Habitat. Des millions de modifications capturées par seconde, à travers des milliers de conteneurs et des millions de partitions de conteneurs, transitent par plus d'une centaine de sujets Kafka pour alimenter également des index secondaires à cohérence éventuelle, des pipelines de déplacement de données, des analyses hors ligne et d'autres fonctionnalités produit en temps réel. À mesure que notre infrastructure sous-jacente s'est développée à l'échelle mondiale, il est devenu de plus en plus complexe d'écouter les modifications de lignes. Si un client souhaite enregistrer un callback devant s'exécuter chaque fois que les lignes de son type sont modifiées, il doit, entre autres, d'abord déterminer quels comptes et conteneurs Cosmos contiennent ses données, quel déploiement du service CDC s'exécute dans les régions respectives en écoutant ces conteneurs, et dimensionner correctement la flotte de workers du service CDC, Kafka et son consommateur. Notre plateforme CDC vise à abstraire tout cela des développeurs produit et à fournir à la place une surface simple où les clients enregistrent une fonction qu'ils souhaitent voir appelée sur leurs lignes modifiées, et nous gérons toute la plomberie et la mise à l'échelle.
Un plan de contrôle interne garantit en continu que tous les conteneurs requis sont bien capturés, sélectionne les régions Cosmos à partir desquelles lire et distribue efficacement le travail entre des milliers de workers répartis sur plusieurs régions géographiques. L'utilisation de régions répliques et de lectures de flux de priorité inférieure protège le trafic Habitat sensible à la latence des charges de travail de capture en arrière-plan. Les plantages de workers, les problèmes de conteneurs, les erreurs de lease-store, les configurations invalides et les défaillances de destination surviennent en permanence dans une flotte de cette taille. Nous avons renforcé le cycle de vie des workers CDC et la gestion des leases, isolé les défaillances entre les flux, et construit des garde-fous pour les modifications et déploiements de configuration. Nous utilisons des dead letter queues pour empêcher le head-of-line blocking causé par des échecs de livraison permanents. Nous tolérons les pannes régionales en basculant les lecteurs vers des régions Cosmos alternatives. À notre échelle, nous avons même dû modifier le SDK CDC d'Azure pour rendre son implémentation de checkpointing plus efficace en regroupant les checkpoints en mémoire et en ne les vidant que périodiquement.
Bien que l'utilisation interne d'Azure Cosmos DB par Habitat soit complexe, sa proposition de valeur a toujours été d'abstraire les détails de l'infrastructure des développeurs produit afin d'améliorer la vélocité du développement produit, et notre plateforme CDC ne fait pas exception.
Perspectives
Une petite équipe a pu soutenir une plateforme de stockage en croissance rapide en gardant l'API de stockage délibérément étroite, en continuant de faire monter en charge le service Python et en priorisant les défis d'ingénierie les plus urgents. Nous nous sommes d'abord concentrés sur la fiabilité du trafic produit critique, puis sur les performances de lecture pour les utilisateurs mondiaux, et enfin sur l'efficacité opérationnelle à grande échelle. Cette approche a permis une croissance rapide et de nouveaux cas d'usage tout en améliorant la fiabilité, les performances et la vélocité du développement produit.
Afin de faire passer Habitat et le stockage des conversations à l'échelle pour le prochain milliard d'utilisateurs, nous devons relever la difficulté de répliquer nos données sur notre infrastructure, à une échelle encore plus grande. Pour anticiper cela, nous construisons un système de stockage en ligne à l'échelle de l'exaoctet, développé en interne et optimisé pour les types de données volumineux de Habitat. Ce système en cours de développement combine le cœur RocksDB Cloud de Rockset avec une réplication basée sur Raft. Il implémente le même modèle de document qu'Azure Cosmos DB, mais utilise un stockage désagrégé et une réplication physique pour réduire la quantité de capacité flash nécessaire, en échange d'un qps de crête plus faible.
Si vous souhaitez travailler sur ce type d'efforts d'infrastructure de données à l'échelle des frontières technologiques, consultez nos postes ouverts.