Preskočite na glavni sadržaj
OpenAI

22. siječnja 2026.

Inženjerstvo

Skaliranje PostgreSQL-a za 800 milijuna korisnika ChatGPT‑a

Autor: Bohan Zhang, član tehničkog osoblja

Učitavanje…

PostgreSQL je već godinama jedan od najvažnijih podatkovnih sustava u pozadini koji pokreće ključne proizvode kao što su ChatGPT i OpenAI-jev API. Kako naša baza korisnika brzo raste, tako su i zahtjevi za našim bazama podataka eksponencijalno porasli. Tijekom protekle godine, opterećenje našeg PostgreSQL-a poraslo je više od 10 puta i nastavlja brzo rasti.

Naši napori da unaprijedimo našu proizvodnu infrastrukturu kako bismo podržali ovaj rast otkrili su novu spoznaju: PostgreSQL se može skalirati kako bi pouzdano podržavao znatno veća opterećenja s pretežitim čitanjem nego što su mnogi ranije smatrali mogućim. Sustav (koji je u početku izradio tim znanstvenika na Sveučilištu Kalifornija, Berkeley) omogućio nam je podršku za masivan globalni promet s jednom primarnom Azure PostgreSQL instancom fleksibilnog poslužitelja(otvara se u novom prozoru) i gotovo 50 replika za čitanje raspoređenih u više regija diljem svijeta. Ovo je priča o tome kako smo u OpenAI-ju skalirali PostgreSQL kako bismo podržali milijune upita u sekundi za 800 milijuna korisnika kroz rigorozne optimizacije i čvrsto inženjerstvo; također ćemo podijeliti ključne spoznaje koje smo stekli putem.

Pukotine u našem početnom dizajnu

Nakon lansiranja ChatGPT‑a, promet je rastao neviđenom brzinom. Kako bismo to podržali, brzo smo implementirali opsežne optimizacije na razinama aplikacije i PostgreSQL baze podataka, povećali smo veličinu instanci i dodali više replika za čitanje. Ova arhitektura nas je dugo dobro služila. Uz stalna poboljšanja, nastavlja pružati dovoljno prostora za budući rast.

Možda zvuči iznenađujuće da arhitektura s jednim primarnim čvorom može zadovoljiti zahtjeve razmjera OpenAI-ja; međutim, ostvariti to u praksi nije jednostavno. Vidjeli smo nekoliko SEV-ova uzrokovanih preopterećenjem Postgresa, i često slijede isti obrazac: uzvodni problem uzrokuje nagli skok opterećenja baze podataka, kao što su rašireni promašaji predmemorije zbog kvara sloja predmemoriranja, porast skupih višestrukih spajanja koji zasićuje CPU ili propterećenost upisa zbog pokretanja nove značajke. Kako se iskorištenost resursa povećava, kašnjenje upita raste, a vrijeme zahtjeva počinje istjecati. Ponovni pokušaji dodatno pojačavaju opterećenje, pokrećući začarani krug s potencijalom da degradira kompletne usluge ChatGPT‑a i API-ja.

Dijagram skaliranja opterećenja

Iako se PostgreSQL dobro skalira za naša opterećenja s pretežitim čitanjem, i dalje nailazimo na izazove tijekom razdoblja intenzivnog prometa pisanja. To je uglavnom zbog PostgreSQL-ove implementacije viševerzijske kontrole istodobnosti (MVCC), što ga čini manje učinkovitim za radna opterećenja s velikim brojem upisa. Primjerice, kada upit ažurira n-torku ili čak samo jedno polje, cijeli redak se kopira kako bi se stvorila nova verzija. Pod velikim opterećenjem upisa dolazi do značajnog pojačanja upisa. Također povećava pojačanje čitanja, jer upiti moraju proći kroz više verzija n-torki ('mrtve n-torke') kako bi dohvatili najnoviju verziju. MVCC uvodi dodatne izazove kao što su preopterećenje tablica i indeksa, povećano opterećenje održavanja indeksa i složeno podešavanje automatskog vakuuma. (Detaljnu analizu ovih problema možete pronaći u blogu koji sam napisao s prof. Andyjem Pavlom na Sveučilištu Carnegie Mellon pod nazivom The Part of PostgreSQL We Hate the Most(otvara se u novom prozoru), citiranom(otvara se u novom prozoru) na stranici PostgreSQL na Wikipediji.)

