ai newsFr., 23. Okt.
EilmeldungOpenAI skaliert Online-Speicher fuer ueber eine Milliarde ChatGPT-Nutzer

OpenAI skaliert Online-Speicher fuer ueber eine Milliarde ChatGPT-Nutzer

OpenAI hat die Speicherinfrastruktur Habitat mit zuverlaessigem Multi-Tenancy, globalem Caching und Azure Cosmos DB ausgebaut. Dadurch lassen sich Lesezugriffe fuer ChatGPT rund zwolfmal schneller abwickeln.

OpenAI NewsFreitag 19:00 · 23.10.DE20 Min. Lesezeit

Wie wir Azure Cosmos DB in unserer Anwendungs-Speicherplattform Habitat optimiert haben, um ein beispielloses Wachstum zu bewältigen.

Von Jon Lee, Hrachya Hakobyan und Chaomin Yu, Members of Technical Staff

Teilen Multi-Tenancy zuverlässig im großen Maßstab machen Multi-Tenancy zuverlässig im großen Maßstab machen Eine vereinfachte Einführung in Azure Cosmos DB Container Phase I: Verlassen auf Azure Cosmos DB Primitive Grenzen logischer Partitionsschlüssel Noisy Neighbors Schwache Partitionen Phase II: Versuch mit Workload-Isolation und Single-Tenancy Fehlendes stabiles aggregates Wachstum Kapazitätsplanung Phase III: Standardmäßige Multi-Tenancy mit Datenverschiebungsfähigkeit Hot-Key-Throttling Quality of Service (QoS) Serverless-Modus Bessere Zuverlässigkeit als gemeinsam verwaltete Dienste Verbesserung der Leistung in globalem Maßstab Daten nahe der Nutzer speichern, wenn Kapazität es erlaubt Nur die kleinsten und latenzempfindlichsten Datensätze breit replizieren Für alles andere auf integriertes Caching setzen Proxy-Overhead mit Rust reduzieren Skalierung Change Data Capture Ausblick Multi-Tenancy zuverlässig im großen Maßstab machen Eine vereinfachte Einführung in Azure Cosmos DB Container Phase I: Verlassen auf Azure Cosmos DB Primitive Grenzen logischer Partitionsschlüssel Noisy Neighbors Schwache Partitionen Phase II: Versuch mit Workload-Isolation und Single-Tenancy Fehlendes stabiles aggregates Wachstum Kapazitätsplanung Phase III: Standardmäßige Multi-Tenancy mit Datenverschiebungsfähigkeit Hot-Key-Throttling Quality of Service (QoS) Serverless-Modus Bessere Zuverlässigkeit als gemeinsam verwaltete Dienste Verbesserung der Leistung in globalem Maßstab Daten nahe der Nutzer speichern, wenn Kapazität es erlaubt Nur die kleinsten und latenzempfindlichsten Datensätze breit replizieren Für alles andere auf integriertes Caching setzen Proxy-Overhead mit Rust reduzieren Skalierung Change Data Capture Ausblick Alle OpenAI-Produkte benötigen schnellen und zuverlässigen Zugriff auf Daten, der von unserer Speicherplattform Habitat bereitgestellt wird. Sie muss innerhalb von Millisekunden auf über 1 Milliarde Nutzer in 40 geografischen Regionen reagieren. Unsere Infrastruktur sah sich einer außergewöhnlichen Herausforderung gegenüber: explosive Wachstumsraten und neue Anwendungsfälle zu unterstützen und dabei weiterhin Zuverlässigkeit, Leistung und die Entwicklungsgeschwindigkeit der Produktentwickler zu verbessern.

Die Lösung dieser Herausforderung erforderte ein Umdenken darüber, wie unsere Online-Speicherplattform entworfen und betrieben wurde. Wir bewerteten ständig Kompromisse und folgten dabei dem Muster: zuerst ausreichend zuverlässig werden für geschäftskritisches Produkttraffic, dann schnell genug für globale Nutzer und schließlich geschickt 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 bedient. 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 pushen, was viele als praktikabel betrachten 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 zu einem tragfähigen Multi-Tenancy-Ansatz mit gemeinsam verwalteten Containern und hervorragender Leseleistung bei massivem, globalem Maßstab.

Habitat ist die Online-Speicherplattform, die wir gebaut haben, damit OpenAI-Produkte schnell und zuverlässig auf benötigte Informationen zugreifen können.

Abspielen Request Response Changes (CDC) Clients

ChatGPT API Codex Interne Dienste Und mehr Habitat

Azure Cosmos DB Online-Speicher Nanobase Online-Speicher Valkey Caches Blob-Speicher Speicherressourcen CDC Services Change Data Capture Databricks Rockset Kafka Und mehr Multi-Tenancy zuverlässig im großen Maßstab machen Wenn Unternehmen wachsen, entwickeln sich Infrastrukturteams oft von single-tenant Systemen hin zu multi-tenant Systemen. Single-Tenancy, bei der jedes Produkt-Team sein eigenes System betreibt, reduziert den Auswirkungsradius und bietet klare Eigentumsgrenzen und Workload-Isolation. Mit wachsender Zahl von Teams und Produkten wird dies jedoch teuer im Betrieb. Single-Tenancy macht jedes Produkt-Team für die Kapazitätsplanung verantwortlich und führt oft zu erheblicher Überprovisionierung.

Umgekehrt überträgt Multi-Tenancy die Kontrolle auf ein zentrales Datenbank-Team mit besseren Kenntnissen und Erfahrungen bei der Bereitstellung von Ressourcen. Aber Multi-Tenancy schafft ihre eigenen Probleme, etwa Noisy-Neighbor-Probleme, bei denen ein Tenant einen ungewöhnlich großen Anteil der Ressourcen verbraucht und dadurch die anderen Tenants beeinträchtigt.

Bei Habitat mussten wir all diese Kompromisse abwägen, um ein zuverlässiges, effizientes Speichersystem im großen Maßstab zu entwerfen. Heute ist Habitat eine Multi-Tenant-Datenbankschicht für viele OpenAI-Produkte, aber wir haben einen kreisförmigen Weg durchlaufen: von einem einzigen Multi-Tenant-Azure-Cosmos-DB-Container über die Bereitstellung von Single-Tenant-Container-Backends zurück zur Multi-Tenant-Container als Standard. Wir stehen noch ganz am Anfang unserer Multi-Tenancy-Geschichte bei Habitat: Wir unterstützen eine große Zahl von Anwendungsfällen, konnten Investitionen in diesem Bereich jedoch aufsparen, indem wir wo immer möglich die integrierten Primitive von Azure Cosmos DB nutzten.

Bevor wir unsere Azure Cosmos DB Reise durchgehen, etwas grundlegender Kontext:

Ein Azure Cosmos DB Account definiert eine Reihe von Containern und eine Replikationsrichtlinie

