Alle OpenAI-Produkte erfordern schnellen, zuverlässigen Zugriff auf Daten, der von unserer Speicherplattform Habitat bereitgestellt wird. Sie muss in Millisekunden auf über 1 Milliarde Nutzer in 40 geografischen Regionen reagieren. Unsere Infrastruktur sah sich einer außergewöhnlichen Herausforderung gegenüber: die Unterstützung von explosives Wachstum und neuen Anwendungsfällen, während Zuverlässigkeit, Leistung und die Entwicklungsgeschwindigkeit für Produktentwickler kontinuierlich verbessert werden sollten. Die Lösung dieser Herausforderung erforderte ein Umdenken darüber, wie unsere Online-Speicherplattform entworfen und betrieben wurde. Wir bewerteten ständig Kompromisse, entlang des Musters: zuerst zuverlässig genug für geschäftskritisches Produkttraffic werden, dann schnell genug für globale Nutzer und schließlich gewandt im Betrieb bei massivem Maßstab.
Diese zweiteilige Serie erklärt, wie wir Habitat von einer einfachen Client-Bibliothek und Datenbank zu einer global verteilten Speicherplattform entwickelt haben, die über 500 Petabyte an Daten verwaltet. Teil I konzentrierte sich auf Habitats Entwicklung von einem Python-Client zu einem Dienst. Er behandelte auch die Techniken, die es uns ermöglichten, einen Python-basierten Dienst weit über das hinaus zu bringen, was viele als praktikabel ansehen würden.
In diesem Beitrag, Teil II, zeigen wir, wie wir die zugrunde liegende Speicherkomponente skaliert haben: Azure Cosmos DB. Durch eine enge Arbeitspartnerschaft mit dem Azure-Team fanden wir einen tragfähigen Multi-Tenancy-Ansatz mit ko-verwalteten Containern und ausgezeichneter Leseleistung bei massivem, globalem Maßstab.
Abbildung 01 · Was ist Habitat?
Online-Speicherplattform
Habitat ist die Online-Speicherplattform, die wir gebaut haben, damit OpenAI-Produkte schnell und zuverlässig auf benötigte Informationen zugreifen können.
- Anfrage
- Antwort
- Änderungen (CDC)
Clients
Online-Speicherplattform
Speicherressourcen
- ChatGPT
- API
- Codex
- Interne Dienste
- Und mehr
Habitat
- CachingCaches
- ACL-RichtlinienAutorisierung
- Platzierung & DatenresidenzDatenresidenz
- VerschlüsselungDatensicherheit
- IsolationMulti-Tenancy
- RatenbegrenzungAnfrageformung
- RoutingSchema-Lookup · Datenresidenz
- Azure Cosmos DBOnline-Speicher
- NanobaseOnline-Speicher
- ValkeyCaches
- Blob-SpeicherSpeicherressourcen
- Databricks
- Rockset
- Kafka
- Und mehr
Zuverlässiges Multi-Tenancy im großen Maßstab
Wenn Unternehmen wachsen, entwickeln sich Infrastrukturteams oft von Single-Tenant-Systemen zu Multi-Tenant-Systemen weiter. Single-Tenancy, bei der jedes Product-Surface-Team sein eigenes System betreibt, verringert den Auswirkungsradius (Blast Radius) und bietet klare Eigentumsgrenzen sowie Workload-Isolation. Mit wachsender Zahl von Teams und Product Surfaces wird dies jedoch teuer im Betrieb. Single-Tenancy macht jedes Produktteam für die Kapazitätsplanung verantwortlich und führt häufig zu erheblicher Überprovisionierung.
Umgekehrt legt Multi-Tenancy die Kontrolle in die Hände eines zentralisierten Datenbankteams, das über besseres Wissen und mehr Erfahrung bei der Bereitstellung von Ressourcen verfügt. Aber Multi-Tenancy schafft ihre eigenen Probleme, etwa Noisy-Neighbor-Probleme, bei denen ein Mandant einen ungewöhnlich großen Anteil der Ressourcen verbraucht, zum Nachteil der anderen Mandanten.
In Habitat mussten wir all diese Trade-offs abwägen, um ein zuverlässiges und effizientes Speichersystem für den Einsatz im großen Maßstab zu entwerfen. Heute ist Habitat eine Multi-Tenant-Datenbankschicht für viele OpenAI-Produkte, aber wir haben eine kreisförmige Reise durchlaufen: von der Verwendung eines Multi-Tenant-Azure-Cosmos-DB-Containers über die Bereitstellung von Single-Tenant-Backing-Containers bis hin zur Rückkehr zu einem Multi-Tenant-Container als Standard. Wir stehen mit unserer Multi-Tenancy-Story bei Habitat noch ganz am Anfang: Wir unterstützen eine große Auswahl an Anwendungsfällen, aber wir konnten Investitionen in diesem Bereich aufschieben, indem wir die integrierten Primitive von Azure Cosmos DB überall dort nutzen, wo dies möglich war.
Ein vereinfachter Grundriss zu Azure Cosmos DB-Containern
Bevor wir unsere Azure-Cosmos-DB-Reise nachzeichnen, etwas grundlegender Kontext:
Ein Azure Cosmos DB-Konto definiert eine Menge von Containern und eine Replikationsrichtlinie
Für jeden Container wird eine bestimmte Menge an Durchsatz in Request Units pro Sekunde (RU/s) provisioniert
Daten innerhalb jedes Containers werden auf typische Weise nach einem angegebenen logischen Partitionsschlüssel geshardet, mit dokumentierten Grenzwerten für Speicher und Durchsatz pro logischem Partitionsschlüssel
Azure Cosmos DB verwaltet das Sharding logischer Partitionsschlüssel über weniger Backing-Physical-Partitions hinweg und teilt sie in mehrere physische Partitionen auf, wenn der Speicher einen Schwellwert überschreitet oder wenn der Container mehr physische Partitionen benötigt, um den provisionierten Durchsatz zu halten
Phase I: Sich auf Azure Cosmos DB-Primitive verlassen
Ursprünglich ließen wir jeglichen Client-Last durch und verließen uns auf den angegebenen Grenzwert von 10K RU/s pro logischem Partitionsschlüssel, um Back Pressure bereitzustellen. Zu Beginn verwendeten wir auch nur einen einzigen Standard-Azure-Cosmos-DB-Container pro Konto. Das funktionierte gut genug, erwies sich aber aus einigen Gründen als problematisch:
Grenzwerte für logische Partitionsschlüssel
Aufgrund früherer API-Entscheidungen haben wir die Auswahl des logischen Partitionsschlüssels direkt an Clients weitergegeben. Infolgedessen kommt es häufig zu Kollisionen bei häufig verwendeten Partitionsschlüsseln, wie etwa user_id oder workspace_id, über verschiedene Produktanwendungsfälle hinweg. Da sich diese Anwendungsfälle die Azure Cosmos DB-Grenzwerte auf denselben logischen Partitionsschlüsseln teilten, schränkte das Design den Gesamtdurchsatz für einen bestimmten Benutzer ein.
Ein häufiges Problem, dem wir beim Skalieren von Postgres begegneten, bevor wir zu Habitat und Azure Cosmos DB wechselten, war die Möglichkeit, dass viele Anfragen von einer sehr stark frequentierten Organisation oder einem Hot Key – ein Mandant, der eine unverhältnismäßig hohe Last erzeugt – die gesamte Datenbank lahmlegen konnten. Es wurde erheblicher kollektiver Aufwand betrieben, um diese Schlüssel zu cachen und Leases in Cache Misses einzubauen, damit die daraus resultierende Cache-Miss-Last nicht die zugrunde liegende Datenbank überwältigt.
Abbildung 02a · Grenzwerte für logische Partitionsschlüssel
Verschiedene Clients, derselbe Partitionsschlüssel
Noisy Neighbors
Die dokumentierten Grenzwerte für Datengröße und Durchsatz gelten zwar für logische Partitionsschlüssel, durchgesetzt werden die Grenzwerte jedoch tatsächlich auf der physischen Partition. In der Praxis bedeutet dies, dass logische Partitionen 10,000 RU/s nicht erreichen können. Stattdessen teilen sie sich die Grenzwerte der physischen Partition mit jedem anderen logischen Partitionsschlüssel, der auf derselben physischen Partition untergebracht ist.
Dies ermöglichte es Noisy Neighbors, viele nicht verwandte logische Partitionsschlüssel zu beeinträchtigen, wobei Workloads, die auf Daten für Nischen-Produktfunktionen operierten, die zentralen Login- und Signup-Abläufe störten.
Abbildung 02b · Noisy Neighbors
Verschiedene Benutzer, dieselbe physische Partition
Schwache Partitionen
Der Durchsatzgrenzwert einer bestimmten physischen Partition ist zudem variabel und hängt vom gesamten Durchsatz ab, der dem Azure Cosmos DB-Container zugewiesen ist. Der provisionierte Durchsatz des Containers wird ungefähr gleichmäßig auf die physischen Partitionen dieses Containers verteilt.
Mit wachsendem Datenvolumen werden physische Partitionen aufgeteilt, wodurch die Gesamtzahl der Partitionen steigt, während der gesamte provisionierte Durchsatz unverändert bleibt. Das Ergebnis ist, dass der effektive Durchsatzgrenzwert pro Partition mit wachsender Datenmenge schrumpft.
Für unsere speicherintensivsten, blob-artigen Anwendungsfälle entsteht dadurch ein Fehlermodus, bei dem bereits das Speicherwachstum allein den Durchsatz für aktive Workloads reduziert, selbst wenn Anforderungsraten und Nutzung konstant bleiben.
Abbildung 02C · Schwache Partitionen
Das Limit fällt unter die Nachfrage
Eine Partition teilt sich, wenn sie ihr Speicherlimit erreicht. Jedes Kind erhält die Hälfte der Durchsatzkapazität.
Vor dem Split: 10K RU/s Kapazität
Nach dem Split: 5K RU/s Kapazität × 2 Partitionen
Phase II: Versuch mit Workload-Isolation und Single-Tenancy
Getrennte Container helfen, diese Partitionsschlüssel-Limits und laute Nachbarn teilweise zu lösen. Durch die Verwendung getrennter Container kann jedem seine eigenen unabhängigen physischen Partitionen zugewiesen werden, und nur die variierenden Workloads auf dem jeweiligen Container selbst könnten laute Nachbarn sein oder sich im Partitionsschlüssel-Raum überlappen. Nachteilig ist, dass der Aufwand für die Bereitstellung neuer Container nicht Null ist – jeder Container erfordert eine individuelle Kapazitätsplanung –, daher möchten wir Container nur an einer sinnvollen Dienst- oder Use-Case-Grenze erstellen.
Wir begannen, getrennte Azure Cosmos DB-Container für die kritischsten neuen Anwendungsfälle zu verwenden, die eine starke Isolation erfordern. In Kombination mit einem Skript zur Verwaltung schwacher Partitionen, das den gesamten für jeden Container bereitgestellten Durchsatz mit dessen Anzahl physischer Partitionen synchronisiert, vermieden wir die meisten der Probleme, mit denen wir konfrontiert waren.
Da wir jedoch mit einem Standard-Container begonnen hatten, blieben leider viele ältere kritische Anwendungsfälle ohne Datenmigration darin hängen. Um kritische Dienste zu schützen, die weiterhin auf Daten im gemeinsamen Standard-Container angewiesen waren, empfahlen wir Teams oft, getrennte Container für neue Dienste oder große Produktfunktionen zu erstellen. Als wir skalierten, wuchs der Druck auf den Standard-Container, und auch die Produktionsvorfälle nahmen zu. Wir begannen, Container für jeden neuen Dienst oder jede große Produktfunktion bereitzustellen, nur um den Standard-Container vor weiterer Last zu schützen.
Das bewahrte uns vor vielen ausfallsicheren site-weiten Ausfällen und begrenzte den Schadensradius auf Funktions- oder Dienstdegradierungen. Letztendlich holte uns jedoch der Aufwand für die Bereitstellung neuer Container ein:
Fehlendes stabiles aggregiertes Wachstum
Im alten gemeinsamen Standard-Container war es unwahrscheinlich, dass ein bestimmter neuer Anwendungsfall, der von 0 QPS auf 1 Million QPS anstieg, die gesamte aggregierte Nutzung erheblich veränderte. Auf einem isolierten Container hingegen könnte ein neuer Anwendungsfall, der von 0 auf 1 Million QPS ansteigt, das 1,000-fache der Last darstellen, verglichen mit der Basislinie, die nur dem internen Dogfooding zugeschrieben wurde.
Mit dedizierten Containern mussten sich Teams nun merken, Container vorab auf das erwartete Durchsatzniveau für ihre Launches bereitzustellen. Produktteams schickten ihre Funktionen häufig in Produktion und vergaßen das vorzeitige Skalieren, und wir saßen am Launch-Tag in SEV-Räumen und versuchten, Container in letzter Minute hochzuskalieren.
Dies ist ein wesentlicher Vorteil der Workload-Durchmischung von Multi-Tenant-Systemen, den wir zugunsten einer besseren Isolation mit dieser Container-Strategie verlieren.
Abbildung 03 · Fehlendes stabiles aggregiertes Wachstum
Dieselbe Workload, unterschiedlicher Spielraum
1K RU/s während des Lasttests → 1M RU/s beim Launch
Gemeinsamer Container
100M RU/s Kapazität · 80% ausgelastet
Lasttest
1K20M=0.005%
Spielraum
Launch
1M20M=5%
Spielraum
Neuer dedizierter Container
10K RU/s Kapazität · keine anderen Workloads
Lasttest
1K10K=10%
Spielraum
Launch
1M10K=10,000%
Spielraum
Kapazitätsplanung
Mit der Zeit erforderte die Überwachung der Nutzung dedizierter Container auch tiefgreifendes Produktwissen darüber, wann sich der Verkehr verschieben oder vergrößern würde.
Wir können ein grundlegendes Control-Plane-Skript bereitstellen, um den bereitgestellten Durchsatz für jeden Container auf Basis von Wachstumstrends hochzuskalieren, aber ohne das stabile aggregierte Wachstum bedeutete das korrekte Skalieren, dass im Wesentlichen jedes Produktteam bei OpenAI in die Kapazitätsplanung eingebunden war.
Das ließ sich organisatorisch nicht skalieren, da die Sorgfalt, mit der das besitzende Team jedes Service seine Azure-Cosmos-DB-Bereitstellung pflegte, stark variierte.
Phase III: Nutzung von Multi-Tenancy als Standard, mit Datenverschiebungsfähigkeit
Als die Organisation wuchs, wurden diese operativen Probleme schwer skalierbar. Kein einzelnes, zentrales Team, das Planung betrieb, führte dazu, dass viele Teams schlecht planten. Infolgedessen kehrten wir zu einer Multi-Tenant-Lösung zurück und arbeiteten eng mit dem Azure-Team zusammen, um Lösungen für unsere ursprünglichen Multi-Tenancy-Probleme mit einem Shared-Tenancy-Design zu entwickeln:
Drosselung von Hot Keys
Die Drosselung der heißesten Keys zuerst verhindert, dass laute Nachbarn den gesamten verfügbaren Durchsatz für eine bestimmte physische Partition verbrauchen. Dies reduziert den Noisy-Neighbor-Effekt, bei dem ein einzelner hot key einen auswirkungsreich über die gesamte Site reichenden Blast Radius haben kann.
Quality of Service (QoS)
Eine Request-Tiering-QoS verhindert ebenso, dass Offline-Workloads wie Bulk-Inserts oder Datenextraktionen den gesamten Durchsatz einer bestimmten physischen Partition beanspruchen. Indem den Anfragen dieser Workloads eine niedrigere Priorität zugewiesen wird, werden sie zuerst gedrosselt, wodurch Drosselungen bei interaktiven Anfragen mit höherer Priorität, die Endnutzern dienen, verhindert werden.
Serverless-Modus
Azure Cosmos DB hatte bereits einen Serverless-Modus, der unser Problem mit schwachen Partitionsen im provisionierten Modus gelöst hätte. Es gab jedoch einen großen Nachteil: Er skalierte ausschließlich auf Basis der Speichernutzung und begrenzte die RU-Nutzung auf 5K RU/s pro physischer Partition. Dies hätte unser Problem der schwachen Partitionen gelöst, hätte uns aber daran gehindert, RU-intensive Anwendungsfälle hochzuskalieren.
Ein neuer, verbesserter Serverless-Modus, den das Azure-Cosmos-DB-Team für uns baute, erlaubte es jeder physischen Partition, bis zu 10K RU/s zu skalieren, und führte RU-nutzungsbasierte Splits zusätzlich zu speichernutzungsbasierten Splits ein, wodurch wir aufhören konnten, Container manuell zu überwachen und hochzuskalieren.
Mit diesen Änderungen konnten wir zurückwechseln zu einem gemeinsamen Container, was die Notwendigkeit einer anfänglichen oder laufenden Kapazitätsplanung für Produktteams erheblich vereinfacht hat, während die Isolation zwischen Workloads erhalten blieb. Seitdem haben wir in den Monaten trotz beschleunigter Produktentwicklung keine Bereitstellungs- oder Noisy-Neighbor-Vorfälle mehr gehabt.
Bessere Zuverlässigkeit als gemeinsam verwaltete Dienste
Wenn Unternehmen auf OpenAI-Skala wachsen, werden verwaltete Dienste weniger „managed“. Viele Workloads werden zu Ausreißern in der Infrastruktur des Cloud-Anbieters. Als OpenAI zu einem der größten Nutzer von Azure Cosmos DB wurde, erforderte der erfolgreiche Betrieb ein anderes Modell: gemeinsame Tests, gemeinsame Dashboards, direkte Engineering-Kanäle und gemeinsame Verantwortung für Fehlermodi, die nur in unserer Größenordnung auftreten. Heute speichert unser größtes Azure-Cosmos-DB-Konto 10s of Petabytes an Daten und bedient mehr als 20M QPS. Nach unserem Wissen sind wir das einzige Unternehmen, das Azure Cosmos DB in dieser Größenordnung eingesetzt hat.
Ende 2024 machte ein regionaler Ausfall deutlich, warum diese Zusammenarbeit notwendig war. Damals traf ein Stromausfall die primäre Azure-Region für eines unserer größten Azure-Cosmos-DB-Konten. Bei der daraus resultierenden Failover-Übung stellten wir fest, dass unsere größten Konten mehrere Stunden brauchen würden, um auf eine sekundäre Region umzuschalten, etwas, das weder unser Team noch das Azure-Cosmos-DB-Team in unserer Größenordnung getestet hatte.
Etwa einen Monat später lösten wir beim Zurückumschalten dieser Konten in die ursprüngliche Region einen weiteren schwerwiegenden Ausfall aus. Ein Connection Stampede in die ursprüngliche Region überlastete das Azure-Cosmos-DB-Gateway der ursprünglichen Region. Eine Besonderheit darin, wie der Gateway-Modus des Azure-Cosmos-DB-Clients Failovers behandelte, veranlasste alle Clients, sofort Verbindungen aufzubauen und Anfragen an die ursprüngliche Region zu senden, obwohl nur eine Teilmenge der physischen Partitionen in diese Region zurückbewegt worden war.
Bei jedem Ausfall durchliefen sowohl OpenAI als auch Azure ihre Incident-Management-Prozesse, wobei eine Reihe von Ursachen identifiziert und Fixes bereitgestellt wurden. Doch diese Ausfälle waren Symptome eines größeren systemischen Problems zwischen der Art und Weise, wie unser Team und das Azure-Cosmos-DB-Team arbeiteten und testeten. Anfänglich ermöglichte uns die Nutzung verwalteter Dienste, uns schnell zu bewegen und uns auf Geschäftsziele zu konzentrieren. In großem Maßstab werden verwaltete Dienste weniger verwaltet und erfordern tiefe Integrationen mit dem Dienstanbieter auf allen Ebenen, vom Design bis zum Betrieb.
Heute überwachen die Habitat- und Azure-Engineering-Teams gemeinsam jede Ebene von Habitat, von unseren Service-Ebenen hinunter bis in die physischen Speicherebenen von Azure Cosmos DB. Wir betreiben und testen Änderungen an einem Azure-Cosmos-DB-Konto in Produktionsgröße, aber nicht produktiv. Wir testen SDKs gemeinsam und iterieren daran und legen Best Practices fest, wie wir unsere Azure-Cosmos-DB-Konten skalieren und dimensionieren. Wir haben direkten Engineering-Zugriff zueinander über Teams und haben sogar Möglichkeiten geschaffen, einander direkt zu pageren, ohne durch Support-Ebenen zu gehen.
Ein erheblicher Umfang gemeinsamer Arbeit ist im letzten Jahr erfolgt, um die Steuerungsebenen- und Gateway-Skalierbarkeit von Azure Cosmos DB zu verbessern, darunter der Zusatz von Lastschutz und Caching verschiedener Azure-Cosmos-DB-Metadaten in ihrem Gateway, um die Azure-Cosmos-DB-Metadaten-Partition zu schützen, die Reduzierung des Drucks durch unsere Flotte von Python-habitat-service-Prozessen durch Connection Pooling und höhere Verbindungswiederverwendung, die Kolokation von Workloads, wo möglich, und die Isolierung kritischer Workloads von Offline-Workloads über verschiedene VPCs.
Dieses Maß an Co-Engineering war entscheidend für die Stabilität unserer Azure-Cosmos-DB-Nutzung im letzten Jahr. Jedes System, das groß oder kritisch genug ist, erfordert diese Art von Teamarbeit: das Abbauen von Eigentumsgrenzen und das Arbeiten über Organisationsgrenzen hinweg.
Verbesserung der Leistung in globalem Maßstab
Wie in Teil I erwähnt, lautet unsere Kernphilosophie: make it work, make it fast, make it cheap. Zuerst Zuverlässigkeit, dann Leistung, dann Effizienz. Habitat war zu kritischer Infrastruktur für OpenAI geworden, daher hatte die Verbesserung der Zuverlässigkeit höchste Priorität. Als sich das System 2025 stabilisierte und OpenAI global expandierte, wurde die Latenz zur nächsten Einschränkung für Nutzer und Produktteams.
Rechen- und Speicherkapazitäten befanden sich oft in unterschiedlichen Regionen, sodass regionsübergreifende Anfragen unvermeidlich waren.
Abbildung 04 · Verbesserung der Leistung auf globaler Ebene
Globaler Datenverkehr
Rechen- und Speicherkapazitäten befanden sich oft in unterschiedlichen Regionen, sodass regionsübergreifende Anfragen unvermeidlich waren.
Die geografischen Standorte von Compute und Speicher dienen nur zur Veranschaulichung.
Die Latenz variierte stark, je nachdem, wo die Daten gespeichert waren und wo die Anfrage bearbeitet wurde. Um Habitat-Anfragen performant zu machen, griffen Produktentwickler auf zusätzliche clientseitige Caches, Anfrage-Batching, die Minimierung von Datenbanklese- und -schreibvorgängen sowie darauf zurück, Arbeit möglichst vom kritischen Pfad zu verlagern. Obwohl dies zu einigen interessanten Produktdesigns führte, die widerstandsfähiger gegenüber Datenbankproblemen waren, machte es die Produkte auch komplexer und verlangsamte die Produktiteration: ein Versäumnis, Habitats Ziel zu erreichen, eine schnelle Produktentwicklung zu fördern und ein Verständnis der Datenbank unnötig zu machen.
Dies war 2026 ein großer Schwerpunkt für uns, und wir haben eine mehrschichtige Strategie zur Optimierung der Leseleistung entwickelt, die über 90% der Habitat-Anfragen ausmacht:
Daten möglichst nah bei den Nutzern speichern, wenn die Kapazität es erlaubt
Wir besitzen mehrere Azure Cosmos DB-Konten in verschiedenen Azure-Regionen weltweit für Nutzer mit Datenresidenzanforderungen. Selbst für Nutzer ohne strikte Datenresidenzanforderungen verbessert die Platzierung von Daten in der Nähe der Nutzer deren Erfahrung durch geringere Latenz.
In der Praxis können wir dies außerhalb von Datenresidenzfällen nicht umfassend erreichen, da die Platzierung durch die physische Cloud-Kapazität eingeschränkt ist. Aufgrund der Größe unserer Datensätze können viele Regionen den massiven Speicherbedarf, der für ihren anteiligen Nutzertraffic erforderlich wäre, nicht aufnehmen.
In Regionen mit begrenzter Rechenzentrumskapazität, Stromversorgung oder Kühlung stehen wir manchmal vor einem direkten Zielkonflikt: Kapazität für gemeinsam platzierte Speicher und eine geringere Habitat-Latenz reservieren, oder diese Kapazität für zusätzliche Serving-Compute-Ressourcen nutzen und Speicheranfragen weiter entfernt leiten.
Infolgedessen sind Platzierungsentscheidungen oft ein Produktkompromiss zwischen: A) der Optimierung der Latenz für verschiedene Produkte und Nutzergruppen und B) reiner Speicher- und Rechenkapazität.
Nur die kleinsten und latenzempfindlichsten Datensätze umfassend replizieren
Für die am stärksten latenzkritischen Workloads, wie Authentifizierungsmetadaten, die bei jeder Anfrage benötigt werden, reduzieren wir die RTT zwischen Compute und Habitat, indem wir stark replizierte Azure Cosmos DB-Konten mit vielen sekundären Regionen verwenden. Einige Konten replizieren in alle 40 Azure-Regionen, in denen wir Traffic bedienen; andere replizieren in mindestens eine Region pro Kontinent.
Wir mussten eine transparente Datenbewegungs- und Routing-Schicht in Habitat früher als gewünscht einbauen, damit wir Datensätze zwischen Konten mit unterschiedlichen Replikationstopologien verschieben können.
Dieser Ansatz ist letztlich jedoch ebenfalls kapazitätsbeschränkt und daher auf wenige handverlesene Datensätze begrenzt.
Für alles Übrige auf integriertes Caching setzen
Habitat-Workloads sind überwiegend leselastig, wobei Anwendungen ständig Einstellungen, Zustände und andere Eigenschaften abrufen; ein unverhältnismäßig großer Anteil des Traffics entfällt auf eine relativ kleine Arbeitsmenge häufig abgerufener Typen. Dadurch eignet sich das System besonders gut für verteiltes Caching. Während Caching die Leistung in einer Rechenzentrumsinfrastruktur mit einer Region verbessert, potenziert sich die Verbesserung bei regionenübergreifenden Arbeiten, wo es genutzt werden kann, um die regionsübergreifende Latenz zu „verbergen“.
Wir haben eine verteilte Lookaside-Caching-Schicht in Habitat integriert. Sie speichert eine Kopie kürzlich abgerufener Objekte und Kanten auf Valkey-Servern, die in unserer Infrastruktur nahe dem Ort platziert sind, an dem Nutzeranfragen verarbeitet werden. Solche Caches verwenden typischerweise eine TTL, die einen Kompromiss zwischen Trefferquote und Aktualität darstellt. Kurze TTLs verringern die Veraltung, schwächen aber die positive Wirkung des Cachings, da Einträge schnell aus dem Cache ablaufen und häufiger neu abgerufen werden müssen. Längere TTLs verbessern die Trefferquote, liefern aber über längere Zeiträume veraltete Daten, was zu schlechten Nutzererfahrungen und unvorhersehbarem Produktverhalten führt.
Ein anderer Ansatz besteht darin, explizite Invalidierungsvorgänge auszuführen, wenn die Anwendung Schreibvorgänge auf denselben zugrunde liegenden Daten durchführt. Dies verbessert die Frische der zwischengespeicherten Daten erheblich, kann aber schwer korrekt umzusetzen sein. Es bleibt zudem einer Reihe von Randfällen unterworfen, wie partiellen Ausfällen, die den Cache veralten lassen. Multi-Region-Bereitstellungen verschärfen diese Probleme, mit weniger zuverlässigen regionenübergreifenden Netzwerken und dem Rennen zwischen Cache und Replikation des zugrunde liegenden Speichers. Die Ergänzung durch TTLs begrenzt die maximale Veraltung, leidet aber weiterhin unter demselben zugrunde liegenden TTL-Kompromiss wie oben beschrieben.
Wir haben einen fortgeschritteneren Ansatz zur Invalidierung verfolgt, in Anlehnung an Systeme wie Metas TAO. Habitats integrierter Cache nutzt Change Data Capture (CDC) über den Change Feed von Azure Cosmos DB, um Invalidierungen innerhalb von Sekunden global zu verbreiten und so hohe Trefferquoten bei geringer Veraltung zu erreichen. Ein Dienst in unserer Infrastruktur konsumiert diesen Change Feed und führt Invalidierungen der lokalen Caches durch, wenn die zugrunde liegenden Daten aktualisiert werden. Dies verursacht beim nächsten Lesen der Daten einen Miss, und der Cache wird mit der neuesten Version der Daten gefüllt.
Proxy-Overhead mit Rust reduzieren
Wir haben die Latenz auch erheblich reduziert, indem wir Overhead im Habitat-Dienst selbst beseitigt haben, indem wir den Code in Rust neu geschrieben haben, das performanter als das ursprüngliche Python ist.
Insgesamt haben diese Änderungen unser globales p50 für Lesevorgänge von rund 120 ms auf rund 10 ms reduziert, eine 12-fache Verbesserung seit Beginn unseres Fokus auf Performance Anfang dieses Jahres.
Abbildung 05 · Verbesserung der Performance auf globaler Ebene
p50-Latenz für Lesevorgänge
Drei große Designänderungen verbesserten die Leselatenz
rund 120 ms → rund 10 ms
12-fache Verbesserung
- kritische Daten in nahegelegene Geos repliziert
- In-Region-Caching eingeführt
- Rust-Rewrite eingeführt
Während Caching die mediane Latenz drastisch reduziert hat, erfordern die langsamsten Anfragen weiterhin das Abrufen ungecachter Daten aus dem zugrunde liegenden Speicher, oft über weit entfernte Regionen. Da mehr Kapazität verfügbar wird und wir die Speichereffizienz verbessern, planen wir, eine grundlegendere Herausforderung anzugehen: Daten von Anfang an näher am Ort ihres späteren Zugriffs zu platzieren.
Skalierung von Change Data Capture
Zuverlässiges und skalierbares CDC – also die Weitergabe von Datenbankänderungen an nachgelagerte Systeme – ist eine kritische Komponente weit über Habitats Caching-Strategie hinaus. Millionen erfasster Änderungen pro Sekunde, verteilt über Tausende Container und Millionen Container-Partitionen, laufen durch mehr als hundert Kafka-Themen und treiben zusätzlich eventuell konsistente Sekundärindizes, Datenverschiebe-Pipelines, Offline-Analysen und andere Echtzeit-Produktfeatures an. Da sich unsere zugrunde liegende Infrastruktur global vergrößert hat, ist das Lauschen auf Zeilenänderungen zunehmend komplexer geworden. Wenn ein Client einen Callback registrieren möchte, der bei jeder Änderung der Zeilen seines Typs ausgeführt wird, muss er unter anderem zunächst feststellen, welche Cosmos-Konten und -Container seine Daten enthalten, welche CDC-Service-Bereitstellung in den jeweiligen Regionen läuft und diese Container abhört, sowie die CDC-Service-Worker-Flotte, Kafka und seinen Consumer richtig skalieren. Unsere CDC-Plattform zielt darauf ab, all dies von Produktentwicklern fernzuhalten und stattdessen eine einfache Oberfläche bereitzustellen, auf der Clients eine Funktion registrieren, die bei Änderungen ihrer Zeilen aufgerufen werden soll, während wir die gesamte Verdrahtung und Skalierung übernehmen.
Eine interne Steuerungsebene stellt kontinuierlich sicher, dass alle erforderlichen Container erfasst werden, wählt aus, aus welchen Cosmos-Regionen gelesen wird, und verteilt die Arbeit effizient auf Tausende Worker über mehrere geografische Regionen. Die Nutzung von Replikatregionen und Feed-Lesevorgängen mit niedrigerer Priorität schützt latenzempfindlichen Habitat-Traffic vor Hintergrund-Erfassungsworkloads. Worker-Abstürze, Containerprobleme, Lease-Store-Fehler, ungültige Konfigurationen und Ziel-Fehler treten bei einer so großen Flotte ständig auf. Wir haben den CDC-Worker-Lebenszyklus und das Lease-Handling gehärtet, Fehler zwischen Streams isoliert und Schutzmechanismen für Konfigurationsänderungen und Rollouts gebaut. Wir nutzen Dead-Letter-Queues, um Head-of-Line-Blocking durch dauerhafte Zustellfehler zu verhindern. Regionale Ausfälle tolerieren wir, indem wir Leser auf alternative Cosmos-Regionen umschalten. In unserem Umfang mussten wir sogar Änderungen am CDC-SDK von Azure vornehmen, um dessen Checkpointing-Implementierung effizienter zu machen, indem Checkpoints im Arbeitsspeicher gebündelt und nur periodisch geleert werden.
Auch wenn Habitats interne Nutzung von Azure Cosmos DB komplex ist, besteht ihr Mehrwert stets darin, Produktentwicklern Infrastrukturdetails abzunehmen, um die Produktentwicklungsgeschwindigkeit zu steigern – und an unserer CDC-Plattform ist das nicht anders.
Ausblick
Ein kleines Team konnte eine schnell wachsende Speicherplattform unterstützen, indem es die Storage-API bewusst schmal hielt, den Python-Dienst weiter skalierte und die dringendsten technischen Herausforderungen priorisierte. Wir konzentrierten uns zuerst auf Zuverlässigkeit für geschäftskritischen Produkt-Traffic, dann auf die Lese-Performance für globale Nutzer und schließlich auf betriebliche Effizienz bei großem Umfang. Dieser Ansatz ermöglichte schnelles Wachstum und neue Anwendungsfälle bei gleichzeitiger Verbesserung von Zuverlässigkeit, Performance und Produktentwicklungsgeschwindigkeit.
Um Habitat und die Konversationsspeicherung für die nächste Milliarde Nutzer zu skalieren, müssen wir die Schwierigkeit angehen, unsere Daten in noch größerem Umfang über unsere Infrastruktur zu replizieren. Um dem vorzubeugen, bauen wir ein kundenspezifisches, hausinternes Online-Speichersystem im Exabyte-Maßstab auf, optimiert für voluminöse Habitat-Datentypen. Dieses sich in Entwicklung befindende System kombiniert den RocksDB-Cloud-Kern von Rockset mit Raft-basierter Replikation. Es implementiert dasselbe Dokumentenmodell wie Azure Cosmos DB, verwendet jedoch disaggregierten Speicher und physische Replikation, um die benötigte Flash-Kapazität zu reduzieren – im Gegenzug für ein niedrigeres Spitzen-qps.
Wenn Sie daran interessiert sind, an diesen Arten von Dateninfrastruktur-Projekten im Frontier-Maßstab zu arbeiten, schauen Sie sich unsere offenen Stellen an.