Skaliranje PostgreSQL-a na milijune upita u sekundi

Kako bismo ublažili ta ograničenja i smanjili pritisak pisanja, migrirali smo i nastavljamo migrirati sementirana (npr. radna opterećenja koja se mogu horizontalno particionirati) radna opterećenja s velikim brojem upisa u segmentirane sustave kao što je Azure Cosmos DB, čime se optimizira logika aplikacije kako bi se smanjili nepotrebni upisi. Također više ne dopuštamo dodavanje novih tablica u trenutnu PostgreSQL implementaciju. Nova radna opterećenja po zadanom su postavljena na segmentirane sustave.

Iako se naša infrastruktura razvijala, PostgreSQL je ostao nesegmentiran, s jednom primarnom instancom koja obrađuje sve upise. Primarni razlog je taj da bi podjela postojećih radnih opterećenja aplikacije bila vrlo složena i dugotrajna, zahtijevajući promjene na stotinama krajnjih točaka aplikacije i potencijalno trajući mjesecima ili čak godinama. Budući da su naša opterećenja uglavnom usmjerena na čitanje, a implementirali smo opsežne optimizacije, trenutna arhitektura još uvijek pruža dovoljno prostora za podršku daljnjem rastu prometa. Iako ne isključujemo mogućnost segmentiranja PostgreSQL-a u budućnosti, to nam nije prioritet u bliskom razdoblju s obzirom na to da imamo dovoljno prostora za trenutačni i budući rast.

U sljedećim odjeljcima detaljno ćemo istražiti izazove s kojima smo se suočili i opsežne optimizacije koje smo proveli kako bismo ih riješili i spriječili buduće prekide rada, dovodeći PostgreSQL do njegovih granica i skalirajući ga na milijune upita u sekundi (QPS).

Smanjenje opterećenja na primarnom sustavu

Izazov: sa samo jednim 'piscem', postavka s jednim primarnim sustavom ne može skalirati upise. Nagla povećanja u zapisivanju mogu brzo preopteretiti primarni sustav i utjecati na usluge poput ChatGPT‑a i našeg API-ja.

Rješenje: smanjujemo opterećenje na primarnom sustavu što je više moguće — i za čitanje i za pisanje — kako bismo osigurali da ima dovoljno kapaciteta za podnošenje naglih povećanja u pisanju. Promet čitanja preusmjerava se na replike kad god je to moguće. Međutim, neki upiti za čitanje moraju ostati na primarnom sustavu jer su dio transakcija pisanja. Usredotočujemo se na to da budu učinkoviti i izbjegavamo spore upite. Za promet pisanja, migrirali smo radna opterećenja koja se mogu dijeliti i koja intenzivno koriste upis u podijeljene sustave kao što je Azure CosmosDB. Radna opterećenja koja je teže podijeliti na dijelove, ali i dalje generiraju velik obujam upisa, dulje se migriraju, a taj je proces još uvijek u tijeku. Također smo agresivno optimizirali naše aplikacije kako bismo smanjili opterećenje zapisivanja; primjerice, ispravili smo pogreške u aplikacijama koje su uzrokovale suvišna zapisivanja i uveli odgođena zapisivanja, gdje je to prikladno, kako bismo ublažili vršne opterećenja prometa. Osim toga, prilikom popunjavanja polja tablice, primjenjujemo stroga ograničenja brzine kako bismo spriječili prekomjerno opterećenje zapisivanja.

Optimizacija upita

Izazov: utvrdili smo nekoliko 'skupih' upita u PostgreSQL-u. U prošlosti su iznenadni skokovi u opterećenju ovih upita trošili velike količine CPU-a, usporavajući i ChatGPT i API zahtjeve.