Jeder Container wird mit einer bestimmten Menge an Durchsatz in Request Units pro Sekunde (RU/s) provisioniert

Daten innerhalb eines Containers werden auf typische Weise shardiert, indem nach einem angegebenen logischen Partitionsschlüssel partitioniert wird, mit dokumentierten Grenzen für Speicher und Durchsatz pro logischem Partitionsschlüssel

Azure Cosmos DB verteilt die logischen Partitionsschlüssel über weniger physische Partitionen und teilt sie in mehrere physische Partitionen auf, wenn der Speicher einen Schwellwert überschreitet oder der Container mehr physische Partitionen benötigt, um den provisionierten Durchsatz aufrechtzuerhalten

Wir ließen anfangs jeglichen Client-Traffic durch und verließen uns auf die angegebene Grenze von 10K RU/s pro logischem Partitionsschlüssel, um Backpressure zu erzeugen. Zu Beginn verwendeten wir auch nur einen Standard-Azure-Cosmos-DB-Container pro Account. Das funktionierte zunächst gut genug, erwies sich aber aus mehreren Gründen als problematisch:

Aufgrund früherer API-Entscheidungen haben wir die Auswahl des logischen Partitionsschlüssels direkt den Clients überlassen. Dadurch kam es häufig zu Kollisionen bei häufig verwendeten Partitionsschlüsseln wie user_id oder workspace_id über verschiedene Produktanwendungsfälle hinweg. Da sich diese Anwendungsfälle die Azure Cosmos DB Grenzen auf denselben logischen Partitionsschlüsseln teilten, beschränkte das Design den Gesamtdurchsatz für einen bestimmten Nutzer.

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 beanspruchten Organisation oder einem Hot Key, also einem Tenant, der unverhältnismäßig hohe Last erzeugt, die gesamte Datenbank lahmlegen konnten. erhebliche gemeinsame Anstrengungen wurden darauf verwendet, diese Schlüssel zu cachen und Leases in Cache-Misses einzubauen, damit die entstehende Cache-Miss-Last die zugrunde liegende Datenbank nicht beeinträchtigt.

Abbildung 02a · Grenzen logischer Partitionsschlüssel

Abspielen Cosmos DB Container Physische Partition PK: user-id-28104 Kapazität Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Memory / dreaming: Kapazität belegt Conversations Erfolgsrate Antworten in dieser Animationsschleife — Auth Erfolgsrate Antworten in dieser Animationsschleife — Memory / dreaming Erfolgsrate Antworten in dieser Animationsschleife — Noisy Neighbors Während dokumentierte Grenzen für Datengröße und Durchsatz für logische Partitionsschlüssel gelten, werden die Grenzen tatsächlich auf der physischen Partition durchgesetzt. In der Praxis bedeutet dies, dass logische Partitionen die 10.000 RU/s nicht erreichen können. Stattdessen teilen sie sich die Grenzen der physischen Partition mit jedem anderen logischen Partitionsschlüssel, der auf dieser physischen Partition liegen ist.

Dies ermöglichte es Noisy Neighbors, viele unbeteiligte logische Partitionsschlüssel zu beeinträchtigen, wobei Workloads, die auf Daten für Nischenproduktfunktionen operierten, die zentralen Login- und Registrierungsabläufe störten.

Abspielen Cosmos DB Container Physische Partition PKs: user-id-28104, user-id-10384 Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104: Kapazität belegt Conversations user-id-28104 Erfolgsrate Antworten in dieser Animationsschleife — Conversations user-id-10384 Erfolgsrate Antworten in dieser Animationsschleife — Schwache Partitionen Der Durchsatzgrenzwert für eine bestimmte physische 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.

Wenn das Datenvolumen wächst, teilen sich physische Partitionen auf, wodurch die Gesamtzahl der Partitionen steigt, während der insgesamt bereitgestellte Durchsatz unverändert bleibt. Das Ergebnis ist, dass das effektive Durchsatzlimit pro Partition mit wachsender Datenmenge sinkt.

Für unsere speicherintensivsten, blob-artigen Anwendungsfälle entsteht dadurch ein Fehlermodus, bei dem allein das Speicherwachstum die Durchsatzkapazität für aktive Workloads reduziert, selbst wenn Anfrageraten und Nutzung stabil bleiben.

Eine Partition teilt sich, wenn sie ihr Speicherlimit erreicht. Jedes Kind erhält die Hälfte der Durchsatzkapazität.

Nach der Aufteilung: 5K RU/s Kapazität × 2 Partitionen

Getrennte Container helfen dabei, diese Partitionsschlüssel-Limits und störende Nachbarn teilweise zu lösen. Durch die Verwendung separater Container kann jedem Container seine eigenen unabhängigen physischen Partitionen zugewiesen werden, und nur die variierenden Workloads auf dem jeweiligen Container selbst könnten störende Nachbarn darstellen oder sich im Partitionsschlüsselraum überschneiden. Nachteilig ist, dass die Bereitstellung neuer Container einen nicht unerheblichen Overhead verursacht, da jeder Container eine individuelle Kapazitätsplanung erfordert. Daher wollen wir Container nur an sinnvollen Service- oder Use-Case-Grenzen erstellen.

Wir begannen, separate Azure Cosmos DB Container für die kritischsten neuen Anwendungsfälle zu verwenden, die eine starke Isolation erforderten. In Kombination mit einem Skript zum Management schwacher Partitionen, das den insgesamt bereitgestellten Durchsatz jedes Containers mit dessen Anzahl physischer Partitionen synchronisierte, vermieden wir die meisten der Probleme, mit denen wir konfrontiert waren.

Da wir jedoch mit einem Standardcontainer begonnen hatten, blieben viele ältere kritische Anwendungsfälle leider ohne Datenmigration darin hängen. Um kritische Dienste zu schützen, die weiterhin auf Daten im gemeinsam genutzten Standardcontainer angewiesen waren, empfahlen wir Teams häufig, separate Container für neue Dienste oder größere Produktfunktionen zu erstellen. Mit unserem Wachstum stieg der Druck auf den Standardcontainer, und auch Produktionsvorfälle nahmen zu. Wir begannen, Container für jeden neuen Dienst oder jedes große Produktfeature bereitzustellen, nur um den Standardcontainer vor weiterer Last zu schützen.

Das bewahrte uns vor vielen ausfallspezifischen Ausfällen im gesamten System und begrenzte die Reichweite von Störungen auf Funktions- oder Dienstverschlechterungen. Allerdings holte uns der Overhead der Bereitstellung neuer Container schließlich ein:

Im alten gemeinsam genutzten Standardcontainer war es unwahrscheinlich, dass ein bestimmter neuer Anwendungsfall, der von 0 QPS auf 1 Million QPS anstieg, die gesamte aggregierte Nutzung wesentlich 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 bedeuten, die zuvor nur dem internen Dogfooding zugeschrieben wurde.

