Zum Hauptinhalt springen
OpenAI

22. Januar 2026

Ingenieurwesen

PostgreSQL-Skalierung, um 800 Mio. ChatGPT‑Nutzer zu ermöglichen

Von Bohan Zhang, Mitglied des technischen Teams

Laden …

Seit Jahren ist PostgreSQL eines der wichtigsten Datensysteme hinter den Kulissen, das Kernprodukte wie ChatGPT und die OpenAI-API antreibt. Während unsere Benutzerbasis rasant wächst, sind auch die Anforderungen an unsere Datenbanken exponentiell gestiegen. Im vergangenen Jahr ist die PostgreSQL-Last bei uns um mehr als das Zehnfache gewachsen, und sie steigt weiterhin schnell an.

Unsere Bemühungen, unsere Produktionsinfrastruktur weiterzuentwickeln, um dieses Wachstum zu tragen, haben eine neue Erkenntnis zutage gefördert: PostgreSQL lässt sich zuverlässig so skalieren, dass es deutlich größere leseintensive Workloads unterstützt, als viele bislang für möglich gehalten haben. Das System (ursprünglich von einem Team von Wissenschaftlern an der University of California, Berkeley entwickelt) hat es uns ermöglicht, massiven globalen Traffic mit einer einzigen Primary-Azure-PostgreSQL-Flexible-Server-Instanz(wird in einem neuen Fenster geöffnet) und fast 50 Read-Replikas zu bewältigen, die weltweit über mehrere Regionen verteilt sind. Dies ist die Geschichte, wie wir PostgreSQL bei OpenAI durch konsequente Optimierungen und solides Engineering so skaliert haben, dass es Millionen von Queries pro Sekunde für 800 Millionen Benutzer unterstützt; außerdem teilen wir zentrale Erkenntnisse, die wir auf diesem Weg gewonnen haben.

Risse in unserem ursprünglichen Design

Nach dem Start von ChatGPT wuchs der Traffic in einem bisher beispiellosen Tempo. Um das zu unterstützen, haben wir zügig umfangreiche Optimierungen sowohl auf Anwendungs- als auch auf PostgreSQL-Datenbankebene umgesetzt, vertikal skaliert durch größere Instanzen und horizontal skaliert durch zusätzliche Read-Replikas. Diese Architektur hat uns lange Zeit gute Dienste geleistet. Mit fortlaufenden Verbesserungen bietet sie auch weiterhin ausreichend Spielraum für zukünftiges Wachstum.

Es mag überraschend klingen, dass eine Single-Primary-Architektur den Anforderungen in der Größenordnung von OpenAI gerecht werden kann; in der Praxis ist das jedoch alles andere als trivial. Wir haben mehrere SEVs erlebt, die durch eine Überlastung von Postgres verursacht wurden, und sie folgen oft demselben Muster: Ein vorgelagertes Problem führt zu einem plötzlichen Anstieg der Datenbanklast, etwa durch großflächige Cache-Misses infolge eines Ausfalls der Caching-Schicht, durch eine Flut teurer Mehrfach-Joins, die die CPU auslasten, oder durch einen Write-Storm nach dem Launch eines neuen Features. Wenn die Ressourcenauslastung steigt, erhöht sich die Query-Latenz und Anfragen beginnen zu timeouten. Retries verstärken die Last dann weiter und lösen einen Teufelskreis aus, der das Potenzial hat, die gesamten ChatGPT- und API-Dienste zu beeinträchtigen.

Diagramm zur Lastskalierung

Obwohl PostgreSQL für unsere leseintensiven Workloads gut skaliert, stoßen wir in Phasen mit hohem Schreib-Traffic weiterhin auf Herausforderungen. Das liegt vor allem an der Implementierung der Multiversion Concurrency Control (MVCC) in PostgreSQL, die es für schreibintensive Workloads weniger effizient macht. Wenn beispielsweise eine Query ein Tupel oder auch nur ein einzelnes Feld aktualisiert, wird die gesamte Zeile kopiert, um eine neue Version zu erzeugen. Unter hoher Schreiblast führt das zu erheblicher Write-Amplification. Gleichzeitig steigt auch die Read-Amplification, da Queries mehrere Tupel-Versionen (Dead Tuples) durchsuchen müssen, um die aktuelle Version zu finden. MVCC bringt zudem weitere Herausforderungen mit sich, etwa Table- und Index-Bloat, erhöhten Aufwand für die Index-Pflege und komplexes Autovacuum-Tuning. (Eine ausführliche Analyse dieser Themen findet sich in einem Blogpost, den ich gemeinsam mit Prof. Andy Pavlo von der Carnegie Mellon University geschrieben habe: Was wir an PostgreSQL am wenigsten ausstehen können(wird in einem neuen Fenster geöffnet), der auf der PostgreSQL-Wikipedia-Seite zitiert(wird in einem neuen Fenster geöffnet) wird.)

