Preskočite na glavno vsebino
OpenAI

22. januar 2026

Tehnologija

Skaliranje PostgreSQL za 800 milijonov uporabnikov ChatGPT

Avtor: Bohan Zhang, član tehničnega osebja

Nalaganje …

PostgreSQL je že vrsto let eden najpomembnejših podatkovnih sistemov v ozadju, ki poganjajo ključne produkte, kot sta ChatGPT in OpenAI API. Ker naša uporabniška baza hitro raste, se eksponentno povečujejo tudi zahteve do naših podatkovnih baz. V zadnjem letu se je obremenitev PostgreSQL povečala za več kot desetkrat in še naprej hitro narašča.

Naša prizadevanja za napredek produkcijske infrastrukture, ki bo sposobna vzdrževati to rast, so razkrila novo ugotovitev: PostgreSQL je mogoče skalirati tako, da zanesljivo podpira bistveno večje delovne obremenitve, kjer prevladuje branje, kot so mnogi prej menili, da je mogoče. Sistem (sprva ga je razvila skupina znanstvenikov na University of California, Berkeley) nam je omogočil obvladovanje izjemno obsežnega globalnega prometa z enim primarnim primerkom Azure PostgreSQL flexible server(odpre se v novem oknu) in skoraj 50 replikami za branje, razporejenimi po več regijah po svetu. To je zgodba o tem, kako smo pri OpenAI-ju skalirali PostgreSQL, da podpira milijone poizvedb na sekundo za 800 milijonov uporabnikov z doslednimi optimizacijami in trdnim inženirskim delom; hkrati bomo povzeli tudi ključne nauke, ki smo jih ob tem pridobili.

Pomanjkljivosti začetne zasnove

Po lansiranju ChatGPT se je promet povečal z doslej neprimerljivo hitrostjo. Da bi to podprli, smo v zelo kratkem času uvedli obsežne optimizacije tako na ravni aplikacije kot na ravni podatkovne baze PostgreSQL, povečali zmogljivost z večanjem velikosti instanc ter razširili obseg z dodajanjem dodatnih replik za branje. Takšna arhitektura nam je dolgo časa dobro služila. Z nenehnimi izboljšavami še naprej zagotavlja dovolj manevrskega prostora za prihodnjo rast.

Morda se zdi presenetljivo, da lahko arhitektura z enim primarnim vozliščem zadosti zahtevam obsega, kakršnega ima OpenAI, vendar takšna zasnova v praksi ni preprosta. Doživeli smo več resnih incidentov, ki so bili posledica preobremenitve Postgres, in ti pogosto sledijo enakemu vzorcu: težava višje v skladu povzroči nenaden porast obremenitve podatkovne baze, na primer množične zgrešitve predpomnilnika zaradi okvare predpomnilniške plasti, porast zahtevnih združevanj več tabel, ki zasičijo procesor, ali pa nenaden val zapisov ob uvedbi nove funkcionalnosti. Ko se poraba virov povečuje, narašča zakasnitev poizvedb in zahteve začnejo presegati časovne omejitve. Ponovni poskusi nato obremenitev še dodatno okrepijo in sprožijo začaran krog, ki lahko poslabša delovanje celotnih storitev ChatGPT in OpenAI API.

Diagram skaliranja obremenitve

Čeprav se PostgreSQL dobro skalira pri naših delovnih obremenitvah, kjer prevladuje branje, se v obdobjih izrazito povečanega prometa zapisovanja še vedno soočamo z izzivi. To je v veliki meri posledica implementacije nadzora sočasnosti z več različicami (MVCC) v PostgreSQL-u, zaradi katere je ta pri delovnih obremenitvah, kjer prevladuje zapisovanje, manj učinkovit. Na primer, ko poizvedba posodobi zapis (tuple) ali celo zgolj posamezno polje, se za ustvarjanje nove različice kopira celotna vrstica. Pri velikih delovnih obremenitvah z zapisovanjem to povzroči izrazito povečanje ojačanja zapisovanja. Prav tako poveča amplifikacijo branja, saj morajo poizvedbe pregledati več različic n-teric (mrtve n-terice), da pridobijo najnovejšo. MVCC prinaša dodatne izzive, kot so napihnjenost tabel in indeksov, povečana obremenitev vzdrževanja indeksov ter kompleksno prilagajanje funkcije autovacuum. (O teh vprašanjih lahko najdete poglobljeno obravnavo v blogu, ki sem ga napisal s prof. Andyjem Pavlom na Univerzi Carnegie Mellon z naslovom The Part of PostgreSQL We Hate the Most(odpre se v novem oknu), citirano(odpre se v novem oknu) na Wikipediji o PostgreSQL.)