Mit dedizierten Containern mussten sich Teams nun merken, Container vor ihren Launches auf das erwartete Durchsatzniveau vorab bereitzustellen. Produktteams brachten ihre Funktionen häufig in Produktion und vergaßen das vorausgeplante Hochskalieren, sodass wir am Launch-Tag in SEV-Räumen saßen und versuchten, Container in letzter Minute hochzuskalieren.

Das ist ein zentraler Vorteil der Arbeitslastvermischung in Multi-Tenant-Systemen, den wir mit dieser Containerstrategie zugunsten besserer Isolation verlieren.

Abbildung 03 · Fehlendes stabiles aggregiertes Wachstum

1K RU/s während des Lasttests → 1M RU/s beim Launch

Mit der Zeit erforderte die Überwachung der Nutzung dedizierter Container zudem fundierte Produktkenntnisse darüber, wann sich der Verkehr verschieben oder wachsen würde.

Wir könnten ein grundlegendes Control-Plane-Skript bereitstellen, das den bereitgestellten Durchsatz jedes Containers auf Basis von Wachstumstrends hochskaliert, aber ohne stabiles aggregiertes Wachstum bedeutete das korrekte Skalieren im Wesentlichen, dass jedes Produktteam bei OpenAI in die Kapazitätsplanung eingebunden war.

Dies skalierte organisatorisch nicht, da die Sorgfalt, mit der das zuständige Team jedes Dienstes seine Azure Cosmos DB Bereitstellung berücksichtigte, stark variierte.

Mit dem Wachstum der Organisation wurden diese operativen Probleme schwer zu skalieren. Da kein einzelnes zentrales Team die Planung übernahm, planten viele Teams schlecht. Infolgedessen kehrten wir schließlich zu einer Multi-Tenant-Lösung zurück und arbeiteten eng mit dem Azure-Team zusammen, um Fixes für unsere ursprünglichen Multi-Tenancy-Probleme mit einem Shared-Tenancy-Design zu entwickeln:

Das Drosseln der am stärksten frequentierten Schlüssel zuerst verhindert, dass störende Nachbarn den gesamten verfügbaren Durchsatz einer bestimmten physischen Partition verbrauchen. Dies verringert den Noisy-Neighbor-Effekt, bei dem ein einzelner heißer Schlüssel eine Reichweite bis zum gesamten System haben kann.

Request Tiering QoS verhindert ebenso, dass Offline-Workloads wie Masseneinfügungen oder Datenextraktionen den gesamten Durchsatz einer bestimmten physischen Partition belegen. Indem den Anfragen dieser Workloads eine niedrigere Priorität zugewiesen wird, werden sie zuerst gedrosselt, was Drosselungen bei interaktiven Anfragen mit höherer Priorität, die Endnutzer bedienen, verhindert.

Azure Cosmos DB verfügte bereits über einen Serverless-Modus, der unser Problem schwacher Partitionen im Provisioned-Modus gelöst hätte. Allerdings gab es einen erheblichen Nachteil: Er skalierte ausschließlich auf Basis der Speichernutzung und begrenzte die RU-Nutzung auf 5K RU/s pro physischer Partition. Das hätte unser Problem schwacher 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 gebaut hat, erlaubte es jeder physischen Partition, bis zu 10K RU/s zu skalieren, und führte zusätzlich zu den speicherbasierten Aufteilungen auch RU-nutzungsbasierte Aufteilungen ein. So konnten wir aufhören, Container manuell zu überwachen und hochzuskalieren.

Mit diesen Änderungen konnten wir zur Nutzung eines einzigen gemeinsam genutzten Containers zurückkehren, was den Bedarf an vorausschauender oder fortlaufender Kapazitätsplanung für Produktteams erheblich vereinfacht hat, während die Isolation zwischen Workloads erhalten blieb. Seitdem hatten wir in den folgenden Monaten keinerlei Bereitstellungs- oder Noisy-Neighbor-Vorfälle, trotz sich beschleunigender Produktentwicklung.

Wenn Unternehmen auf OpenAIs Größenordnung wachsen, werden verwaltete Dienste weniger „verwaltet“. 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 Zehntel von Petabyte an Daten und bedient mehr als 20M QPS. Nach unserem Wissen sind wir das einzige Unternehmen, das Azure Cosmos DB in diesem Maßstab eingesetzt hat.

Ende 2024 zeigte ein regionaler Ausfall, 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 für ein Failover in eine sekundäre Region benötigen würden, etwas, das weder unser Team noch das Azure Cosmos DB Team in unserem Maßstab getestet hatte.

Etwa einen Monat später lösten wir beim Zurückführen dieser Konten in die ursprüngliche Region einen weiteren schwerwiegenden Ausfall aus. Ein Verbindungsexplosionsansturm auf die ursprüngliche Region überlastete das Azure Cosmos DB Gateway der ursprünglichen Region. Eine Besonderheit darin, wie der Azure Cosmos DB Client im Gateway-Modus 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ückverschoben 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 in der Art und Weise, wie unser Team und das Azure Cosmos DB Team arbeiteten und testeten. Anfangs ermöglichten uns verwaltete 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 Schicht von Habitat, von unseren Service-Schichten bis hinunter zu den physischen Speicherschichten von Azure Cosmos DB. Wir betreiben und testen Änderungen an einem produktionsgroßen, aber nicht-produktiven Azure Cosmos DB Konto. Wir testen und iterieren gemeinsam an SDKs und legen Best Practices fest, wie wir unsere Azure Cosmos DB Konten skalieren und dimensionieren. Wir haben direkten technischen Zugang zueinander über Teams und haben sogar Wege geschaffen, uns gegenseitig direkt zu benachrichtigen, ohne Support-Ebenen zu durchlaufen.

Im vergangenen Jahr wurde eine erhebliche Menge gemeinsamer Arbeit investiert, um die Skalierbarkeit von Azure Cosmos DB Control-Plane und Gateway zu verbessern, Lastschutz und Caching verschiedener Azure Cosmos DB Metadaten in deren Gateway hinzuzufügen, um die Azure Cosmos DB Metadaten-Partition zu schützen, den Druck unserer Flotte von Python habitat-service-Prozessen durch Connection Pooling und stärkere Wiederverwendung von Verbindungen zu reduzieren, Workloads nach Möglichkeit zu kolokalisieren und kritische Workloads von Offline-Workloads über verschiedene VPCs zu isolieren.

Dieses Maß an Co-Engineering war entscheidend für die Stabilität von Azure Cosmos DB bei uns im letzten Jahr. Jedes System, das groß oder kritisch genug ist, erfordert diese Art von Teamarbeit: das Durchbrechen von Verantwortungsgrenzen und die Zusammenarbeit über organisatorische Grenzen hinweg.