Skalierung von PostgreSQL auf Millionen QPS

Um diese Einschränkungen abzumildern und die Belastung durch Writes zu reduzieren, haben wir shardbare, schreibintensive Workloads – also solche, die sich horizontal partitionieren lassen – auf geshardete Systeme wie Azure Cosmos DB migriert und setzen diese Migration fort, während wir gleichzeitig die Anwendungslogik optimieren, um unnötige Writes zu minimieren. Außerdem erlauben wir keine neuen Tabellen mehr im aktuellen PostgreSQL-Deployment. Neue Workloads laufen standardmäßig auf den geshardeten Systemen.

Auch wenn sich unsere Infrastruktur weiterentwickelt hat, ist PostgreSQL ungeshardet geblieben, mit einer einzelnen Primary-Instanz für alle Writes. Der Hauptgrund dafür ist, dass das Sharding bestehender Anwendungs-Workloads extrem komplex und zeitaufwendig wäre, Änderungen an Hunderten von Application-Endpoints erfordern und potenziell Monate oder sogar Jahre dauern könnte. Da unsere Workloads überwiegend leseintensiv sind und wir umfangreiche Optimierungen umgesetzt haben, bietet die aktuelle Architektur weiterhin ausreichend Headroom, um weiteres Traffic-Wachstum zu tragen. Auch wenn wir ein Sharding von PostgreSQL in Zukunft nicht ausschließen, hat es angesichts des vorhandenen Spielraums für aktuelles und zukünftiges Wachstum derzeit keine Priorität.

In den folgenden Abschnitten gehen wir auf die Herausforderungen ein, mit denen wir konfrontiert waren, sowie auf die umfassenden Optimierungen, die wir implementiert haben, um sie zu bewältigen und zukünftige Ausfälle zu verhindern, indem wir PostgreSQL bis an seine Grenzen treiben und auf Millionen von Queries pro Sekunde (QPS) skalieren.

Reduzierung der Last auf dem Primärsystem

Herausforderung: Mit nur einem Writer kann ein Single-Primary-Setup Writes nicht skalieren. Starke Schreibspitzen können die Primary schnell überlasten und Services wie ChatGPT und unsere API beeinträchtigen.

Lösung: Wir minimieren die Last auf der Primary so weit wie möglich – sowohl Reads als auch Writes –, um sicherzustellen, dass genügend Kapazität für Schreibspitzen vorhanden ist. Lese-Traffic wird, wo immer möglich, auf Replikas ausgelagert. Einige Lese-Queries müssen jedoch auf der Primary verbleiben, weil sie Teil von Write-Transaktionen sind. Für diese stellen wir sicher, dass sie effizient sind, und vermeiden langsame Queries. Beim Schreib-Traffic haben wir shardbare, schreibintensive Workloads auf geshardete Systeme wie Azure CosmosDB migriert. Workloads, die schwieriger zu sharden sind, aber dennoch ein hohes Schreibvolumen erzeugen, benötigen länger für die Migration, und dieser Prozess ist noch im Gange. Zusätzlich haben wir unsere Anwendungen aggressiv optimiert, um die Schreiblast zu reduzieren; zum Beispiel haben wir Anwendungsfehler behoben, die redundante Schreibvorgänge verursacht haben, und dort, wo es sinnvoll ist, Lazy Writes eingeführt, um Traffic-Spitzen zu glätten. Darüber hinaus setzen wir beim Backfilling von Tabellenfeldern strikte Rate Limits durch, um übermäßigen Schreibdruck zu verhindern.

Abfrageoptimierung

Herausforderung: Wir haben mehrere kostspielige Abfragen in PostgreSQL identifiziert. In der Vergangenheit haben plötzliche Volumenspitzen bei diesen Abfragen große Mengen an CPU verbraucht, was sowohl ChatGPT als auch API-Anfragen verlangsamt hat.

Lösung: Einige teure Abfragen, wie solche, die viele Tabellen zusammenführen, können die Service-Performance erheblich beeinträchtigen oder sogar den gesamten Dienst lahmlegen. Deshalb müssen wir PostgreSQL-Queries kontinuierlich optimieren, um sicherzustellen, dass sie effizient sind und gängige Anti-Patterns im Online Transaction Processing (OLTP) vermeiden. So haben wir beispielsweise einmal eine extrem kostspielige Query identifiziert, die 12 Tabellen miteinander verknüpft hat und deren Lastspitzen für frühere schwerwiegende SEVs verantwortlich waren. Komplexe Multi-Table-Joins sollten daher wann immer möglich vermieden werden. Wenn Joins notwendig sind, haben wir gelernt, in Betracht zu ziehen, die Query aufzuteilen und komplexe Join-Logik stattdessen in die Anwendungsschicht zu verlagern. Viele dieser problematischen Queries werden von Object-Relational-Mapping-Frameworks (ORMs) erzeugt, weshalb es wichtig ist, das von ihnen generierte SQL sorgfältig zu prüfen und sicherzustellen, dass es sich wie erwartet verhält. Außerdem findet man in PostgreSQL häufig lange laufende, idle Queries. Die Konfiguration von Timeouts wie idle_in_transaction_session_timeout ist essenziell, um zu verhindern, dass sie autovacuum blockieren.

