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 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'utilisation tout en continuant à améliorer la fiabilité, les performances et la vélocité des développeurs de 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 le trafic des produits critiques, ensuite suffisamment rapide pour les utilisateurs du monde entier, et enfin, opérer avec agilité à 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 mondialement distribuée servant plus de 500 pétaoctets de données. La Partie I était consacrée à 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 pratique.
Dans cet article, la Partie II, nous expliquons comment nous avons adapté le composant de stockage sous-jacent : Azure Cosmos DB. Grâce à un partenariat de travail étroit avec l'équipe Azure, nous sommes parvenus à une approche de multi-location viable avec des conteneurs cogérés, offrant 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
- Changements (CDC)
Clients
Plateforme de stockage en ligne
Ressources de stockage
- ChatGPT
- API
- Codex
- Services internes
- Et plus encore
Habitat
- CachingCaches
- ACL policiesAuthorization
- Placement & data residencyData residency
- EncryptionData security
- IsolationMulti-tenancy
- Rate limitingRequest shaping
- RoutingSchema lookup · Data residency
- Azure Cosmos DBOnline storage
- NanobaseOnline storage
- ValkeyCaches
- Stockage BlobRessources de stockage
- Databricks
- Rockset
- Kafka
- Et bien plus
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 propriété et une isolation des charges de travail faciles à gérer. À mesure que les équipes et les surfaces produits se multiplient, cela devient coûteux à exploiter. Le mono-tenant fait de chaque équipe produit la responsable de la planification de capacité et se traduit souvent par un sur-provisionnement considérable.
À 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, 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 : d’un conteneur Azure Cosmos DB multi-tenant unique vers le provisionnement de conteneurs sous-jacents mono-tenant, puis retour à un conteneur multi-tenant par défaut. Nous en sommes encore aux tout débuts de notre histoire du 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 dans la mesure du possible.
Un aide-mémoire simplifié sur les conteneurs Azure Cosmos DB
Avant de passer en revue notre parcours avec Azure Cosmos DB, un peu de contexte de base :
Un compte Azure Cosmos DB définit un ensemble de conteneurs et une politique de réplication
Chaque conteneur est provisionné pour un certain débit en unités de requête par seconde (RU/s)
Les données de chaque conteneur sont stockées selon une approche classique de partitionnement par clé de partition logique spécifiée, avec des limites documentées de stockage et de débit par clé de partition logique
Azure Cosmos DB gère le partitionnement des clés de partition logique sur un nombre plus réduit de partitions physiques sous-jacentes, en les divisant en plusieurs partitions physiques lorsque le stockage dépasse un seuil ou lorsque le conteneur nécessite davantage 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 de 10K RU/s par clé de partition logique pour assurer une contre-pression. À nos débuts, nous n’utilisions également qu’un seul conteneur Azure Cosmos DB par défaut dans chaque compte. Cela fonctionnait suffisamment 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 exposions directement la sélection de la clé de partition logique aux clients. Par conséquent, nous rencontrons fréquemment des collisions sur des clés de partition couramment utilisées, telles que user_id ou workspace_id, à travers différents cas d’usage produits. Comme ces cas d’usage partageaient les limites d’Azure Cosmos DB sur les mêmes clés de partition logique, la conception limitait le débit global d’un utilisateur donné.
Un problème courant que nous rencontrions lors de la mise à l’échelle de Postgres avant notre passage à Habitat et Azure Cosmos DB était la possibilité pour de nombreuses requêtes d’une organisation très sollicitée ou d’une clé très sollicitée — un locataire générant une charge disproportionnellement élevée — de faire tomber toute la base de données. Des efforts collectifs importants ont été consacrés à mettre en cache ces clés 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
Différents clients, 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. Au lieu de cela, elles partagent 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 produit de niche perturbant les flux fondamentaux de connexion et d’inscription.
Figure 02b · Voisins bruyants
Différents utilisateurs, 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 distribué à peu près uniformément entre les partitions physiques de ce conteneur.
À mesure que le volume de données croît, les partitions physiques se scindent, augmentant 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 passe sous la demande
Une partition se divise lorsqu'elle atteint sa limite de stockage. Chaque partition enfant reçoit la moitié de la capacité de débit.
Avant division : capacité de 10K RU/s
Après division : capacité de 5K RU/s × 2 partitions
Phase II : Tentative d'isolation de la charge de travail et de mono-tenant
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 peuvent ê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 souhaitons 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é avec 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, comme nous avions commencé avec un conteneur par défaut, de nombreux cas d'utilisation critiques existants é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 des produits. À mesure que nous évoluions, la pression sur le conteneur par défaut a augmenté et les incidents de production ont également augmenté. Nous avons commencé à provisionner des conteneurs pour tout nouveau service ou grande fonctionnalité de produit simplement pour protéger le conteneur par défaut de toute 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 :
Manque de croissance agrégée stable
Dans l'ancien conteneur par défaut partagé, tout nouveau cas d'utilisation passant de 0 QPS à 1 million de QPS était peu susceptible de déplacer 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 pourrait représenter 1,000 fois plus de charge que la base de référence 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é-ajuster l'échelle, et nous nous retrouvions dans des salles SEV le jour du lancement en tentant d'augmenter l'échelle des conteneurs à la dernière minute.
Il s'agit d'un avantage fondamental du mélange des charges de travail des systèmes multi-tenants que nous perdons au profit d'une meilleure isolation avec cette stratégie de conteneurs.
Figure 03 · Manque 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, la surveillance de l'utilisation des conteneurs dédiés nécessitait également une connaissance intime du produit pour savoir quand le trafic allait se déplacer ou croître.
Nous pouvions 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 agrégée stable, ajuster correctement ces paramètres impliquait que pratiquement chaque équipe produit d'OpenAI participe à la planification de la capacité.
Cette approche ne passait pas à l'échelle au niveau organisationnel, avec une grande variance dans le soin que chaque équipe propriétaire apportait au provisionnement de son Azure Cosmos DB.
Phase III : Utilisation du multi-tenant 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 chargée de la planification a conduit de nombreuses équipes à mal planifier. En conséquence, nous sommes revenus 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-tenance avec une conception de mutualisation :
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 hiérarchisation des requêtes en QoS empêche de même 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 aux requêtes de ces charges de travail une priorité plus faible, elles sont limitées en premier, ce qui évite la limitation des 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 présentait un inconvénient majeur : il passait à l'échelle uniquement en fonction de l'utilisation du stockage et limitait l'utilisation des RU à 5K RU/s par partition physique. Cela aurait résolu notre problème de partitions faibles, mais nous aurait empêchés de passer à l'échelle pour des cas d'usage consommateurs de RU.
Un nouveau mode serverless amélioré, construit pour nous par l'équipe Azure Cosmos DB, permettait à chaque partition physique de monter jusqu'à 10K RU/s et introduisait des scissions basées sur l'utilisation des RU en plus des scissions basées sur l'utilisation du stockage, ce qui nous a permis d'arrêter la surveillance et l'augmentation manuelle des 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 de faire une planification de capacité initiale ou continue, tout en préservant l'isolation entre les charges de travail. Nous n'avons eu aucun incident de provisionnement ni de voisin bruyant depuis plusieurs mois, malgré le rythme accéléré du développement produit.
Une meilleure fiabilité grâce à des services co-gé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 atypiques dans 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é clairement 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 nécessiteraient plusieurs heures pour 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, en rebasculant ces comptes vers la région d'origine, nous avons déclenché une autre panne grave. Une ruée de connexions vers la région d'origine a submermé la passerelle Azure Cosmos DB de cette région. Une particularité de la gestion des basculements par le client Azure Cosmos DB en mode passerelle a fait que tous les clients ont immédiatement établi des connexions et envoyé des requêtes vers la région d'origine, alors que seule une partie des partitions physiques était revenue dans cette région.
À chaque panne, OpenAI et Azure ont appliqué leurs processus de gestion d'incident, avec un certain nombre de causes racines identifiées et de correctifs déployés. 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 vite et de nous concentrer sur les objectifs métier. À grande échelle, les services gérés deviennent moins gérés et nécessitent des intégrations profondes avec le fournisseur de services à tous les niveaux, de la conception aux opérations.
Aujourd'hui, les équipes d'ingénierie d'Habitat et d'Azure surveillent conjointement chaque couche d'Habitat, de nos couches de service jusqu'aux couches de stockage physique d'Azure Cosmos DB. Nous exploitons et testons les modifications sur un compte Azure Cosmos DB de taille production mais hors production. Nous testons et itérons ensemble sur les SDK et déterminons les bonnes pratiques pour dimensionner et faire évoluer 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 des alertes paging directement 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 pooling de connexions et à une réutilisation accrue des connexions, en colocalisant les charges de travail lorsque 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 : abolir 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, donc la première priorité était 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 les requêtes inter-régions inévitables.
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 les requêtes inter-régions inévitables.
Les emplacements géographiques du calcul et du stockage sont fournis à titre illustratif uniquement.
La latence variait considérablement selon l'endroit où les données étaient stockées et où la requête était traitée. Pour rendre les requêtes Habitat performantes, les développeurs produit s'appuyaient sur du cache côté client supplémentaire, le regroupement de requêtes, la réduction 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 donné lieu à des conceptions produit intéressantes, plus résilientes aux problèmes de base de données, cela a également rendu les produits plus complexes et ralenti l'itération produit : un échec quant à l'objectif d'Habitat d'encourager un développement produit rapide et de ne pas avoir à comprendre la base de données.
Cela a été un axe majeur pour nous en 2026, et nous avons développé une stratégie par couches pour optimiser les performances de lecture, qui représentent plus de 90% des requêtes d'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 de manière généralisée 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 datacenter, d'alimentation électrique ou de refroidissement limitée, nous sommes parfois confrontés à un compromis direct : réserver la capacité pour du stockage colocalisé et réduire la latence d'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 jeux de données les plus sensibles à la latence
Pour les charges de travail les plus critiques en termes 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 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 elle aussi finalement limitée par la capacité et donc restreinte à quelques jeux de données sélectionnés manuellement.
S'appuyer sur le cache intégré pour tout le reste
Les charges de travail Habitat sont écrasamment orientées lecture, les applications récupérant constamment des paramètres, de l'état et d'autres propriétés ; une part disproportionnée du trafic est imputable à un ensemble de travail relativement restreint de types fréquemment consultés. Cela rend le système particulièrement bien adapté au cache distribué. Si le cache améliore les performances dans une infrastructure mono-région, les améliorations se cumulent lorsqu'on travaille entre régions, où il peut être utilisé pour « masquer » la latence inter-régions.
Nous avons intégré à Habitat une couche de cache distribué de type lookaside. Elle stocke une copie des objets et arêtes récemment consultés dans des serveurs Valkey placés dans notre infrastructure à proximité de l'endroit où les requêtes utilisateurs sont traitées. En général, ce type de cache utilise un TTL qui constitue un compromis entre taux de succès et fraîcheur. Des TTL courts réduisent la péremption mais atténuent l'impact positif du 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 des périodes prolongées, entraînant de mauvaises expériences utilisateur et un comportement produit imprévisible.
Une autre approche consiste à exécuter des opérations explicites d'invalidation 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 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 faisant la course avec la réplication du stockage sous-jacent. Le complémenter avec des TTL borne la péremption maximale, mais souffre toujours du même compromis TTL sous-jacent évoqué ci-dessus.
Nous avons adopté une approche d'invalidation plus avancée, en suivant les traces de systèmes comme le TAO de Meta. Le cache intégré d'Habitat s'appuie sur la capture de changements de données (CDC) via le flux de changement d'Azure Cosmos DB pour propager les invalidations à l'échelle mondiale en quelques secondes, obtenant des taux de succès élevés tout en maintenant une faible péremption. Un service de notre infrastructure consomme ce flux de changement et exécute les invalidations sur les caches locaux lorsque les données sous-jacentes sont mises à jour. Cela provoque un échec de cache 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 réduit considérablement la latence en supprimant la surcharge dans le service Habitat lui-même en réécrivant le code en Rust, qui est plus performant que le Python d'origine.
Ensemble, ces changements ont réduit notre p50 global de lectures, partout dans le monde, 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élioration des performances à l'échelle mondiale
latence p50 pour les lectures
Trois changements de conception majeurs ont amélioré la latence de lecture
environ 120 ms → environ 10 ms
amélioration d'un facteur 12
- données critiques répliquées vers des géos proches
- mise en cache dans la 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 de récupérer des 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 leur lieu d'accès dès le départ.
Passage à 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 d'Habitat. Des millions de modifications capturées par seconde, réparties sur des milliers de conteneurs et des millions de partitions de conteneurs, transitent par plus d'une centaine de topics Kafka pour alimenter également des index secondaires à cohérence à terme, des pipelines de déplacement de données, des analyses hors ligne et d'autres fonctionnalités produits en temps réel. À mesure que notre infrastructure sous-jacente s'est mondialisée, l'écoute des modifications de lignes est devenue de plus en plus complexe. Si un client souhaite enregistrer un callback à exécuter chaque fois que des lignes de son type sont modifiées, il doit, entre autres choses, 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 concernées à l'écoute de ces conteneurs, et dimensionner correctement la flotte de workers du service CDC, Kafka et son consommateur. Notre plateforme CDC vise à abstraire tout cela pour les développeurs produits 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 capturés, sélectionne les régions Cosmos à partir desquelles lire et distribue efficacement le travail parmi des milliers de workers répartis sur plusieurs régions géographiques. L'utilisation de régions répliques et de lectures de flux à priorité plus basse protège le trafic Habitat sensible à la latence des charges de travail de capture en arrière-plan. Les crashs de workers, les problèmes de conteneurs, les erreurs de lease store, les configurations invalides et les défaillances de destination surviennent en permanence sur une flotte de cette taille. Nous avons durci 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 changements de configuration et les déploiements. Nous utilisons des dead letter queues pour empêcher le blocage en tête de file causé par des échecs de livraison permanents. Nous tolérons les pannes régionales en basculant les lecteurs vers d'autres régions Cosmos. À notre échelle, nous avons même dû modifier le SDK CDC d'Azure pour rendre son implémentation du 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 d'infrastructure des développeurs produits afin d'améliorer la vélocité de 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 maintenant délibérément étroite l'API de stockage, en continuant de faire monter en charge le service Python et en hiérarchisant 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 du monde entier, 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é de développement produit.
Afin de faire passer Habitat et le stockage des conversations à l'échelle du 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 développons un système de stockage en ligne interne à l'échelle de l'exaoctet, optimisé pour les types de données volumineux d'Habitat. Ce système en 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 documents qu'Azure Cosmos DB, mais utilise un stockage désagrégé et une réplication physique pour réduire la capacité flash nécessaire, en échange d'un qps de pointe plus faible.
Si vous souhaitez travailler sur ce type d'efforts d'infrastructure de données à l'échelle frontière, consultez nos postes ouverts.