Wie bereits in Teil I erwaehnt, lautet unsere Kernphilosophie: erst funktionsfaehig machen, dann schnell, dann guenstig. Zuerst Zuverlaessigkeit, dann Leistung, dann Effizienz. Habitat war zu kritischer Infrastruktur fuer OpenAI geworden, daher hatte die Zuverlaessigkeit oberste Prioritaet. Als sich das System 2025 stabilisierte und OpenAI global expandierte, wurde die Latenz zur naechsten Einschraenkung fuer Nutzer und Produktteams.

Rechen- und Speicherkapazitaeten befanden sich haeufig in unterschiedlichen Regionen, wodurch Anfragen ueber Regionsgrenzen hinweg unvermeidlich waren.

Abbildung 04 · Verbesserung der Leistung auf globaler Ebene

Abspielen Compute Storage Veranschaulichende Routen · Laengere Wege benoetigen mehr Zeit Die geografischen Standorte von Compute und Storage dienen lediglich der Veranschaulichung.

Die Latenz variierte stark je nachdem, wo die Daten gespeichert waren und wo die Anfrage bearbeitet wurde. Damit Habitat-Anfragen performant blieben, griffen Produktentwickler auf zusaetzliches clientseitiges Caching, Request-Batching, die Minimierung von Datenbanklese- und Schreiboperationen sowie darauf zurueck, Arbeit wo immer moeglich vom kritischen Pfad zu verlagern. Zwar fuehrte dies zu einigen interessanten Produktdesigns, die resilienter gegen Datenbankprobleme waren, doch es machte die Produkte auch komplexer und verlangsamte die Produktiteration: ein Versagen des Ziels von Habitat, eine schnelle Produktentwicklung zu foerdern und das Verstaendnis der Datenbank ueberfluessig zu machen.

Das war 2026 ein grosser Schwerpunkt fuer uns, und wir haben eine mehrschichtige Strategie zur Optimierung der Leseleistung entwickelt, die ueber 90% der Habitat-Anfragen ausmacht:

Wir betreiben mehrere Azure Cosmos DB Accounts in verschiedenen Azure-Regionen weltweit fuer Nutzer mit Anforderungen an die Datenaufbewahrung. Auch fuer Nutzer ohne strenge Anforderungen an den Datenstandort verbessert die Platzierung der Daten in der Naehe der Nutzer das Erlebnis durch geringere Latenz.

In der Praxis laesst sich dies ausserhalb von Datenaufbewahrungsszenarien nicht flaechendeckend umsetzen, da die Platzierung durch die physische Cloud-Kapazitaet begrenzt ist. Wegen der Groesse unserer Datenmengen koennen viele Regionen den massiven Speicherbedarf, der fuer ihren proportionalen Anteil am Nutzerverkehr erforderlich waere, nicht aufnehmen.

In Regionen mit begrenzter Rechenzentrumskapazitaet, Strom- oder Kuehlversorgung stehen wir manchmal vor einem direkten Zielkonflikt: Kapazitaet fuer gemeinsam lokalisierten Speicher und geringere Habitat-Latenz reservieren, oder diese Kapazitaet fuer zusaetzliches Serving-Compute nutzen und Speicheranfragen weiter entfernt leiten.

Folglich sind Platzierungsentscheidungen haefufig ein produktspezifischer Kompromiss zwischen: A) der Optimierung der Latenz fuer verschiedene Produkte und Nutzergruppen und B) reiner Speicher- und Rechenkapazitaet.

Fuer die Latenz-kritischsten Workloads, etwa Authentifizierungsmetadaten, die bei jeder Anfrage benoetigt werden, verringern wir die RTT zwischen Compute und Habitat durch stark replizierte Azure Cosmos DB Accounts mit zahlreichen sekundaeren Regionen. Manche Accounts replizieren in alle 40 Azure-Regionen, in denen wir Traffic bedienen; andere replizieren in mindestens eine Region pro Kontinent.

Wir mussten eine transparente Schicht fuer Datenverschiebung und Routing in Habitat frueher als gewuenscht aufbauen, damit wir Datensaetze zwischen Accounts mit unterschiedlichen Replikationstopologien verschieben koennen.

Auch dieser Ansatz stoesst letztlich an Kapazitaetsgrenzen und ist daher auf einige wenige handverlesene Datensaetze beschraenkt.

Habitat-Workloads sind ueberwaiegend leseintensiv, wobei Anwendungen staendig Einstellungen, Zustaende und andere Eigenschaften abrufen; ein unverhaeltnismaessig grosser Teil des Traffic entfaellt auf eine relativ kleine Menge haeufig abgerufener Typen. Das macht das System besonders gut fuer verteiltes Caching geeignet. Waehrend Caching die Leistung in Infrastruktur mit einer einzigen Region verbessert, potenziert sich der Nutzen beim Arbeiten ueber mehrere Regionen, wo es genutzt werden kann, um die regionsuebergreifende Latenz zu ueberdecken.

Wir haben eine verteilte Lookaside-Caching-Schicht in Habitat integriert. Sie speichert Kopien kuerzlich abgerufener Objekte und Kanten auf Valkey-Servern, die in unserer Infrastruktur nahe dem Ort platziert sind, an dem Nutzeranfragen verarbeitet werden. Typischerweise arbeiten solche Caches mit einer TTL, die einen Kompromiss zwischen Trefferquote und Aktualitaet darstellt. Kurze TTLs reduzieren die Veraltung, schwaechen aber den positiven Effekt des Cachings, da Eintraege schnell aus dem Cache ablaufen und haeufiger neu abgerufen werden muessen. Laengere TTLs verbessern die Trefferquote, liefern aber ueber laengere Zeitraeume veraltete Daten, was zu schlechten Nutzererlebnissen und unvorhersehbarem Produktverhalten fuehrt.

Ein anderer Ansatz besteht darin, explizite Invalidierungsoperationen auszufuehren, wenn die Anwendung Schreiboperationen auf denselben zugrunde liegenden Daten durchfuehrt. Dies verbessert die Frische der zwischengespeicherten Daten erheblich, ist aber schwer korrekt umzusetzen. Zudem bleibt er anfaellig fuer diverse Randfaelle wie Teilausfaelle, die den Cache veraltet zuruecklassen. Multi-Region-Deployments verschärfen diese Probleme noch, mit unzuverlaessigeren regionsuebergreifenden Netzwerken und dem Wettrennen des Cache mit der Replikation des zugrunde liegenden Speichers. Die Kombination mit TTLs begrenzt die maximale Veraltung, leidet aber weiterhin unter dem oben beschriebenen TTL-Kompromiss.