Rješenje: nekoliko 'skupih' upita, poput onih koji spajaju mnoge tablice, može značajno pogoršati ili čak srušiti cijelu uslugu. Moramo kontinuirano optimizirati PostgreSQL upite kako bismo osigurali njihovu učinkovitost i izbjegli uobičajene anti-uzorke u obradi internetskih transakcija - Online Transaction Processing (OLTP). Primjerice, jednom smo identificirali iznimno zahtjevan upit koji je spajao 12 tablica, pri čemu su skokovi u tom upitu bili odgovorni za ranije SEV-ove visoke ozbiljnosti. Trebali bismo izbjegavati složene višestolne spojeve kad god je to moguće. Ako su spajanja nužna, naučili smo razmotriti razlaganje upita i premještanje složene logike spajanja u aplikacijski sloj. Mnogi od ovih problematičnih upita generiraju se pomoću okvira za objektno-relacijsko mapiranje (ORM), stoga je važno pažljivo pregledati SQL koji oni proizvode i osigurati da se ponaša kako se očekuje. Također je uobičajeno pronaći dugotrajne neaktivne upite u PostgreSQL-u. Konfiguriranje vremenskih ograničenja poput idle_in_transaction_session_timeout ključno je za sprječavanje blokiranja automatskog vakuuma.

Ublažavanje jedne točke kvara

Izazov: ako se replika za čitanje sruši, promet se i dalje može preusmjeriti na druge replike. Međutim, oslanjanje na jednog pisca znači imati jednu točku kvara — ako dođe do kvara, cijela usluga je pogođena.

Rješenje: većina najkritičnijih zahtjeva uključuje samo upite za čitanje. Kako bismo ublažili jedinstvenu točku kvara na primarnom sustavu, preusmjerili smo ta čitanja s glavnog sustava na replike, osiguravajući da ti zahtjevi mogu i dalje biti opsluženi čak i ako primarni sustav padne. Iako bi operacije pisanja i dalje bile neuspješne, utjecaj je smanjen; više nije SEV0 jer su operacije čitanja i dalje dostupne.

Kako bismo ublažili primarne kvarove, primarni sustav pokrećemo u načinu rada visoke dostupnosti (HA) s instancom pripravnosti (hot standby), kontinuirano sinkroniziranom replikom koja je uvijek spremna preuzeti opsluživanje prometa. Ako primarni sustav padne ili ga je potrebno isključiti radi održavanja, možemo brzo unaprijediti sustav u pripravnosti kako bismo smanjili vrijeme zastoja. Tim Azure PostgreSQL-a obavio je značajan posao kako bi osigurao da ovi prijelazi ostanu sigurni i pouzdani čak i pod vrlo velikim opterećenjem. Kako bismo riješili neuspjehe replika za čitanje, postavljamo više replika u svakoj regiji s dovoljnom rezervom kapaciteta, osiguravajući da neuspjeh jedne replike ne dovede do regionalnog prekida rada.

Izolacija radnog opterećenja

Izazov: često nailazimo na situacije u kojima određeni zahtjevi troše nerazmjerno veliku količinu resursa na instancama PostgreSQL-a. To može dovesti do smanjenja performansi za druga radna opterećenja koja se izvode na istim instancama. Na primjer, pokretanje nove značajke može uvesti neučinkovite upite koji značajno opterećuju CPU PostgreSQL-a, usporavajući zahtjeve za ostale ključne značajke.

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.

Dijagram proxyja za postgreSQL.

Svaka replika za čitanje ima vlastitu Kubernetes implementaciju koja pokreće više PgBouncer podova. Mui pokrećemo više Kubernetesa iza iste Kubernetes usluge, koja raspodjeljuje promet između podova.

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(otvara se u novom prozoru), 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.

postgreSQL dijagram kaskadne replikacije

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(otvara se u novom prozoru). 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(otvara se u novom prozoru) 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

Priznanja

Posebne zahvale Jonu Leeju, Sichengu Liuu, Chaominu Yuu i Chenglongu Haou, koji su doprinijeli ovoj objavi, kao i cijelom timu koji je pomogao u skaliranju PostgreSQL-a. Također želimo zahvaliti timu Azure PostgreSQL na njihovom čvrstom partnerstvu.