Gå direkt till huvudinnehåll
OpenAI

22 januari 2026

Teknik

Skala PostgreSQL för att hantera 800 miljoner ChatGPT‑användare

Av Bohan Zhang, medlem av den tekniska personalen

Laddar …

I åratal har PostgreSQL, som driver kärnprodukter som ChatGPT och OpenAI:s API, varit ett av de viktigaste datasystemen bakom kulisserna. I takt med att vår användarbas växer snabbt har även kraven på våra databaser ökat exponentiellt. Under det senaste året har vår PostgreSQL-belastning ökat tiofaldigt och fortsätter att växa snabbt.

Våra ansträngningar att förbättra vår produktionsinfrastruktur för att upprätthålla den här tillväxten gav oss en ny insikt: PostgreSQL kan skalas upp för att på ett tillförlitligt sätt hantera mycket större läsintensiva arbetsbelastningar än vad många tidigare trodde var möjligt. Systemet (ursprungligen utvecklat av ett forskarteam vid University of California, Berkeley) har gjort det möjligt för oss att hantera en enorm global trafik med en flexibel primär Azure PostgreSQL-serverinstans(öppnas i ett nytt fönster) och nästan 50 läsrepliker distribuerade över flera regioner globalt. Det här är berättelsen om hur vi skalade upp PostgreSQL på OpenAI för att hantera miljontals sökningar per sekund för 800 miljoner användare genom grundliga optimeringar och gediget ingenjörsarbete. Vi kommer också att dela med oss av viktiga lärdomar som vi har dragit under resans gång.

Svaga punkter i vår ursprungliga design

Efter lanseringen av ChatGPT ökade trafiken i en aldrig tidigare skådad takt. För att stödja detta genomförde vi snabbt omfattande optimeringar både på applikationsnivån och på PostgreSQL-databasnivån, skalade upp genom att öka instansstorleken och skalade ut genom att lägga till fler läsrepliker. Den här arkitekturen har fungerat väl under lång tid. Med kontinuerliga förbättringar fortsätter det att erbjuda goda möjligheter för framtida tillväxt.

Det kan verka förvånande att en enkel primär arkitektur kan uppfylla kraven för OpenAI:s skala, men att få detta att fungera i praktiken är inte lätt. Vi har sett flera SEV-fel orsakade av överbelastning i Postgres, och de följer ofta samma mönster: ett uppströmsproblem leder till en kraftig ökning av databasbelastningen, till exempel omfattande cachemissar från ett cache-lagerfel, en plötslig och kraftig ökning av kostsamma flervägssammanfogningar som mättar CPU:n, eller en skrivström som utlöses av lanseringen av en ny funktion. När resursanvändningen ökar, ökar fördröjningen för frågor och förfrågningar börjar löpa ut. Upprepade försök förvärrar belastningen ytterligare och utlöser en ond cirkel som kan försämra hela ChatGPT- och API-tjänsterna.

Skalningsdiagram för belastning

Även om PostgreSQL skalar väl för våra läsintensiva arbetsbelastningar, stöter vi fortfarande på utmaningar under perioder med hög skrivtrafik. Detta beror till stor del på PostgreSQL:s implementering av MVCC (Multi-Version Concurrency Control), vilket gör det mindre effektivt för skrivintensiva arbetsbelastningar. När en fråga uppdaterar en tupel eller till och med ett enda fält kopieras till exempel hela raden för att skapa en ny version. Vid stora skrivbelastningar leder detta till en betydande förstärkning av skrivningen. Det ökar också läsförstärkningen, eftersom frågor måste skanna igenom flera tuplversioner (döda tuplar) för att hämta den senaste. MVCC medför flera utmaningar, bland annat förstorade tabeller och index, ökad extra underhållskostnad för index och komplex justering av autovacuum. (Du kan hitta en grundlig genomgång av dessa frågor i en blogg som jag skrev tillsammans med professor Andy Pavlo vid Carnegie Mellon University, med titeln The Part of PostgreSQL We Hate the Most(öppnas i ett nytt fönster), citerad(öppnas i ett nytt fönster) på PostgreSQLs Wikipedia-sida.)

Skala PostgreSQL till miljontals QPS

För att mildra dessa begränsningar och minska skrivtrycket har vi migrerat, och fortsätter att migrera, delbara (dvs. arbetsbelastningar som kan delas upp horisontellt), skrivintensiva arbetsbelastningar till partitionerade system som Azure Cosmos DB och optimering av applikationslogik för att minimera onödiga skrivningar. Vi tillåter inte längre att nya tabeller läggs till i den aktuella PostgreSQL-distributionen. Nya arbetsbelastningar har partitionerade system som standard.

Även när vår infrastruktur har utvecklats har PostgreSQL förblivit osegmenterad, med en enda primär instans som hanterar alla skrivningar. Den främsta anledningen är att det skulle vara mycket komplicerat och tidskrävande att dela upp befintliga applikationsarbetsbelastningar, eftersom det skulle kräva ändringar av hundratals applikationsslutpunkter, vilket potentiellt skulle kunna ta månader eller till och med år. Eftersom våra arbetsbelastningar huvudsakligen är läsintensiva och vi har genomfört omfattande optimeringar, har den nuvarande arkitekturen fortfarande tillräcklig kapacitet för att hantera ytterligare trafikökningar. Även om vi inte utesluter sharding av PostgreSQL i framtiden, är det inte en prioritet på kort sikt, med tanke på den tillräckliga marginal vi har för nuvarande och framtida tillväxt.

I följande avsnitt kommer vi att fördjupa oss i de utmaningar vi stod inför och de omfattande optimeringar vi genomförde för att hantera dem och förhindra framtida avbrott, vilket pressade PostgreSQL till dess gränser och skalade det till miljontals frågor per sekund (QPS).