Wir haben einen fortgeschritteneren Ansatz zur Invalidierung gewaehlt, in den Fussstapfen von Systemen wie Metas TAO. Habitations integrierter Cache stuetzt sich auf Change Data Capture (CDC) ueber 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 verarbeitet diesen Change Feed und fuehrt Invalidierungen an den lokalen Caches durch, sobald die zugrunde liegenden Daten aktualisiert werden. Dies fuehrt beim naechsten Lesezugriff zu einem Miss, und der Cache wird mit der neuesten Version der Daten gefuellt.

Ausserdem haben wir die Latenz erheblich reduziert, indem wir Overhead im Habitat-Dienst selbst beseitigt haben, indem wir den Code in Rust neu geschrieben haben, das performanter ist als das urspruengliche Python.

Insgesamt haben diese Aenderungen unser globales Lesep50 von rund 120 ms auf rund 10 ms gesenkt, eine Verbesserung um das Zwolffache, seit wir Anfang des Jahres den Fokus auf Leistung gelegt haben.

Abbildung 05 · Verbesserung der Leistung auf globaler Ebene

Drei wesentliche Designaenderungen verbesserten die Leselatenz

1 2 3 kritische Daten in nahegelegene Regionen repliziert In-Region-Caching eingefuehrt Rust-Rewrite eingefuehrt Waehrend Caching die mediane Latenz drastisch gesenkt hat, erfordern die langsamsten Anfragen weiterhin das Abrufen nicht zwischengespeicherter Daten aus dem zugrunde liegenden Speicher, oft ueber weit entfernte Regionen. Je mehr Kapazitaet verfuegbar wird und je besser wir die Speichereffizienz verbessern, wollen wir eine grundlegendere Herausforderung angehen: Daten von Anfang an naeher an dem Ort zu platzieren, an dem auf sie zugegriffen wird.

Zuverlaessiges und skalierbares CDC, also die Weitergabe von Datenbankaenderungen an nachgelagerte Systeme, ist eine kritische Komponente weit ueber Habitations Caching-Strategie hinaus. Millionen erfasster Aenderungen pro Sekunde, verteilt auf tausende Container und Millionen von Containerpartitionen, laufen durch mehr als hundert Kafka-Topics und speisen damit auch schliesslich konsistente Sekundaerindizes, Datenverschiebe-Pipelines, Offline-Analysen und andere Echtzeit-Produktfunktionen. Da sich unsere zugrunde liegende Infrastruktur global ver groessert hat, ist es zunehmend komplex geworden, auf Zeilenaenderungen zu hoeren.

Wenn ein Client einen Callback registrieren moechte, der immer dann ausgefuehrt wird, wenn Zeilen seines Typs geaendert werden, muss er unter anderem zunaechst bestimmen, welche Cosmos-Accounts und Container seine Daten enthalten, welche CDC-Service-Deployment in den jeweiligen Regionen diese Container abhoert, und die CDC-Service-Worker-Flotte, Kafka und seinen Consumer entsprechend skalieren. Unsere CDC-Plattform zielt darauf ab, all dies von Produktentwicklern fernzuhalten und stattdessen eine einfache Oberflaeche bereitzustellen, auf der Clients eine Funktion registrieren, die bei geaenderten Zeilen aufgerufen werden soll, waehrend wir uns um die gesamte Klempnerei und Skalierung kuemmern.

Eine interne Control Plane stellt kontinuierlich sicher, dass alle erforderlichen Container erfasst werden, waehlt aus, aus welchen Cosmos-Regionen gelesen wird, und verteilt die Arbeit effizient auf tausende Worker ueber mehrere geografische Regionen. Die Nutzung von Replikationsregionen und Lesevorgaengen mit niedrigerer Prioritaet schuetzt den latenzempfindlichen Habitat-Traffic vor Hintergrund-Erfassungs-Workloads. Worker-Abstuerze, Containerprobleme, Lease-Store-Fehler, ungueltige Konfigurationen und Ziel-Ausfaelle treten bei einer so grossen Flotte staendig auf.

Wir haben den CDC-Worker-Lebenszyklus und das Lease-Handling gehaertet, Fehler zwischen Streams isoliert und Schutzmassnahmen fuer Konfigurationsaenderungen und Rollouts geschaffen. Wir nutzen Dead Letter Queues, um Head-of-Line-Blocking durch dauerhafte Zustellfehler zu verhindern. Regionale Ausfaelle tolerieren wir, indem wir Leser auf alternative Cosmos-Regionen umschalten.

Bei unserem Massstab mussten wir sogar Aenderungen am CDC SDK von Azure vornehmen, um dessen Checkpointing-Implementierung effizienter zu machen, indem Checkpoints im Speicher gebuendelt und nur periodisch geleert werden.

Auch wenn Habitations interne Nutzung von Azure Cosmos DB komplex ist, war ihr Nutzenversprechen schon immer, Produktentwicklern Infrastruktureinzelheiten abzunehmen, um die Produktentwicklungsgeschwindigkeit zu verbessern, und unsere CDC-Plattform bildet da keine Ausnahme.

Ein kleines Team konnte eine rasant wachsende Speicherplattform unterstützen, indem die Storage-API bewusst schmal gehalten wurde, der Python-Dienst kontinuierlich skaliert wurde und die dringendsten technischen Herausforderungen priorisiert wurden. Zuerst konzentrierten wir uns auf die Zuverlässigkeit des geschäftskritischen Produktverkehrs, danach auf die Leseleistung für Nutzer weltweit und schließlich auf die Betriebseffizienz im großen Maßstab. Dieser Ansatz ermöglichte schnelles Wachstum und neue Anwendungsfälle, während sich Zuverlässigkeit, Performance und die Entwicklungsgeschwindigkeit des Produkts verbesserten.

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 Maßstab über unsere gesamte Infrastruktur zu replizieren. Um dem zuvorzukommen, bauen wir derzeit ein eigenes Inhouse-Online-Speichersystem im Exabyte-Maßstab, das auf umfangreiche Habitat-Datentypen optimiert ist. Dieses sich in der Entwicklung befindende System kombiniert den RocksDB-Cloud-Kern von Rockset mit Raft-basierter Replikation. Es implementiert dasselbe Dokumentenmodell wie Azure Cosmos DB, nutzt jedoch entkoppelten Speicher und physische Replikation, um den benötigten Flash-Speicherplatz zu reduzieren, was mit einer geringeren maximalen qps erkauft wird.

Wenn Sie an dieser Art von Data-Infrastructure-Projekten in einem Frontier-Maßstab mitarbeiten möchten, werfen Sie einen Blick auf unsere offenen Stellen.

Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.

Original (Englisch) anzeigen

How we optimized Azure Cosmos DB in our application storage platform, Habitat, to manage unprecedented growth.

By Jon Lee, Hrachya Hakobyan, and Chaomin Yu, Members of Technical Staff

