Online-Speicher schnell für über 1 Milliarde Menschen mit ChatGPT skalieren (Teil II)
Wie wir Azure Cosmos DB in unserer Anwendungsspeicherplattform Habitat optimiert haben, um beispielloses Wachstum zu bewältigen.
Alle OpenAI-Produkte benötigen schnellen, zuverlässigen Datenzugriff. Dafür sorgt unsere Speicherplattform Habitat. Sie muss für über 1 Milliarde Menschen in 40 geografischen Regionen innerhalb von Millisekunden reagieren. Unsere Infrastruktur stand vor einer außergewöhnlichen Herausforderung: rasantes Wachstum und neue Anwendungsfälle unterstützen und zugleich Zuverlässigkeit, Leistung und das Tempo der Produktentwicklung weiter verbessern. Dafür mussten wir das Design und den Betrieb unserer Online-Speicherplattform neu überdenken. Wir wogen kontinuierlich Zielkonflikte ab und folgten dabei diesem Muster: zuerst zuverlässig genug für geschäftskritische Produktanfragen werden, dann schnell genug für die weltweite Nutzung und schließlich auch in enormer Größenordnung einen souveränen Betrieb ermöglichen.
Diese zweiteilige Reihe zeigt, wie wir Habitat von einer einfachen Clientbibliothek mit Datenbank zu einer weltweit verteilten Speicherplattform entwickelt haben, die über 500 Petabyte Daten bereitstellt. Teil I behandelte die Entwicklung von Habitat vom Python-Client zum Dienst. Darin ging es auch um die Techniken, mit denen wir einen Python-basierten Dienst weit über das hinaus skalieren konnten, was viele für praktikabel halten würden.
In diesem Beitrag, Teil II, zeigen wir, wie wir die zugrunde liegende Speicherkomponente Azure Cosmos DB skaliert haben. In enger Zusammenarbeit mit dem Azure-Team entwickelten wir einen tragfähigen Multi-Tenancy-Ansatz mit gemeinsam verwalteten Containern und hervorragender Leseleistung im weltweiten Einsatz in enormer Größenordnung.
Abbildung 01 · Was ist Habitat?
Onlinespeicherplattform
Habitat ist die von uns entwickelte Onlinespeicherplattform, über die Produkte von OpenAI schnell und zuverlässig auf benötigte Informationen zugreifen können.
- Anfrage
- Antwort
- Änderungen (CDC)
Wenn Unternehmen wachsen, wechseln Infrastrukturteams häufig vom Betrieb von Single-Tenant-Systemen zu Multi-Tenant-Systemen. Bei Single-Tenancy betreibt jedes Team für seinen Produktbereich ein eigenes System. Das begrenzt die Auswirkungen von Störungen, schafft klare Zuständigkeiten und isoliert Workloads. Mit dem Wachstum der Teams und Produktbereiche wird dieser Betrieb teuer. Bei Single-Tenancy ist jedes Produktteam selbst für die Kapazitätsplanung verantwortlich, was oft zu erheblicher Überdimensionierung führt.
Bei Multi-Tenancy liegt die Steuerung dagegen bei einem zentralen Datenbankteam, das mehr Wissen und Erfahrung bei der Ressourcenbereitstellung hat. Multi-Tenancy bringt jedoch eigene Probleme mit sich, etwa störende Nachbarn: Ein Mandant beansprucht einen ungewöhnlich großen Teil der Ressourcen zulasten der anderen Mandanten.
Für Habitat mussten wir all diese Zielkonflikte abwägen, um ein zuverlässiges, effizientes Speichersystem für den Betrieb in großer Größenordnung zu entwickeln. Heute ist Habitat eine Multi-Tenant-Datenbankschicht für viele OpenAI-Produkte. Der Weg dorthin führte uns jedoch im Kreis: von einem einzigen Multi-Tenant-Container in Azure Cosmos DB über separate Single-Tenant-Speichercontainer zurück zu einem Multi-Tenant-Container als Standard. Mit Multi-Tenancy in Habitat stehen wir noch ganz am Anfang. Wir unterstützen viele Anwendungsfälle, konnten Investitionen in diesen Bereich aber bisher zurückstellen, indem wir möglichst die integrierten Grundfunktionen von Azure Cosmos DB nutzen.
Bevor wir unseren Weg mit Azure Cosmos DB beschreiben, einige Grundlagen:
Ein Azure Cosmos DB-Konto definiert eine Gruppe von Containern und eine Replikationsrichtlinie
Für jeden Container wird ein bestimmter Durchsatz in Anforderungseinheiten pro Sekunde (RU/s) bereitgestellt
Die Daten in jedem Container werden wie üblich anhand vorgegebener logischer Partitionsschlüssel auf Shards verteilt. Für jeden logischen Partitionsschlüssel gelten dokumentierte Speicher- und Durchsatzlimits
Azure Cosmos DB verteilt die logischen Partitionsschlüssel auf eine kleinere Zahl zugrunde liegender physischer Partitionen. Diese werden aufgeteilt, wenn der Speicherbedarf einen Schwellenwert überschreitet oder der Container mehr physische Partitionen benötigt, um den bereitgestellten Durchsatz zu halten
Anfangs ließen wir jede Clientlast durch und vertrauten darauf, dass das angegebene Limit von 10.000 RU/s pro logischem Partitionsschlüssel für Rückstau sorgen würde. Außerdem nutzten wir zu Beginn in jedem Konto nur einen Standardcontainer von Azure Cosmos DB. Das funktionierte ausreichend gut, erwies sich aber aus mehreren Gründen als problematisch:
Aufgrund früher API-Entscheidungen überließen wir die Auswahl logischer Partitionsschlüssel direkt den Clients. Dadurch kommt es bei häufig verwendeten Partitionsschlüsseln wie user_id oder workspace_id oft zu Kollisionen zwischen verschiedenen Produktanwendungsfällen. Da diese Anwendungsfälle auf denselben logischen Partitionsschlüsseln auch die Limits von Azure Cosmos DB teilten, begrenzte das Design den Gesamtdurchsatz pro Person.
Schon beim Skalieren von Postgres vor unserem Wechsel zu Habitat und Azure Cosmos DB hatten wir häufig folgendes Problem: Viele Anfragen einer besonders aktiven Organisation oder für einen stark beanspruchten Schlüssel konnten die gesamte Datenbank lahmlegen. Ein einzelner Mandant erzeugte dabei unverhältnismäßig viel Last. Wir investierten gemeinsam viel Arbeit darin, diese Schlüssel zwischenzuspeichern und bei Cache-Fehltreffern Leases einzusetzen, damit die daraus entstehende Last die zugrunde liegende Datenbank nicht überlastet.
Figure 02a · Logical partition key limits
Different clients, the same partition key
Die dokumentierten Limits für Datenmenge und Durchsatz gelten zwar für logische Partitionsschlüssel, durchgesetzt werden sie jedoch auf der physischen Partition. In der Praxis bedeutet das, dass logische Partitionen keine 10.000 RU/s erreichen können. Stattdessen teilen sie sich die Limits der physischen Partition mit allen anderen logischen Partitionsschlüsseln, die dort untergebracht sind.
Dadurch konnten störende Nachbarn viele voneinander unabhängige logische Partitionsschlüssel beeinträchtigen. Workloads für selten genutzte Produktfunktionen störten so zentrale Anmelde- und Registrierungsabläufe.
Figure 02b · Noisy neighbors
Different users, the same physical partition
Auch das Durchsatzlimit einer physischen Partition ist variabel. Es hängt vom Gesamtdurchsatz ab, der dem Azure Cosmos DB-Container zugewiesen ist. Der bereitgestellte Durchsatz des Containers wird ungefähr gleichmäßig auf dessen physische Partitionen verteilt.
Mit wachsendem Datenvolumen teilen sich physische Partitionen. Ihre Gesamtzahl steigt, während der insgesamt bereitgestellte Durchsatz unverändert bleibt. Dadurch sinkt das effektive Durchsatzlimit pro Partition, je größer die Datenmenge wird.
Für unsere speicherintensivsten, Blob-ähnlichen Anwendungsfälle entsteht so ein Fehlerszenario: Allein das Speicherwachstum senkt die Durchsatzkapazität aktiver Workloads, selbst wenn Anfrageraten und Nutzung stabil bleiben.
Figure 02C · Weak partitions
The limit falls below demand
A partition splits when it reaches its storage limit. Each child receives half the throughput capacity.
Getrennte Container helfen, diese Partitionsschlüssellimits und Probleme mit störenden Nachbarn teilweise zu lösen. Jedem getrennten Container lassen sich eigene, unabhängige physische Partitionen zuweisen. Nur die Workloads innerhalb desselben Containers können sich dann noch gegenseitig stören oder im Partitionsschlüsselraum überschneiden. Allerdings verursacht die Bereitstellung neuer Container Aufwand: Jeder Container erfordert eine eigene Kapazitätsplanung. Deshalb wollen wir Container nur dort anlegen, wo sich Dienste oder Anwendungsfälle sinnvoll voneinander abgrenzen lassen.
Für die kritischsten neuen Anwendungsfälle mit hohen Isolationsanforderungen begannen wir, getrennte Azure Cosmos DB-Container einzusetzen. Zusätzlich passte ein Skript zur Verwaltung schwacher Partitionen den Gesamtdurchsatz jedes Containers an die Anzahl seiner physischen Partitionen an. Zusammen konnten wir damit die bisherigen Probleme weitgehend vermeiden.
Da wir jedoch mit einem Standardcontainer begonnen hatten, blieben viele ältere, kritische Anwendungsfälle ohne Datenmigration leider an ihn gebunden. Zum Schutz kritischer Dienste, die weiterhin Daten im gemeinsam genutzten Standardcontainer benötigten, rieten wir Teams oft zu getrennten Containern für neue Dienste oder größere Produktfunktionen. Mit zunehmender Skalierung stieg die Last auf dem Standardcontainer, und Störungen im Produktivbetrieb häuften sich. Wir begannen, für jeden neuen Dienst und jede größere Produktfunktion eigene Container bereitzustellen, allein um den Standardcontainer vor zusätzlicher Last zu schützen.
Das verhinderte viele plattformweite Ausfälle. Die Auswirkungen blieben auf Ausfälle oder Beeinträchtigungen einzelner Funktionen oder Dienste begrenzt. Doch irgendwann machte sich der Aufwand für die Bereitstellung neuer Container bemerkbar:
Im alten, gemeinsam genutzten Standardcontainer veränderte ein neuer Anwendungsfall, der von 0 auf 1 Million QPS wuchs, die Gesamtnutzung wahrscheinlich kaum. Bei einem isolierten Container konnte ein solcher Anstieg von 0 auf 1 Million QPS dagegen das 1.000-Fache der Ausgangslast bedeuten, die zuvor nur durch interne Nutzungstests entstanden war.
Mit dedizierten Containern mussten die Teams nun daran denken, den erwarteten Durchsatz für ihre Produkteinführungen vorab bereitzustellen. Produktteams brachten ihre Funktionen oft in den Produktivbetrieb und vergaßen die vorzeitige Skalierung. So saßen wir am Einführungstag in Krisenrunden und versuchten, Container in letzter Minute hochzuskalieren.
Die Mischung von Workloads ist ein zentraler Vorteil von Multi-Tenant-Systemen, den wir mit dieser Containerstrategie zugunsten einer besseren Isolation verlieren.
Figure 03 · Lack of stable aggregate growth
The same workload, different headroom
1K RU/s during load testing → 1M RU/s at launch
Mit der Zeit erforderte auch die Überwachung dedizierter Container genaue Produktkenntnisse darüber, wann sich der Datenverkehr verlagern oder wachsen würde.
Wir können ein einfaches Skript auf der Steuerungsebene bereitstellen, das den Durchsatz jedes Containers anhand von Wachstumstrends erhöht. Ohne stabiles Wachstum der Gesamtnutzung musste sich für eine korrekte Skalierung aber praktisch jedes Produktteam bei OpenAI an der Kapazitätsplanung beteiligen.
Organisatorisch ließ sich das nicht skalieren: Die zuständigen Teams kümmerten sich mit sehr unterschiedlicher Sorgfalt um die Bereitstellung ihrer Azure Cosmos DB-Ressourcen.
Mit dem Wachstum der Organisation ließen sich diese betrieblichen Probleme immer schwerer bewältigen. Weil kein einzelnes zentrales Team die Planung übernahm, planten viele Teams unzureichend. Deshalb kehrten wir zu einer Multi-Tenant-Lösung zurück. Gemeinsam mit dem Azure-Team entwickelten wir Lösungen für die ursprünglichen Probleme der gemeinsam genutzten Umgebung:
Indem wir die am stärksten beanspruchten Schlüssel zuerst drosseln, verhindern wir, dass störende Nachbarn den gesamten verfügbaren Durchsatz einer physischen Partition verbrauchen. Das verringert den Effekt, bei dem ein einzelner stark beanspruchter Schlüssel die gesamte Plattform beeinträchtigen kann.
Ähnlich verhindert eine QoS-Einstufung von Anfragen, dass Offline-Workloads wie Masseneinfügungen oder Datenextraktionen den gesamten Durchsatz einer physischen Partition beanspruchen. Die Anfragen dieser Workloads erhalten eine niedrigere Priorität und werden deshalb zuerst gedrosselt. So vermeiden wir eine Drosselung der höher priorisierten, interaktiven Anfragen für die Menschen, die unsere Produkte nutzen.
Azure Cosmos DB bot bereits einen serverlosen Modus, der unser Problem mit schwachen Partitionen im Modus mit bereitgestelltem Durchsatz gelöst hätte. Er hatte jedoch einen großen Nachteil: Er skalierte nur anhand der Speichernutzung und begrenzte den RU-Verbrauch auf 5.000 RU/s pro physischer Partition. Das hätte zwar das Problem schwacher Partitionen gelöst, uns aber daran gehindert, RU-intensive Anwendungsfälle hochzuskalieren.
Das Azure Cosmos DB-Team entwickelte für uns einen verbesserten serverlosen Modus. Damit konnte jede physische Partition auf bis zu 10.000 RU/s skalieren und sich nicht nur anhand der Speichernutzung, sondern auch anhand des RU-Verbrauchs teilen. Dadurch mussten wir Container nicht mehr manuell überwachen und hochskalieren.
Dank dieser Änderungen konnten wir wieder einen einzigen gemeinsam genutzten Container einsetzen. Das hat die anfängliche und laufende Kapazitätsplanung für Produktteams erheblich vereinfacht, ohne die Isolation zwischen Workloads aufzugeben. In den Monaten seither gab es trotz beschleunigter Produktentwicklung keine Störungen durch Ressourcenbereitstellung oder störende Nachbarn.
Wenn Unternehmen die Größe von OpenAI erreichen, nehmen ihnen verwaltete Dienste weniger Verwaltungsarbeit ab. Viele Workloads werden zu Ausnahmefällen in der Infrastruktur des Cloud-Anbieters. Als OpenAI zu einem der größten Unternehmen mit Azure Cosmos DB wurde, brauchten wir für den erfolgreichen Betrieb einen anderen Ansatz: gemeinsame Tests und Dashboards, direkte Kommunikationskanäle zwischen den Entwicklungsteams und gemeinsame Verantwortung für Fehlerszenarien, die erst in unserer Größenordnung auftreten. Heute speichert unser größtes Azure Cosmos DB-Konto mehrere zehn Petabyte Daten und verarbeitet mehr als 20 Millionen QPS. Soweit wir wissen, sind wir das einzige Unternehmen, das Azure Cosmos DB in dieser Größenordnung einsetzt.
Ende 2024 zeigte ein regionaler Ausfall, warum diese Zusammenarbeit nötig war. Damals kam es in der primären Azure-Region eines unserer größten Azure Cosmos DB-Konten zu einem Stromausfall. Beim anschließenden Failover stellten wir fest, dass der Wechsel unserer größten Konten in eine sekundäre Region mehrere Stunden dauern würde. Weder unser Team noch das Azure Cosmos DB-Team hatte das in unserer Größenordnung getestet.
Etwa einen Monat später lösten wir beim Zurückschalten dieser Konten in die ursprüngliche Region einen weiteren schweren Ausfall aus. Eine Flut gleichzeitiger Verbindungsversuche überlastete das Azure Cosmos DB-Gateway in der ursprünglichen Region. Eine Besonderheit im Failover-Verhalten des Azure Cosmos DB-Clients im Gateway-Modus führte dazu, dass alle Clients sofort Verbindungen zur ursprünglichen Region aufbauten und Anfragen dorthin sendeten. Dabei war erst ein Teil der physischen Partitionen in diese Region zurückgekehrt.
Bei jedem Ausfall durchliefen sowohl OpenAI als auch Azure ihre Prozesse zur Störungsbehebung. Dabei identifizierten wir mehrere Ursachen und spielten Korrekturen ein. Diese Ausfälle waren jedoch Symptome eines größeren systemischen Problems im Zusammenspiel der Betriebs- und Testabläufe unseres Teams und des Azure Cosmos DB-Teams. Anfangs konnten wir dank verwalteter Dienste schnell vorankommen und uns auf Geschäftsziele konzentrieren. In großer Größenordnung nehmen uns verwaltete Dienste weniger Arbeit ab. Sie erfordern eine enge Verzahnung mit dem Dienstanbieter auf allen Ebenen, vom Design bis zum Betrieb.
Heute überwachen die Entwicklungsteams von Habitat und Azure gemeinsam jede Schicht von Habitat, von unseren Dienstschichten bis zu den physischen Speicherschichten von Azure Cosmos DB. Wir betreiben ein nicht produktiv genutztes Azure Cosmos DB-Konto in Produktionsgröße und testen Änderungen daran. Wir testen und verbessern SDKs gemeinsam und legen bewährte Vorgehensweisen für die Skalierung und Dimensionierung unserer Azure Cosmos DB-Konten fest. Unsere Entwicklungsteams erreichen einander direkt über Teams. Wir haben sogar Möglichkeiten geschaffen, uns gegenseitig direkt zu alarmieren, ohne die Supportstufen durchlaufen zu müssen.
Im vergangenen Jahr haben wir gemeinsam intensiv daran gearbeitet, die Skalierbarkeit der Steuerungsebene und des Gateways von Azure Cosmos DB zu verbessern. Zum Schutz der Metadatenpartition von Azure Cosmos DB haben wir einen Überlastschutz sowie Caching für verschiedene Metadaten im Gateway eingeführt. Verbindungspooling und die häufigere Wiederverwendung von Verbindungen entlasten das System angesichts unserer vielen Python-Prozesse von habitat-service. Außerdem haben wir Workloads nach Möglichkeit am selben Standort untergebracht und kritische Workloads über getrennte VPCs von Offline-Workloads isoliert.
Diese enge technische Zusammenarbeit war im vergangenen Jahr entscheidend für die Stabilität unserer Azure Cosmos DB. Jedes hinreichend große oder kritische System erfordert diese Art der Zusammenarbeit: Zuständigkeitsgrenzen überwinden und organisationsübergreifend arbeiten.
Wie in Teil I erwähnt, lautet unser Grundprinzip: Erst muss es funktionieren, dann schnell sein, dann kostengünstig. Zuerst Zuverlässigkeit, dann Leistung, dann Effizienz. Habitat war zu einer kritischen Infrastruktur für OpenAI geworden. Deshalb hatte Zuverlässigkeit oberste Priorität. Als sich das System 2025 stabilisierte und OpenAI weltweit expandierte, wurde die Latenz zum nächsten Engpass für die Menschen, die unsere Produkte nutzen, und für unsere Produktteams.
Rechen- und Speicherkapazität lagen oft in unterschiedlichen Regionen, sodass regionsübergreifende Anfragen unvermeidbar waren.
Figure 04 · Improving performance at a global scale
Global traffic
Compute and storage capacity were often located in different regions, making cross-region requests unavoidable.
Die geografischen Standorte der Rechen- und Speicherressourcen dienen nur der Veranschaulichung.
Die Latenz schwankte stark, je nachdem, wo die Daten gespeichert waren und wo die Anfrage verarbeitet wurde. Für schnelle Habitat-Anfragen setzten die Produktentwicklungsteams auf zusätzliches clientseitiges Caching, gebündelte Anfragen, möglichst wenige Datenbankzugriffe und die Verlagerung von Arbeit aus dem kritischen Pfad, wo immer das möglich war. Das führte zwar zu einigen interessanten Produktkonzepten, die robuster gegenüber Datenbankproblemen waren, es machte die Produkte aber auch komplexer und verlangsamte ihre Weiterentwicklung. Damit verfehlten wir das Ziel von Habitat: schnelle Produktentwicklung zu ermöglichen, ohne dass dafür Kenntnisse der Datenbank nötig sind.
Das ist 2026 ein wichtiger Schwerpunkt für uns. Wir haben eine mehrstufige Strategie entwickelt, um Lesezugriffe zu beschleunigen. Sie machen über 90 % der Habitat-Anfragen aus:
Für Personen und Organisationen mit Anforderungen an die Datenresidenz betreiben wir mehrere Azure Cosmos DB-Konten in verschiedenen Azure-Regionen weltweit. Auch ohne feste Anforderungen an die Datenresidenz verbessert die Speicherung nahe am Nutzungsort durch geringere Latenz das Nutzungserlebnis.
In der Praxis können wir das außerhalb von Anwendungsfällen mit Datenresidenzanforderungen nicht flächendeckend umsetzen, da die physische Cloud-Kapazität die Platzierung begrenzt. Unsere Datenbestände sind so groß, dass viele Regionen den enormen Speicherbedarf für ihren jeweiligen Anteil am Nutzungsverkehr nicht decken können.
In Regionen mit begrenzter Rechenzentrumskapazität, Stromversorgung oder Kühlung müssen wir manchmal direkt abwägen: Reservieren wir Kapazität für Speicher vor Ort und senken so die Habitat-Latenz? Oder nutzen wir diese Kapazität für zusätzliche Rechenleistung zur Anfrageverarbeitung und leiten Speicheranfragen weiter weg?
Bei der Datenplatzierung müssen wir deshalb oft zwischen zwei Produktanforderungen abwägen: A) optimierter Latenz für verschiedene Produkte und Nutzungsgruppen und B) verfügbarer Speicher- und Rechenkapazität.
Bei den latenzkritischsten Workloads, etwa den für jede Anfrage benötigten Authentifizierungsmetadaten, verkürzen wir die Round-Trip-Zeit (RTT) zwischen Rechenressourcen und Habitat. Dafür nutzen wir stark replizierte Azure Cosmos DB-Konten mit vielen sekundären Regionen. Einige Konten replizieren in alle 40 Azure-Regionen, in denen wir Anfragen verarbeiten; andere in mindestens eine Region pro Kontinent.
Wir mussten früher als geplant eine transparente Schicht für Datenverschiebung und Routing in Habitat einbauen, um Datenbestände zwischen Konten mit unterschiedlichen Replikationstopologien verschieben zu können.
Doch auch dieser Ansatz ist letztlich durch die Kapazität begrenzt und bleibt daher wenigen gezielt ausgewählten Datenbeständen vorbehalten.
Bei Habitat-Workloads überwiegen Lesezugriffe deutlich: Anwendungen rufen ständig Einstellungen, Zustände und andere Eigenschaften ab. Ein überproportional großer Teil des Datenverkehrs entfällt auf einen relativ kleinen Arbeitsdatenbestand häufig abgerufener Typen. Dadurch eignet sich das System besonders gut für verteiltes Caching. Caching verbessert bereits in einer einzelnen Region die Leistung. Regionsübergreifend verstärken sich diese Vorteile, weil sich damit die Latenz zwischen Regionen „verbergen“ lässt.
Wir haben eine verteilte Lookaside-Cache-Schicht in Habitat integriert. Sie speichert Kopien kürzlich abgerufener Objekte und Kanten auf Valkey-Servern in unserer Infrastruktur, nahe an den Orten, an denen Nutzungsanfragen verarbeitet werden. Solche Caches nutzen üblicherweise eine Gültigkeitsdauer (TTL), die zwischen Trefferquote und Datenaktualität abwägt. Kurze TTLs halten Daten aktueller, schwächen aber den Nutzen des Cachings: Einträge laufen schnell ab und müssen häufiger neu abgerufen werden. Längere TTLs verbessern die Trefferquote, liefern aber länger veraltete Daten. Das verschlechtert das Nutzungserlebnis und macht das Produktverhalten unvorhersehbar.
Ein anderer Ansatz besteht darin, Cache-Einträge ausdrücklich ungültig zu machen, wenn die Anwendung Schreibvorgänge bei denselben zugrunde liegenden Daten ausführt. Das verbessert die Aktualität zwischengespeicherter Daten erheblich, lässt sich aber nur schwer fehlerfrei umsetzen. Zudem bleiben Sonderfälle wie Teilausfälle, durch die veraltete Daten im Cache verbleiben. Bei Bereitstellungen über mehrere Regionen verschärfen weniger zuverlässige Netzwerkverbindungen diese Probleme. Hinzu kommen Wettlaufsituationen zwischen dem Cache und der Replikation des zugrunde liegenden Speichers. Ergänzende TTLs begrenzen zwar die maximale Veralterung, unterliegen aber weiterhin dem oben beschriebenen Zielkonflikt.
Wir haben einen weiterentwickelten Ansatz zur Invalidierung gewählt, der sich an Systemen wie TAO von Meta orientiert. Der integrierte Cache von Habitat nutzt Änderungsdatenerfassung (CDC) über den Änderungsfeed von Azure Cosmos DB, um Invalidierungen innerhalb von Sekunden weltweit zu verteilen. So erreichen wir hohe Trefferquoten bei geringer Datenveralterung. Ein Dienst in unserer Infrastruktur verarbeitet diesen Änderungsfeed und invalidiert Einträge in den lokalen Caches, sobald die zugrunde liegenden Daten aktualisiert werden. Beim nächsten Lesezugriff entsteht dadurch ein Cache-Fehltreffer, und der Cache wird mit der neuesten Datenversion gefüllt.
Wir haben die Latenz auch deutlich gesenkt, indem wir den Code des Habitat-Dienstes in Rust neu geschrieben und so dessen Overhead reduziert haben. Rust ist leistungsfähiger als die ursprüngliche Python-Implementierung.
Zusammen haben diese Änderungen unsere weltweite p50-Leselatenz von rund 120 ms auf rund 10 ms gesenkt. Das entspricht einer Verbesserung um den Faktor 12, seit wir uns Anfang dieses Jahres auf die Leistung konzentrieren.
Figure 05 · Improving performance at a global scale
p50 latency for reads
Three major design changes improved read latency
Caching hat die mittlere Latenz erheblich reduziert. Die langsamsten Anfragen müssen jedoch weiterhin nicht zwischengespeicherte Daten aus dem zugrunde liegenden Speicher abrufen, oft aus weit entfernten Regionen. Mit zusätzlichen Kapazitäten und effizienterer Speichernutzung wollen wir eine grundlegendere Herausforderung angehen: Daten von Anfang an näher an den Orten speichern, von denen aus sie abgerufen werden.
Zuverlässige, skalierbare CDC, also die Weitergabe von Datenbankänderungen an nachgelagerte Systeme, ist nicht nur für die Caching-Strategie von Habitat entscheidend. Pro Sekunde fließen Millionen erfasster Änderungen aus Tausenden Containern und Millionen Containerpartitionen durch mehr als hundert Kafka-Topics. Sie versorgen auch letztlich konsistente Sekundärindizes, Pipelines zur Datenverschiebung, Offline-Analysen und weitere Echtzeit-Produktfunktionen. Mit dem weltweiten Wachstum unserer Infrastruktur ist es immer komplexer geworden, Zeilenänderungen zu verfolgen. Will ein Client einen Callback registrieren, der bei Änderungen an Zeilen seines Typs ausgeführt wird, muss er zunächst unter anderem ermitteln, welche Cosmos-Konten und -Container seine Daten enthalten und welche CDC-Dienstbereitstellung in den jeweiligen Regionen diese Container überwacht. Außerdem müssen die Worker-Flotte des CDC-Dienstes, Kafka und der eigene Consumer passend skaliert werden. Unsere CDC-Plattform soll diese Details vor den Produktentwicklungsteams verbergen und stattdessen eine einfache Schnittstelle bieten, über die Clients eine Funktion für Änderungen an ihren Zeilen registrieren, wobei wir uns um die gesamte technische Anbindung und Skalierung kümmern.
Eine interne Steuerungsebene stellt kontinuierlich sicher, dass alle benötigten Container erfasst werden. Sie wählt die zu lesenden Cosmos-Regionen aus und verteilt die Arbeit effizient auf Tausende Worker in mehreren geografischen Regionen. Replikatregionen und niedriger priorisierte Feed-Lesezugriffe schützen latenzempfindliche Habitat-Anfragen vor den Workloads der Hintergrunddatenerfassung. In einer so großen Flotte treten ständig Worker-Abstürze, Containerprobleme, Fehler im Lease-Speicher, ungültige Konfigurationen und Ausfälle von Zielsystemen auf. Wir haben den Lebenszyklus der CDC-Worker und die Lease-Verwaltung robuster gemacht, Fehler zwischen Datenströmen isoliert und Schutzmechanismen für Konfigurationsänderungen und Rollouts eingebaut. Mit Dead-Letter-Queues verhindern wir, dass dauerhafte Zustellungsfehler nachfolgende Nachrichten blockieren. Regionale Ausfälle fangen wir ab, indem wir Leseprozesse auf andere Cosmos-Regionen umschalten. In unserer Größenordnung mussten wir sogar das CDC-SDK von Azure anpassen, um dessen Checkpointing effizienter zu machen. Dafür bündeln wir Checkpoints im Arbeitsspeicher und schreiben sie nur in regelmäßigen Abständen in den persistenten Speicher.
Habitat nutzt Azure Cosmos DB intern auf komplexe Weise. Sein Nutzen bestand aber immer darin, Infrastrukturdetails vor Produktentwicklungsteams zu verbergen und so die Produktentwicklung zu beschleunigen. Für unsere CDC-Plattform gilt dasselbe.
Ein kleines Team konnte eine schnell wachsende Speicherplattform betreuen, indem es die Speicher-API bewusst schlank hielt, den Python-Dienst weiter skalierte und die dringendsten technischen Herausforderungen priorisierte. Zuerst konzentrierten wir uns auf Zuverlässigkeit für geschäftskritische Produktanfragen, dann auf schnelle Lesezugriffe weltweit und schließlich auf einen effizienten Betrieb in großer Größenordnung. Dieser Ansatz ermöglichte schnelles Wachstum und neue Anwendungsfälle. Zugleich verbesserte er Zuverlässigkeit, Leistung und das Tempo der Produktentwicklung.
Um Habitat und den Gesprächsspeicher für die nächste Milliarde Menschen zu skalieren, müssen wir die schwierige Replikation unserer Daten über die gesamte Infrastruktur in noch größerem Maßstab bewältigen. Um dem vorzugreifen, entwickeln wir intern ein eigenes Online-Speichersystem im Exabyte-Maßstab, das für die umfangreichen Datentypen von Habitat optimiert ist. Das noch in Entwicklung befindliche System kombiniert den RocksDB Cloud-Kern von Rockset mit Raft-basierter Replikation. Es implementiert dasselbe Dokumentmodell wie Azure Cosmos DB, nutzt aber entkoppelten Speicher und physische Replikation, um den Bedarf an Flash-Kapazität zu senken. Dafür nehmen wir eine niedrigere maximale QPS-Rate in Kauf.
Wenn du an solchen Dateninfrastrukturprojekten in bisher unerreichter Größenordnung mitarbeiten möchtest, sieh dir unsere offenen Stellen an.