Abmilderung von Single Points of Failure

Herausforderung: Wenn eine Read-Replika ausfällt, kann der Traffic weiterhin zu anderen Replikas geroutet werden. Sich jedoch auf einen einzelnen Writer zu verlassen bedeutet, einen Single Point of Failure zu haben – fällt dieser aus, ist der gesamte Service betroffen.

Lösung: Die meisten kritischen Requests bestehen ausschließlich aus Read-Queries. Um den Single Point of Failure in der Primary abzumildern, haben wir diese Reads vom Writer auf Replikas ausgelagert und stellen so sicher, dass diese Requests auch dann weiter bedient werden können, wenn die Primary ausfällt. Schreiboperationen würden zwar weiterhin fehlschlagen, die Auswirkung ist jedoch geringer; es ist kein SEV0 mehr, da Reads verfügbar bleiben.

Um Ausfälle der Primary weiter abzufedern, betreiben wir sie im High-Availability-(HA-)Modus mit einer Hot-Standby-Instanz, einer kontinuierlich synchronisierten Replika, die jederzeit bereit ist, den Traffic zu übernehmen. Fällt die Primary aus oder muss sie für Wartungsarbeiten offline genommen werden, können wir den Standby schnell promoten, um die Ausfallzeiten zu minimieren. Das Azure-PostgreSQL-Team hat erheblichen Aufwand betrieben, um sicherzustellen, dass diese Failovers selbst unter sehr hoher Last sicher und zuverlässig bleiben. Um Ausfälle von Read-Replikas zu handhaben, setzen wir in jeder Region mehrere Repliken mit ausreichender Kapazitätsreserve ein, sodass der Ausfall einer einzelnen Replika nicht zu einem regionalen Ausfall führt.

Workload-Isolierung

Herausforderung: Wir stoßen häufig auf Situationen, in denen bestimmte Requests unverhältnismäßig viele Ressourcen auf PostgreSQL-Instanzen verbrauchen. Das kann zu einer verschlechterten Performance für andere Workloads führen, die auf denselben Instanzen laufen. So kann zum Beispiel der Launch eines neuen Features ineffiziente Queries einführen, die stark PostgreSQL-CPU verbrauchen und Requests für andere kritische Features ausbremsen.

Solution: To mitigate the “noisy neighbor” problem, we isolate workloads onto dedicated instances to ensure that sudden spikes in resource-intensive requests don’t impact other traffic. Specifically, we split requests into low-priority and high-priority tiers and route them to separate instances. This way, even if a low-priority workload becomes resource-intensive, it won’t degrade the performance of high-priority requests. We apply the same strategy across different products and services as well, so that activity from one product does not affect the performance or reliability of another.

Connection pooling

Challenge: Each instance has a maximum connection limit (5,000 in Azure PostgreSQL). It’s easy to run out of connections or accumulate too many idle ones. We’ve previously had incidents caused by connection storms that exhausted all available connections.

Solution: We deployed PgBouncer as a proxy layer to pool database connections. Running it in statement or transaction pooling mode allows us to efficiently reuse connections, greatly reducing the number of active client connections. This also cuts connection setup latency: in our benchmarks, the average connection time dropped from 50 milliseconds (ms) to 5 ms. Inter-region connections and requests can be expensive, so we co-locate the proxy, clients, and replicas in the same region to minimize network overhead and connection use time. Moreover, PgBouncer must be configured carefully. Settings like idle timeouts are critical to prevent connection exhaustion.

PostgreSQL-Proxy-Diagramm

Jede Read-Replika hat ihr eigenes Kubernetes-Deployment, das mehrere PgBouncer-Pods ausführt. Wir betreiben mehrere Kubernetes-Deployments hinter demselben Kubernetes Service, der den Traffic über die Pods hinweg verteilt.

Caching

Challenge: A sudden spike in cache misses can trigger a surge of reads on the PostgreSQL database, saturating CPU and slowing user requests.