Share Making multi-tenancy reliable at scale Making multi-tenancy reliable at scale A simplified primer on Azure Cosmos DB containers Phase I: Relying on Azure Cosmos DB primitives Logical partition key limits Noisy neighbors Weak partitions Phase II: Trying workload isolation and single-tenancy Lack of stable aggregate growth Capacity planning Phase III: Using multi-tenancy by default, with data movement capability Hot key throttling Quality of Service (QoS) Serverless mode Better reliability as co-managed services Improving performance at a global scale Store data near users when capacity allows Broadly replicate only the smallest and most latency-sensitive datasets Rely on integrated caching for everything else Reduce proxy overhead with Rust Scaling Change Data Capture Looking ahead Making multi-tenancy reliable at scale A simplified primer on Azure Cosmos DB containers Phase I: Relying on Azure Cosmos DB primitives Logical partition key limits Noisy neighbors Weak partitions Phase II: Trying workload isolation and single-tenancy Lack of stable aggregate growth Capacity planning Phase III: Using multi-tenancy by default, with data movement capability Hot key throttling Quality of Service (QoS) Serverless mode Better reliability as co-managed services Improving performance at a global scale Store data near users when capacity allows Broadly replicate only the smallest and most latency-sensitive datasets Rely on integrated caching for everything else Reduce proxy overhead with Rust Scaling Change Data Capture Looking ahead All OpenAI products require fast, reliable access to data, provided by our storage platform, Habitat. It has to respond in milliseconds to over 1 billion users across 40 geographic regions. Our infrastructure faced an extraordinary challenge: supporting explosive growth and new use cases while continuing to improve reliability, performance, and product developer velocity.

Solving that challenge required rethinking how our online storage platform was designed and operated. We constantly evaluated tradeoffs, following the pattern of: first become reliable enough for mission-critical product traffic, then fast enough for global users, and, finally, to deftly operate at massive scale.

This two-part series explains how we developed Habitat from a simple client library and database into a globally distributed storage platform serving over 500 petabytes of data. Part I focused on Habitat’s evolution from a Python client to a service. It also covered the techniques that allowed us to push a Python-based service far beyond what many would consider practical.

In this post, Part II, we walk through how we scaled the underlying storage component: Azure Cosmos DB. Through a close working partnership with the Azure team, we arrived at a viable multi-tenancy approach of co-managed containers, with excellent read performance at massive, global scale.

Habitat is the online storage platform we built so OpenAI products can quickly and reliably access needed information.

Play Request Response Changes (CDC) Clients

ChatGPT API Codex Internal services And more Habitat

Azure Cosmos DB Online storage Nanobase Online storage Valkey Caches Blob storage Storage resources CDC Services Change Data Capture Databricks Rockset Kafka And more Making multi-tenancy reliable at scale As companies scale, infra teams often evolve from running single-tenant systems to multi-tenant ones. Single-tenancy, where every product surface team maintains their own system, reduces blast radius and provides easy ownership boundaries and workload isolation. As teams and product surfaces grow, this becomes expensive to run. Single-tenancy makes every product team responsible for capacity planning and often results in significant over-provisioning.

Conversely, multi-tenancy assigns control to a centralized database team, with better knowledge and experience for provisioning resources. But multi-tenancy creates its own problems, such as noisy-neighbor issues, where one tenant consumes an unusually large share of the resources to the detriment of the other tenants.

In Habitat, we had to weigh all these tradeoffs to design a reliable, efficient, storage system at scale. Today, Habitat is a multi-tenant database layer for many OpenAI products, but we went through a circular journey from using one multi-tenant Azure Cosmos DB container to provisioning single-tenant backing containers to reverting to a multi-tenant container by default. We’re still very early in our multi-tenancy story on Habitat: we support a large set of use cases, but we’ve been able to defer investment in this area by leveraging Azure Cosmos DB’s built-in primitives wherever possible.

Before walking through our Azure Cosmos DB journey, some basic context:

An Azure Cosmos DB account defines a set of containers and a replication policy

Each container is provisioned for a certain amount of throughput in request units per second (RU/s)

Data within each container is stored in a typical shard-by-specified-logical-partition-key manner with documented limits on storage and throughput per logical partition key

Azure Cosmos DB handles sharding logical partition keys across fewer backing physical partitions, splitting them into multiple physical partitions when storage exceeds a threshold or when the container requires more physical partitions to sustain the provisioned throughput

We originally allowed any client load through, relying on the stated 10K RU/s limit per logical partition key to provide back pressure. When we first set out, we also used only one default Azure Cosmos DB container in each account. This worked well enough, but proved problematic for a few reasons:

Due to early API choices, we exposed logical partition key selection directly to clients. As a result, we have frequent collisions on commonly used partition keys, such as user_id or workspace_id, across different product use cases. Because these use cases shared Azure Cosmos DB limits on the same logical partition keys, the design constrained overall throughput for a given user.

One common problem we faced in scaling Postgres before we moved to Habitat and Azure Cosmos DB was the ability for many requests from a very hot organization or hot key—one tenant generating a disproportionately high load—to take out the whole database. Significant collective effort was spent to cache those keys and build leases into cache misses so that the resulting cache miss load does not stomp on the underlying database.

Figure 02a · Logical partition key limits

Play Cosmos DB container Physical partition PK: user-id-28104 Capacity Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Memory / dreaming: capacity used Conversations Success rate Responses returned in this animation loop — Auth Success rate Responses returned in this animation loop — Memory / dreaming Success rate Responses returned in this animation loop — Noisy neighbors While documented data size and throughput limits apply to logical partition keys, the physical partition is actually where limits are enforced. In practice, this means logical partitions are unable to reach 10,000 RU/s. Instead, they share the physical partition’s limits with every other logical partition key colocated on that physical partition.

This allowed noisy neighbors to impact many unrelated logical partition keys, with workloads operating on data for niche product features disrupting core login and signup flows.

Play Cosmos DB container Physical partition PKs: user-id-28104, user-id-10384 Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104: capacity used Conversations user-id-28104 Success rate Responses returned in this animation loop — Conversations user-id-10384 Success rate Responses returned in this animation loop — Weak partitions The throughput limit for a given physical partition is also variable, dependent on the total throughput assigned to the Azure Cosmos DB container. The container’s provisioned throughput is distributed roughly evenly across the physical partitions for that container.

As data volume grows, physical partitions split, increasing the total partition count while total provisioned throughput remains unchanged. The result is that the effective throughput limit per partition shrinks as data size grows.

For our most storage-heavy, blob-like use cases, this creates a failure mode where storage growth alone reduces throughput capacity for active workloads even while request rates and usage remain stable.

A partition splits when it reaches its storage limit. Each child receives half the throughput capacity.

After split: 5K RU/s capacity × 2 partitions