Skaliranje PostgreSQL na milijone poizvedb na sekundo

Da bi omilili te omejitve in zmanjšali pritisk zapisov, smo migrirali in še naprej migriramo razdeljive (tj. delovne obremenitve, ki jih je mogoče horizontalno particionirati) in so intenzivne pri zapisovanju, v sisteme z delitvijo podatkov, kot je Azure Cosmos DB, pri čemer optimiziramo logiko aplikacije, da zmanjšamo nepotrebne zapise. Dodajanje novih tabel v obstoječo namestitev PostgreSQL ni več dovoljeno. Nove delovne obremenitve se privzeto usmerjajo v sisteme z delitvijo podatkov.

Čeprav se je naša infrastruktura razvijala, PostgreSQL ostaja sistem brez delitve podatkov, pri čemer ena sama primarna instanca obdeluje vse zapise. Glavni razlog za to je, da bi bila uvedba delitve podatkov pri obstoječih aplikacijskih delovnih obremenitvah izjemno zapletena in dolgotrajna, saj bi zahtevala spremembe na stotinah aplikacijskih končnih točk in bi lahko trajala več mesecev ali celo let. Ker so naše delovne obremenitve pretežno usmerjene v branje in smo uvedli obsežne optimizacije, trenutna arhitektura še vedno zagotavlja dovolj rezerve za nadaljnjo rast prometa. Čeprav uvedbe delitve podatkov v PostgreSQL v prihodnosti ne izključujemo, trenutno to ni kratkoročna prednostna naloga, saj imamo za sedanje in prihodnje potrebe še vedno dovolj prostora za rast.

V nadaljevanju se bomo poglobili v izzive, s katerimi smo se soočili, ter v obsežne optimizacije, ki smo jih uvedli, da bi jih odpravili in preprečili prihodnje izpade, s čimer smo PostgreSQL potisnili do njegovih meja in ga razširili na milijone poizvedb na sekundo (QPS).

Zmanjševanje obremenitve primarne instance

Izziv: pri zasnovi z enim zapisovalcem sistem brez delitve podatkov ne omogoča skaliranja zapisov. Nenadni porasti zapisov lahko hitro preobremenijo primarno instanco in vplivajo na storitve, kot sta ChatGPT in OpenAI API.

Rešitev: obremenitev primarne instance zmanjšujemo, kolikor je mogoče, tako pri branjih kot pri zapisih, da zagotovimo dovolj zmogljivosti za obvladovanje nenadnih porastov zapisov. Promet branja se, kjer je to mogoče, preusmeri na replike. Nekatere bralne poizvedbe pa morajo ostati na primarni instanci, ker so del transakcij zapisovanja. Pri teh se osredotočamo na to, da so učinkovite, ter se dosledno izogibamo počasnim poizvedbam. Pri prometu zapisov smo migrirali razdeljive delovne obremenitve z intenzivnim zapisovanjem v sisteme z delitvijo podatkov, kot je Azure CosmosDB. Delovne obremenitve, ki so težko primerne za delitev podatkov, a kljub temu ustvarjajo velik obseg zapisov, zahtevajo daljši čas migracije in ta proces še vedno poteka. Poleg tega smo aplikacije odločno optimizirali, da bi zmanjšali obremenitev zapisovanja; na primer odpravili smo napake v aplikacijah, ki so povzročale podvojene zapise, ter tam, kjer je to smiselno, uvedli odložene zapise, s čimer izravnavamo nenadne poraste prometa. Dodatno pri zapolnjevanju polj tabel uveljavljamo stroge omejitve hitrosti, da preprečimo pretiran pritisk zapisovanja.

Optimizacija poizvedb

Izziv: v PostgreSQL smo prepoznali več dragih poizvedb. V preteklosti so nenadni porasti obsega teh poizvedb porabili velike količine procesorskega časa, kar je upočasnilo tako zahteve ChatGPT kot tudi zahteve API.