Solution: To reduce read pressure on PostgreSQL, we use a caching layer to serve most of the read traffic. However, when cache hit rates drop unexpectedly, the burst of cache misses can push a large volume of requests directly to PostgreSQL. This sudden increase in database reads consumes significant resources, slowing down the service. To prevent overload during cache-miss storms, we implement a cache locking (and leasing) mechanism so that only a single reader that misses on a particular key fetches the data from PostgreSQL. When multiple requests miss on the same cache key, only one request acquires the lock and proceeds to retrieve the data and repopulate the cache. All other requests wait for the cache to be updated rather than all hitting PostgreSQL at once. This significantly reduces redundant database reads and protects the system from cascading load spikes.

Scaling read replicas

Challenge: The primary streams Write Ahead Log (WAL) data to every read replica. As the number of replicas increases, the primary must ship WAL to more instances, increasing pressure on both network bandwidth and CPU. This causes higher and more unstable replica lag, which makes the system harder to scale reliably.

Solution: We operate nearly 50 read replicas across multiple geographic regions to minimize latency. However, with the current architecture, the primary must stream WAL to every replica. Although it currently scales well with very large instance types and high-network bandwidth, we can’t keep adding replicas indefinitely without eventually overloading the primary. To address this, we’re collaborating with the Azure PostgreSQL team on cascading replication(wird in einem neuen Fenster geöffnet), where intermediate replicas relay WAL to downstream replicas. This approach allows us to scale to potentially over a hundred replicas without overwhelming the primary. However, it also introduces additional operational complexity, particularly around failover management. The feature is still in testing; we’ll ensure it’s robust and can fail over safely before rolling it out to production.

Diagramm zur kaskadierenden PostgreSQL-Replikation

Rate limit

Challenge: A sudden traffic spike on specific endpoints, a surge of expensive queries, or a retry storm can quickly exhaust critical resources such as CPU, I/O, and connections, which causes widespread service degradation.

Solution: We implemented rate-limiting across multiple layers—application, connection pooler, proxy, and query—to prevent sudden traffic spikes from overwhelming database instances and triggering cascading failures. It’s also crucial to avoid overly short retry intervals, which can trigger retry storms. We also enhanced the ORM layer to support rate limiting and when necessary, fully block specific query digests. This targeted form of load shedding enables rapid recovery from sudden surges of expensive queries.

Schema Management

Challenge: Even a small schema change, such as altering a column type, can trigger a full table rewrite(wird in einem neuen Fenster geöffnet). We therefore apply schema changes cautiously—limiting them to lightweight operations and avoiding any that rewrite entire tables.

Solution: Only lightweight schema changes are permitted, such as adding or removing certain columns that do not trigger a full table rewrite. We enforce a strict 5-second timeout on schema changes. Creating and dropping indexes concurrently is allowed. Schema changes are restricted to existing tables. If a new feature requires additional tables, they must be in alternative sharded systems such as Azure CosmosDB rather than PostgreSQL. When backfilling a table field, we apply strict rate limits to prevent write spikes. Although this process can sometimes take over a week, it ensures stability and avoids any production impact.

Results and the road ahead

This effort demonstrates that with the right design and optimizations, Azure PostgreSQL can be scaled to handle the largest production workloads. PostgreSQL handles millions of QPS for read-heavy workloads, powering OpenAI’s most critical products like ChatGPT and the API platform. We added nearly 50 read replicas, while keeping replication lag near zero, maintained low-latency reads across geo-distributed regions, and built sufficient capacity headroom to support future growth.

This scaling works while still minimizing latency and improving reliability. We consistently deliver low double-digit millisecond p99 client-side latency and five-nines availability in production. And over the past 12 months, we’ve had only one SEV-0 PostgreSQL incident (it occurred during the viral launch(wird in einem neuen Fenster geöffnet) of ChatGPT ImageGen, when write traffic suddenly surged by more than 10x as over 100 million new users signed up within a week.)

While we’re happy with how far PostgreSQL has taken us, we continue to push its limits to ensure we have sufficient runway for future growth. We’ve already migrated the shardable write-heavy workloads to our sharded systems like CosmosDB. The remaining write-heavy workloads are more challenging to shard—we’re actively migrating those as well to further offload writes from the PostgreSQL primary. We’re also working with Azure to enable cascading replication so we can safely scale to significantly more read replicas.

Looking ahead, we’ll continue to explore additional approaches to further scale, including sharded PostgreSQL or alternative distributed systems, as our infrastructure demands continue to grow.

Autor

Bohan Zhang

Danksagungen

Besonderer Dank gilt Jon Lee, Sicheng Liu, Chaomin Yu und Chenglong Hao, die zu diesem Beitrag beigetragen haben, sowie dem gesamten Team, das bei der Skalierung von PostgreSQL geholfen hat. Außerdem danken wir dem Azure-PostgreSQL-Team für die enge und starke Zusammenarbeit.