Separate containers help partially solve these partition key limits and noisy neighbors. By using separate containers, each can be assigned its own independent physical partitions and only the varying workloads on that container itself could be noisy neighbors or overlap in partition key space. On the downside, there is non-zero overhead in provisioning new containers—each container requires individualized capacity planning—so we want to create containers only at a service or use case boundary that makes sense.

We began to use separate Azure Cosmos DB containers for the most critical new use cases requiring strong isolation. Combined with a script for managing weak partitions by synchronizing the total throughput provisioned for each container with its number of physical partitions, we mostly avoided all the problems we were facing.

However, since we started with a default container, many legacy critical use cases were unfortunately stuck there without a data migration. To protect critical services that still relied on data in the shared default container, we often advised teams to create separate containers for new services or major product features. As we scaled, pressure on the default container grew and production incidents also grew. We began to provision containers for any new service or large product feature just to protect the default container from more load.

This saved us from many site-wide outages, limiting the blast radius to feature or service degradation outages. However, the overhead of provisioning new containers eventually caught up to us:

In the old shared default container, any particular new use case going from 0 QPS to 1 million QPS was unlikely to significantly shift the total aggregate usage. On an isolated container however, a new use case going from 0 to 1 million QPS might represent 1,000x more load than the baseline attributed to only internal dogfooding.

With dedicated containers, teams now needed to remember to pre-provision containers to the expected level of throughput for their launches. Product teams would often ship their features to production and forget to pre-scale and we’d sit in SEV rooms on launch day attempting to scale up containers last minute.

This is a core workload mixing benefit of multi-tenant systems that we lose in favor of better isolation with this container strategy.

Figure 03 · Lack of stable aggregate growth

1K RU/s during load testing → 1M RU/s at launch

Over time, monitoring usage of dedicated containers also required intimate product knowledge of when traffic would shift or grow.

We can provide a basic control plane script to scale up the provisioned throughput for each container based on growth trends, but without the stable aggregate growth, scaling these correctly enabled essentially every product team at OpenAI to be involved in capacity planning.

This failed to scale organizationally with a wide variance in how much diligence each service’s owning team paid to their Azure Cosmos DB provisioning.

As the organization grew, those operational problems became difficult to scale. No lone, central team doing planning resulted in many teams planning poorly. As a result, we ended up shifting back towards a multi-tenant solution and worked closely with the Azure team to engineer fixes for our original multi-tenancy issues with a shared-tenancy design:

Throttling the hottest keys first prevents noisy neighbors from consuming all of the available throughput for a given physical partition This reduces the noisy neighbor effect, where one hot key can have a site-wide blast radius.

Request tiering QoS similarly prevents offline workloads, such as bulk inserts or data extracts from utilizing all of the throughput for a given physical partition. By assigning the requests for these workloads a lower priority, they are throttled first, preventing throttling on higher priority, interactive requests that serve end users.

Azure Cosmos DB already had a serverless mode that would’ve fixed our weak partitions problem with the provisioned mode. However, there was a major downside: it scaled based on storage usage only and limited RU usage to 5K RU/s per physical partition. This would solve our weak partitions problem, but prevent us from scaling up RU-heavy use cases.

A new improved serverless mode Azure Cosmos DB team built for us allowed every physical partition to scale up to 10K RU/s and introduced RU-usage-based splits in addition to storage-usage-based splits allowed us to stop monitoring and scaling up containers manually.

With these changes, we’ve been able to shift back to using one shared container which has significantly simplified the need to do any upfront or continued capacity planning for product teams while preserving isolation between workloads. We have not had any provisioning or noisy neighbor incidents in the months since, despite accelerating product development.

When companies grow to OpenAI’s scale, managed services become less “managed.” Many workloads become outliers in the cloud provider’s infrastructure. As OpenAI became one of the largest users of Azure Cosmos DB, operating it successfully required a different model: shared tests, shared dashboards, direct engineering channels, and joint ownership of failure modes that only appear at our scale. Today, our largest Azure Cosmos DB account stores 10s of petabytes of data and serves more than 20M QPS. To our knowledge, we are the only company which has deployed Azure Cosmos DB at this scale.

In late 2024, a regional outage made it clear why this collaboration was necessary. At the time, a power outage struck the primary Azure region for one of our largest Azure Cosmos DB accounts. In the resulting failover exercise, we realized that our largest accounts would take several hours to fail over to a secondary region, something neither our team nor the Azure Cosmos DB team had tested at our scale.

About a month later, while failing those accounts back to the original region, we triggered another serious outage. A connection stampede to the original region overwhelmed the original region’s Azure Cosmos DB gateway. A quirk in how the gateway-mode Azure Cosmos DB client handled failovers caused all clients to immediately establish connections and send requests to the original region, even though only a subset of physical partitions had moved back to that region.

In each outage, both OpenAI and Azure ran through their incident management processes, with a number of root causes identified and fixes deployed. But these outages were symptoms of a larger systemic issue between the way our team and Azure Cosmos DB team operated and tested. At first, leveraging managed services allowed us to move fast and focus on business goals. At scale, managed services become less managed and require deep integrations with the service provider at all levels from design to operations.

Today, the Habitat and Azure engineering teams jointly monitor every layer of Habitat from our service layers down into Azure Cosmos DB’s physical storage layers. We operate and test changes against a production-sized but non-production Azure Cosmos DB account. We test and iterate on SDKs together and determine best practices for how we scale and size our Azure Cosmos DB accounts. We have direct engineering access to each other through Teams and have even exposed ways to directly page each other without going through support tiers.

A substantial amount of joint work has occurred over the last year to improve Azure Cosmos DB control-plane and gateway scalability, adding load protection and caching of various Azure Cosmos DB metadata in their gateway to protect the Azure Cosmos DB metadata partition, reducing pressure from our fleet of Python habitat-service processes through connection pooling and higher connection reuse, colocating workloads where possible, and isolating critical workloads from offline workloads via different VPCs.

This level of co-engineering has been critical to our Azure Cosmos DB stability in the last year. Any system large or critical enough necessitates this kind of teamwork: breaking down ownership walls and operating across organizational boundaries.

As mentioned in Part I, our core philosophy is: make it work, make it fast, make it cheap. Reliability first, then performance, then efficiency. Habitat had become critical infrastructure for OpenAI, so the first priority was making it reliable. As the system stabilized in 2025 and OpenAI expanded globally, latency became the next constraint for users and product teams.

Compute and storage capacity were often located in different regions, making cross-region requests unavoidable.

Figure 04 · Improving performance at a global scale

Play Compute Storage Illustrative routes · Longer journeys take more time Geographical locations of compute and storage are for illustrative purposes only.