Rešitev: posamezne drage poizvedbe, na primer tiste, ki združujejo veliko število tabel, lahko občutno poslabšajo delovanje ali celo ohromijo celotno storitev. Poizvedbe PostgreSQL je zato treba neprestano optimizirati, da ostanejo učinkovite, ter se izogibati pogostim antipraksam Online Transaction Processing (OLTP). Na primer, nekoč smo prepoznali izjemno drago poizvedbo, ki je združevala 12 tabel, pri čemer so bili nenadni porasti te poizvedbe odgovorni za pretekle incidente najvišje resnosti (SEV). Zapletenim združevanjem več tabel se je treba, kjer je to mogoče, izogibati. Če so združevanja nujna, se je izkazalo za smiselno razmisliti o razčlenitvi poizvedbe ter prenosu zapletene logike združevanja v aplikacijsko plast. Velik del takšnih problematičnih poizvedb ustvarjajo ogrodja za objektno-relacijsko preslikovanje (ORM), zato je pomembno skrbno pregledati SQL, ki ga generirajo, in preveriti, ali se obnaša skladno s pričakovanji. Pogosto se v PostgreSQL pojavljajo tudi dolgotrajne neaktivne poizvedbe. Nastavitev časovnih omejitev, kot je idle_in_transaction_session_timeout, je ključna za preprečevanje njihovega blokiranja mehanizma autovacuum.

Omilitev kritične točke odpovedi

Izziv: če odpove ena replika za branje, se lahko promet še vedno preusmeri na druge replike. Zanašanje na enega samega zapisovalca pa pomeni tudi kritično točko odpovedi; če ta odpove, je prizadeta celotna storitev.

Rešitev: večina kritičnih zahtev vključuje le bralne poizvedbe. Da bi omilili kritično točko odpovedi na primarni instanci, smo ta branja razbremenili zapisovalca in jih preusmerili na replike, s čimer zagotovimo, da se te zahteve lahko obdelujejo tudi ob izpadu primarne instance. Čeprav bi zapisi v takem primeru še vedno spodleteli, je vpliv bistveno manjši; dogodek tako ni več razvrščen kot SEV0, saj branja ostanejo na voljo.

Za omilitev odpovedi primarne instance jo poganjamo v načinu visoke razpoložljivosti (HA) z vročo rezervo (hot standby), se pravi stalno sinhronizirano repliko, ki je vedno pripravljena prevzeti obdelavo prometa. Če primarna instanca odpove ali jo je treba začasno umakniti zaradi vzdrževanja, lahko rezervo hitro povišamo v primarno vlogo in s tem zmanjšamo čas nedosegljivosti. Ekipa Azure PostgreSQL je opravila obsežno delo, da ti prehodi ostanejo varni in zanesljivi tudi pri zelo visokih obremenitvah. Za obvladovanje odpovedi replik za branje v vsaki regiji uvajamo več replik z zadostno rezervo zmogljivosti, s čimer zagotovimo, da odpoved ene same replike ne povzroči regionalnega izpada.

Izolacija delovnih obremenitev

Izziv: pogosto se srečujemo s primeri, ko nekatere zahteve porabijo nesorazmerno veliko virov na instancah PostgreSQL. To lahko poslabša delovanje drugih delovnih obremenitev, ki se izvajajo na istih instancah. Na primer, uvedba nove funkcionalnosti lahko prinese neučinkovite poizvedbe, ki močno obremenijo procesor PostgreSQL in upočasnijo zahteve drugih ključnih funkcionalnosti.

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.

Diagram posrednika PostgreSQL

Vsaka replika za branje ima svojo uvedbo v okolju Kubernetes, v kateri se izvaja več podov PgBouncer. Uporabljamo več uvedb Kubernetes za isto storitev Kubernetes, ki promet uravnoteženo porazdeli med pode.

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(odpre se v novem oknu), 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.

Diagram kaskadne replikacije PostgreSQL

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(odpre se v novem oknu). 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(odpre se v novem oknu) 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.

Avtor

Bohan Zhang

Zahvala

Posebna zahvala gre Jonu Leeju, Sichengu Liuju, Chaominu Yuju in Chenglongu Hau za njihov prispevek k tej objavi ter celotni ekipi, ki je sodelovala pri skaliranju PostgreSQL-a. Prav tako se zahvaljujemo ekipi Azure PostgreSQL za trdno in konstruktivno partnerstvo.