Minska belastningen på den primära noden

Utmaning: Med endast en skrivare kan en enda primär nodkonfiguration inte skala skrivoperationer. Kraftfulla skrivverktyg kan snabbt överbelasta den primära noden och påverka tjänster som ChatGPT och vårt API.

Lösning: Vi minimerar belastningen på den primära noden så mycket som möjligt – både vid läsning och skrivning – för att säkerställa att den har tillräcklig kapacitet för att hantera plötsliga ökningar i skrivtoppar. Lästrafik omdirigeras till repliker där det är möjligt. Vissa läsförfrågningar måste dock förbli på den primära noden eftersom de ingår i skrivtransaktioner. För dessa fokuserar vi på att säkerställa att de är effektiva och undviker långsamma förfrågningar. För skrivtrafik har vi migrerat leveransbara, skrivintensiva arbetsbelastningar till delade system som Azure CosmosDB. Arbetsbelastningar som är svårare att dela upp, men som ändå genererar en stor mängd skrivande, tar längre tid att migrera, och den processen pågår fortfarande. Vi optimerade också våra applikationer aggressivt för att minska skrivbelastningen. Till exempel korrigerade vi applikationsfel som orsakade onödiga skrivningar och införde fördröjda skrivningar där det var lämpligt för att jämna ut trafiktoppar. Dessutom tillämpar vi strikta användningsbegränsningar när tabellfält fylls i för att förhindra för högt skrivtryck.

Frågeoptimering

Utmaning: Vi identifierade flera resurskrävande frågor i PostgreSQL. Tidigare orsakade plötsliga ökningar i volymen av dessa frågor hög CPU-användning, vilket saktade ner både ChatGPT och API-förfrågningar.

Lösning: Några få resurskrävande frågor, till exempel sådana som sammanfogar många tabeller, kan försämra eller till och med sätta hela tjänsten ur spel. Vi måste kontinuerligt optimera PostgreSQL-frågor för att säkerställa att de är effektiva och undvika vanliga ineffektiva lösningar inom Online Transaction Processing (OLTP). Vi identifierade till exempel en gång en extremt resurskrävande frågeställning som kopplade samman 12 tabeller, där topparna i denna frågeställning var ansvariga för tidigare SEV:er med hög allvarlighetsgrad. Vi bör undvika komplexa sammanfogningar mellan flera tabeller där det är möjligt. Om sammanfogningar är nödvändiga har vi lärt oss att överväga att dela upp frågan och istället flytta komplex sammanfogningslogik till applikationslagret. Många av dessa problematiska frågor genereras av Object-Relational Mapping-ramverk (ORM:er). Det är därför viktigt att noggrant granska den SQL-kod som de producerar och se till att den fungerar som förväntat. Det är också vanligt att hitta långvariga inaktiva frågor i PostgreSQL. Det är viktigt att konfigurera timeouts som idle_in_transaction_session_timeout för att förhindra att de blockerar autovacuum (automatisk rensning).

Åtgärder mot enkelpunktsfel

Utmaning: Om en läsreplika går ner kan trafiken fortfarande dirigeras till andra repliker. Att förlita sig på en enda skrivare innebär dock en enda felkälla – om den slutar fungera påverkas hela tjänsten.

Lösning: De mest kritiska förfrågningarna avser endast läsförfrågningar. För att minska risken för en enda felpunkt i den primära noden har vi flyttat dessa läsoperationer från skrivaren till repliker, så att dessa förfrågningar fortfarande kan hanteras även om den primära noden går ner. Även om skrivoperationer fortfarande skulle misslyckas, minskas konsekvenserna. Det är inte längre ett SEV0 eftersom läsning fortfarande är tillgängligt.

För att minska effekterna av fel på den primära noden kör vi den primära noden i hög tillgänglighetsläge (HA) med en varm standby, en kontinuerligt synkroniserad replik som alltid är redo att ta över trafiken. Om den primära noden går ner eller måste tas offline för underhåll kan vi snabbt uppgradera standby-noden för att minimera driftstoppet. Azure PostgreSQL-teamet har gjort ett betydande arbete för att säkerställa att dessa failovers förblir säkra och tillförlitliga även under mycket hög belastning. För att hantera fel i läsrepliker distribuerar vi flera repliker i varje region med tillräcklig kapacitetsmarginal, så att ett enda replikfel inte orsakar ett regionalt driftavbrott.

Isolering av arbetsbelastningar

Utmaning: Vi stöter ofta på situationer där vissa frågor använder en oproportionerligt stor mängd resurser på PostgreSQL-instanser. Detta kan leda till försämrad prestanda för andra arbetsbelastningar som körs på samma instanser. Till exempel kan lanseringen av en ny funktion resultera i ineffektiva förfrågningar som belastar PostgreSQL-processorn kraftigt, vilket saktar ner förfrågningar för andra viktiga funktioner.

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-proxydiagram

Varje läsreplika har sin egen Kubernetes-distribution som kör flera PgBouncer-poddar. Vi kör flera Kubernetes-distributioner bakom samma Kubernetes-tjänst, som lastbalanserar trafiken över poddar.

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(öppnas i ett nytt fönster), 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.

En serie PostgreSQL-replikationsdiagram

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(öppnas i ett nytt fönster). 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(öppnas i ett nytt fönster) 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.

Författare

Bohan Zhang

Tack

Ett särskilt tack till Jon Lee, Sicheng Liu, Chaomin Yu och Chenglong Hao, som bidrog till det här inlägget, och till hela teamet som hjälpte till att skala upp PostgreSQL. Vi vill också tacka Azure PostgreSQL-teamet för deras starka partnerskap.