Latency varied widely depending on where the data was stored and where the request was served. To make Habitat requests performant, product developers relied on additional client-side caching, request batching, minimizing database reads and writes, and moving work off the critical path wherever possible. While this led to some interesting product designs that were more resilient to database issues, it also made products more complex and slowed down product iteration: a failure to achieve Habitat’s goal to encourage fast product development and not needing to understand the database.

This has been a big focus for us in 2026, and we’ve developed a layered strategy for optimizing read performance, which represents over 90% of Habitat’s requests:

We have a number of Azure Cosmos DB accounts in different Azure regions around the world for users with data residency requirements. Even for users without hard data residency requirements, colocating data near users also improves their experience through lower latency.

In practice, we’re not able to achieve this broadly outside of data residency use cases because placement is constrained by physical cloud capacity. Due to the size of our data sets, many regions cannot accommodate the massive storage footprint required for their proportional share of user traffic.

In regions with limited datacenter capacity, power, or cooling, we sometimes face a direct tradeoff: reserve capacity for colocated storage and lower Habitat latency, or use that capacity for additional serving compute and route storage requests farther away.

As a result, placement decisions are often a product tradeoff between: A) optimizing latency for different products and user subsets, and B) raw storage and compute capacity.

For the most latency-critical workloads, like authentication metadata that is needed on every request, we reduce RTT between compute and Habitat by using highly replicated Azure Cosmos DB accounts with many secondary regions. Some accounts replicate to all 40 Azure regions where we serve traffic; others replicate to at least one region per continent.

We’ve had to build a transparent data movement and routing layer into Habitat earlier than desired so that we can move datasets between accounts with different replication topologies.

However, this approach is also ultimately capacity constrained and therefore limited to a few hand-selected datasets.

Habitat workloads are overwhelmingly read-heavy, with applications constantly fetching settings, state, and other properties; a disproportionate amount of traffic is attributable to a relatively small working set of frequently accessed types. This makes the system particularly well suited for distributed caching. While caching improves performance in single-region infrastructure, the improvements compound when working across regions, where it can be used to “hide” cross-region latency.

We’ve integrated a distributed lookaside caching layer into Habitat. It stores a copy of recently accessed objects and edges in Valkey servers placed in our infrastructure close to where user requests are processed. Typically, these sorts of caches use a TTL that trade off between hit rate and staleness. Short TTLs reduce staleness but blunt the positive impact of caching, because items expire out of the cache quickly and must be retrieved more frequently. Longer TTLs improve hit rates, but serve stale data for extended periods, leading to poor user experiences and unpredictable product behavior.

Another approach is to execute explicit invalidation operations when the application is performing write operations on the same underlying data. This dramatically improves the freshness of cached data, but can be hard to get right. It also remains subject to a number of edge cases, like partial failures, which leave the cache stale. Multi-region deployments compound these problems, with less reliable cross-region networks, and the cache racing with the replication of the underlying storage. Complementing this with TTLs bounds the maximum staleness, but still suffers from the same underlying TTL trade-off discussed above.

We’ve taken a more advanced approach to invalidation, following in the footsteps of systems like Meta’s TAO. Habitat’s integrated cache relies on change data capture (CDC) through Azure Cosmos DB’s change feed to propagate invalidations globally within seconds, achieving high hit rates while keeping staleness low. A service in our infrastructure consumes this change feed and executes invalidations to the local caches when the underlying data is updated. This causes a miss the next time the data is read, and the cache is filled with the latest version of the data.

We’ve also significantly reduced latency by removing overhead in the Habitat service itself through rewriting the code in Rust, which is more performant than the original Python.

Together, these changes have reduced our overall global read p50 from roughly 120 ms to roughly 10 ms, a 12x improvement since we began focusing on performance earlier this year.

Figure 05 · Improving performance at a global scale

Three major design changes improved read latency

1 2 3 critical data replicated into nearby geos in-region caching launched rust rewrite launched While caching has dramatically reduced median latency, the slowest requests still require fetching uncached data from underlying storage, often across distant regions. As more capacity comes online and we improve storage efficiency, we plan to address a more fundamental challenge: placing data closer to where it will be accessed from the start.

Reliable and scalable CDC—propagating database changes to downstream systems—is a critical component in more than just Habitat’s caching strategy. Millions of captured changes per second, across thousands of containers and millions of container partitions feed through more than a hundred Kafka topics to also power eventually consistent secondary indexes, data-movement pipelines, offline analytics, and other real-time product features. As our backing infrastructure has grown globally, it’s become increasingly complex to listen to row changes.

If a client wants to register a callback to run whenever rows of their type are changed, they must, among other things, first determine which Cosmos accounts and containers contain their data, which CDC service deployment is running in respective regions listening to those containers, and properly scale the CDC service worker fleet, Kafka, and their consumer. Our CDC platform aims to abstract all of this away from product developers and provide instead a simple surface where clients register a function they’d like called on their changed rows and we handle all of the plumbing and scaling.

An internal control plane continuously ensures all required containers are being captured, selects which Cosmos regions to read from, and efficiently distributes work among thousands of workers across multiple geographic regions. Using replica regions and lower-priority feed reads protect latency-sensitive Habitat traffic from background capture workloads. Worker crashes, container issues, lease-store errors, invalid configuration, and destination failures occur continually across a fleet this large.

We hardened CDC worker lifecycle and lease handling, isolated failures between streams, and built safeguards for configuration changes and rollouts. We utilize dead letter queues to prevent head-of-line blocking from permanent delivery failures. We tolerate regional outages by switching readers to alternate Cosmos regions.

At our scale, we’ve even had to make changes to Azure’s CDC SDK to make its checkpointing implementation more efficient by batching checkpoints in-memory and only periodically flushing them.

While Habitat’s internal usage of Azure Cosmos DB is complex, its value proposition has always been to abstract away infrastructure details from product developers to improve product development velocity, and our CDC platform is no different.

A small team was able to support a rapidly growing storage platform by keeping the storage API deliberately narrow, continuing to scale the Python service, and prioritizing the most urgent engineering challenges. We focused first on reliability for mission-critical product traffic, then on read performance for global users, and finally on operational efficiency at scale. This approach enabled rapid growth and new use cases while improving reliability, performance, and product development velocity.

In order to scale Habitat and conversation storage for the next billion users, we need to address the difficulty of replicating our data across our infrastructure, on an even larger scale. To preempt this, we’ve been building a custom in-house exabyte-scale online storage system, optimized for bulky Habitat data types. This in-development system combines the RocksDB Cloud core from Rockset with Raft-based replication. It implements the same document model as Azure Cosmos DB, but uses disaggregated storage and physical replication to reduce the amount of flash capacity needed, in exchange for a lower peak qps.

If you’re interested in working on these types of data infrastructure efforts at frontier scale, check out our open roles ⁠.

Originaltitel: Rapidly scaling online storage to serve over 1 billion ChatGPT users (Part II)Quelle: OpenAI News (OpenAI News). Original öffnen