Overslaan naar hoofdinhoud
OpenAI

22 januari 2026

Engineering

PostgreSQL opschalen voor 800 miljoen ChatGPT‑gebruikers

Door Bohan Zhang, lid van de technische staf

Bezig met laden...

Jarenlang was PostgreSQL een van de meest kritieke systemen 'onder de motorkap' van onze kernproducten, zoals ChatGPT en de OpenAI API. Met de explosieve groei van ons gebruikersbestand zijn ook de eisen aan onze databases exponentieel toegenomen. In het afgelopen jaar is de belasting op onze PostgreSQL-omgeving meer dan vertienvoudigd, en deze groei zet onverminderd door.

Onze inspanningen om onze productie-infrastructuur te verbeteren en deze groei te faciliteren, leidden tot een nieuw inzicht: PostgreSQL kan zodanig worden geschaald dat het veel grotere leesintensieve workloads betrouwbaar ondersteunt dan velen voorheen voor mogelijk hielden. Het systeem (oorspronkelijk ontwikkeld door een team wetenschappers van de University of California, Berkeley) heeft ons in staat gesteld enorm wereldwijd verkeer te ondersteunen met slechts één primaire Azure PostgreSQL flexible server-instantie(opent in een nieuw venster) en bijna 50 leesreplica's verspreid over meerdere regio's wereldwijd. Dit is het verhaal van hoe we PostgreSQL bij OpenAI hebben opgeschaald om miljoenen query's per seconde te verwerken voor 800 miljoen gebruikers, door middel van rigoureuze optimalisaties en solide engineering. Ook behandelen we de belangrijkste lessen die we onderweg hebben geleerd.

De barsten in ons eerste ontwerp

Na de lancering van ChatGPT groeide het verkeer met een ongekende snelheid. Om dit op te vangen, hebben we snel uitgebreide optimalisaties doorgevoerd in zowel de applicatie- als de PostgreSQL-databaselaag. We hebben verticaal opgeschaald (door de omvang van de instantie te vergroten) en horizontaal uitgeschaald (door meer leesreplica's toe te voegen). Deze architectuur heeft ons lange tijd goed gediend. Dankzij voortdurende verbeteringen biedt het nog steeds voldoende ruimte voor toekomstige groei.

Hoewel een single-primary architectuur de schaal van OpenAI (verrassend genoeg) aankan, is dit in de praktijk niet eenvoudig te realiseren. We hebben diverse ernstige incidenten gehad door overbelasting van Postgres. Deze volgen vaak hetzelfde patroon: een probleem stroomopwaarts veroorzaakt een plotselinge piek in databasebelasting, zoals wijdverspreide cache misses door een storing in de caching-laag, een golf van dure meervoudige joins die de CPU verzadigen, of een write storm door de lancering van een nieuwe functie. Naarmate het gebruik van resources toeneemt, stijgt de query-latency en ontstaan er time-outs bij requests. Herhaalde pogingen vergroten vervolgens de belasting nog verder, wat leidt tot een vicieuze cirkel die alle ChatGPT‑diensten en de API kan platleggen.

Diagram Belasting opschalen

Hoewel PostgreSQL goed schaalt voor onze leesintensieve workloads, lopen we nog steeds tegen uitdagingen aan tijdens periodes van veel schrijfverkeer. Dit komt grotendeels door de implementatie van multiversion concurrency control (MVCC) in PostgreSQL, wat het minder efficiënt maakt voor schrijfintensieve workloads. Wanneer een query bijvoorbeeld een tuple of zelfs maar één veld bijwerkt, wordt de hele rij gekopieerd om een nieuwe versie te maken. Onder zware schrijfbelasting resulteert dit in aanzienlijke schrijfamplifiatie. Het verhoogt ook de leesamplificatie, aangezien query's door meerdere tuple-versies (dode tuples) moeten scannen om de nieuwste op te halen. MVCC introduceert extra uitdagingen zoals bloat van tabellen en indexen, verhoogde overhead voor indexonderhoud en complexe autovacuum-tuning. (Je vindt een diepgaande analyse van deze problemen in een blog die ik schreef met prof. Andy Pavlo van Carnegie Mellon University, getiteld The Part of PostgreSQL We Hate the Most(opent in een nieuw venster), waarnaar verwezen(opent in een nieuw venster) wordt op de Wikipedia-pagina van PostgreSQL).

PostgreSQL opschalen naar miljoenen QPS

Om deze beperkingen aan te pakken en de schrijfdruk te verlagen, hebben we shardable workloads (die horizontaal gepartitioneerd kunnen worden) gemigreerd naar systemen zoals Azure Cosmos DB. Ook hebben we de applicatielogica geoptimaliseerd om onnodige schrijfacties te minimaliseren. Daarnaast staan we niet langer toe dat er nieuwe tabellen worden toegevoegd aan de huidige PostgreSQL-omgeving. Nieuwe workloads gaan standaard naar de gesharde systemen.

Ondanks deze evolutie is PostgreSQL zelf niet-geshard gebleven, met één primaire instantie voor alle schrijfacties. De belangrijkste reden hiervoor is dat het sharden van bestaande applicatiebelasting extreem complex en tijdrovend is. Dit zou aanpassingen vereisen in honderden endpoints en maanden of zelfs jaren kunnen duren. Omdat onze workloads voornamelijk leesintensief zijn en we veel hebben geoptimaliseerd, biedt de huidige architectuur nog voldoende capaciteit om de voortdurende verkeersgroei te ondersteunen. Hoewel we het sharden van PostgreSQL in de toekomst niet uitsluiten, heeft het op de korte termijn geen prioriteit, omdat we nog voldoende speelruimte hebben voor de huidige en toekomstige groei.

In de volgende secties duiken we dieper in de uitdagingen waarmee we zijn geconfronteerd en de uitgebreide optimalisaties die we hebben doorgevoerd om deze aan te pakken en toekomstige storingen te voorkomen. Hiermee drijven we PostgreSQL tot het uiterste en schalen we op naar miljoenen query's per seconde (QPS).

De belasting op de primaire server verlagen

Uitdaging: Met slechts één schrijf-instantie kan een single-primary architectuur schrijfacties niet schalen. Grote pieken in schrijfverkeer kunnen de primaire server snel overbelasten en diensten zoals ChatGPT en onze API beïnvloeden.

Oplossing: We minimaliseren de belasting op de primaire server zoveel mogelijk (zowel voor lezen als schrijven), zodat er altijd genoeg capaciteit is om pieken op te vangen. Leesverkeer wordt waar mogelijk overgedragen aan replica's. Alleen query's binnen een schrijftransactie blijven op de primaire server. Hierbij focussen we strikt op efficiëntie om trage query's te voorkomen. Voor schrijfverkeer hebben we shardable, write-heavy taken gemigreerd naar gesharde systemen zoals Azure Cosmos DB. Workloads die moeilijker te sharden zijn maar toch een hoog schrijfvolume genereren, vergen meer tijd om te migreren, en dat proces is nog gaande. We hebben ook onze applicaties agressief geoptimaliseerd om de schrijflast te verminderen. Zo hebben we bugs opgelost die redundante schrijfacties veroorzaakten en, waar passend, lazy writes geïntroduceerd om verkeerspieken af te vlakken. Daarnaast hanteren we bij het backfillen van tabelvelden strikte verwerkingslimieten om excessieve schrijfdruk te voorkomen.

Query-optimalisatie

Uitdaging: We hebben verschillende dure query's in PostgreSQL geïdentificeerd. In het verleden zorgden plotselinge volumepieken in deze query's voor een groot CPU-verbruik, wat zowel ChatGPT- als API-verzoeken vertraagde.

Oplossing: Een paar dure query's, zoals die waarbij veel tabellen worden samengevoegd, kunnen de service aanzienlijk vertragen of zelfs platleggen. We moeten PostgreSQL-query's continu optimaliseren om efficiëntie te garanderen en veelvoorkomende OLTP-antipatronen te vermijden. Zo hebben we ooit een extreem kostbare query geïdentificeerd die 12 tabellen joinde, waarbij pieken in deze query verantwoordelijk waren voor eerdere ernstige incidenten. We moeten complexe joins over meerdere tabellen waar mogelijk vermijden. Als joins noodzakelijk zijn, hebben we geleerd de query op te splitsen en complexe join-logica naar de applicatielaag te verplaatsen. Veel van deze problematische query's worden gegenereerd door ORM-frameworks (Object-Relational Mapping), dus het is belangrijk om de geproduceerde SQL zorgvuldig te beoordelen. Het komt ook vaak voor dat er langlopende inactieve query's in PostgreSQL staan. Het configureren van time-outs, zoals idle_in_transaction_session_timeout, is essentieel om te voorkomen dat deze het autovacuum-proces blokkeren.

Beperken van de 'single point of failure'

Uitdaging: Als een replica uitvalt, kan het verkeer eenvoudig worden omgeleid naar andere replica's. Het gebruik van één enkele schrijf-instantie vormt echter een risico: als deze uitvalt, ligt de gehele dienstverlening plat.

Oplossing: De meest kritieke verzoeken bestaan enkel uit lees-query's. Om de single point of failure bij de primaire server te beperken, hebben we dit leesverkeer verplaatst van de schrijf-instantie naar replica's. Hierdoor kunnen deze verzoeken afgehandeld blijven worden, zelfs als de primaire server uitvalt. Hoewel schrijfbewerkingen dan nog steeds mislukken, is de impact beperkt. Het is geen kritiek 'SEV0'-incident meer zolang de leesfunctionaliteit beschikbaar blijft.

Om uitval van de primaire server op te vangen, draaien we deze in High-Availability (HA)-modus met een hot standby: een continu gesynchroniseerde replica die altijd klaarstaat om het verkeer over te nemen. Mocht de primaire server uitvallen of offline gehaald moeten worden voor onderhoud, dan kunnen we de standby snel promoveren om downtime te minimaliseren. Het Azure PostgreSQL-team heeft aanzienlijke inspanningen geleverd om te garanderen dat deze failovers veilig en betrouwbaar verlopen, zelfs onder zeer zware belasting. Om uitval van leesreplica’s op te vangen, zetten we meerdere replica's in per regio met voldoende overcapaciteit. Dit garandeert dat het uitvallen van één enkele replica niet leidt tot een regionale storing.

Isolatie van workloads

Uitdaging: We komen vaak situaties tegen waarin bepaalde verzoeken onevenredig veel resources op PostgreSQL-instanties verbruiken. Dit kan leiden tot verminderde prestaties voor andere workloads die op dezelfde instanties draaien. De lancering van een nieuwe functie kan bijvoorbeeld inefficiënte query's introduceren die zwaar beslag leggen op de CPU, waardoor verzoeken voor andere kritieke functies vertragen.

Oplossing: Om het 'noisy neighbor'-probleem aan te pakken, isoleren we workloads op speciale instanties. Zo zorgen we ervoor dat plotselinge pieken in resource-intensieve verzoeken geen invloed hebben op ander verkeer. Concreet splitsen we verzoeken op in lagen met lage en hoge prioriteit en leiden we deze naar afzonderlijke instanties. Op deze manier gaan de prestaties van verzoeken met hoge prioriteit niet achteruit, zelfs als een workload met lage prioriteit veel resources vraagt. We passen dezelfde strategie toe over verschillende producten en diensten heen, zodat activiteit van het ene product de prestaties of betrouwbaarheid van het andere niet beïnvloedt.

Connection pooling

Uitdaging: Elke instantie heeft een limiet op het aantal verbindingen (5.000 in Azure PostgreSQL). Het is eenvoudig om door de beschikbare verbindingen heen te raken of te veel inactieve verbindingen op te bouwen. We hebben in het verleden incidenten gehad die werden veroorzaakt door connection storms, waarbij alle beschikbare verbindingen uitgeput raakten.

Oplossing: We hebben PgBouncer ingezet als proxylaag om databaseverbindingen te poolen. Door dit in statement- of transaction pooling-modus te draaien, kunnen we verbindingen efficiënt hergebruiken, wat het aantal actieve clientverbindingen aanzienlijk vermindert. Dit verlaagt ook de latency bij het opzetten van verbindingen: in onze benchmarks daalde de gemiddelde verbindingstijd van 50 milliseconden (ms) naar 5 ms. Verbindingen en verzoeken tussen regio's kunnen kostbaar zijn. Daarom plaatsen we de proxy, clients en replica's in dezelfde regio om netwerkoverhead en de duur van het verbindingsgebruik te minimaliseren. Bovendien moet PgBouncer zorgvuldig worden geconfigureerd. Instellingen zoals idle time-outs zijn cruciaal om uitputting van verbindingen te voorkomen.

Diagram PostgreSQL-proxy

Elke lees-replica heeft zijn eigen Kubernetes-deployment die meerdere PgBouncer-pods draait. We draaien meerdere Kubernetes-deployments achter dezelfde Kubernetes Service, die het verkeer over de pods verdeelt.

Caching

Uitdaging: Een plotselinge piek in cache misses kan leiden tot een golf van leesacties op de PostgreSQL-database, waardoor de CPU verzadigd raakt en verzoeken van gebruikers vertragen.

Oplossing: Om de leesdruk op PostgreSQL te verminderen, gebruiken we een caching-laag om het grootste deel van het leesverkeer af te handelen. Wanneer de cache hit rate echter onverwacht daalt, kan een plotselinge golf van misses een groot volume aan verzoeken direct naar PostgreSQL sturen. Deze plotselinge toename van database-reads verbruikt aanzienlijk veel resources en vertraagt de dienst. Om overbelasting tijdens zo'n storm van cache misses te voorkomen, implementeren we een mechanisme voor cache locking (en leasing). Hierdoor haalt slechts één request die een miss krijgt op een bepaalde sleutel, de data daadwerkelijk op uit PostgreSQL. Wanneer meerdere verzoeken dezelfde cache key missen, verkrijgt slechts één verzoek de lock om de data op te halen en de cache opnieuw te vullen. Alle andere verzoeken wachten tot de cache is bijgewerkt in plaats van allemaal tegelijk PostgresSQL te bevragen. Dit vermindert het aantal redundante database-reads aanzienlijk en beschermt het systeem tegen escalerende belastingpieken.

Opschalen van leesreplica’s

Uitdaging: De primaire server streamt Write Ahead Log (WAL)-data naar elke leesreplica. Naarmate het aantal replica's toeneemt, moet de primaire server de WAL naar meer instanties versturen, wat de druk op zowel de netwerkbandbreedte als de CPU verhoogt. Dit veroorzaakt een hogere en onstabielere replicavertraging, waardoor het systeem moeilijker betrouwbaar op te schalen is.

Oplossing: We beheren bijna 50 leesreplica's in meerdere geografische regio's om latency te minimaliseren. Met de huidige architectuur moet de primaire server echter WAL (Write Ahead Log) streamen naar elke replica. Hoewel dit momenteel goed schaalt met zeer grote instantietypes en hoge netwerkbandbreedte, kunnen we niet onbeperkt replica's blijven toevoegen zonder de primaire server uiteindelijk te overbelasten. Om dit aan te pakken, werken we samen met het Azure PostgreSQL-team aan cascading replication(opent in een nieuw venster), waarbij tussenliggende replica's de WAL doorsturen naar stroomafwaartse replica's. Met deze aanpak kunnen we schalen naar potentieel meer dan honderd replica's zonder de primaire server te overweldigen. Het introduceert echter ook extra operationele complexiteit, vooral rondom het beheer van failovers. De functie wordt nog getest. We zorgen ervoor dat deze robuust is en veilig kan failoveren voordat we deze uitrollen naar productie.

Diagram PostgreSQL cascading replication

Verwerkingslimieten

Uitdaging: Een plotselinge verkeerspiek op specifieke endpoints, een golf van kostbare query's, of een retry storm kan kritieke resources zoals CPU, I/O en verbindingen snel uitputten, wat leidt tot een brede degradatie van de dienstverlening.

Oplossing: We hebben verwerkingslimieten geïmplementeerd op meerdere lagen (applicatie, connection pooler, proxy en query) om te voorkomen dat plotselinge verkeerspieken de database-instanties overweldigen en kettingreacties van storingen veroorzaken. Het is ook cruciaal om te korte intervallen voor nieuwe pogingen te vermijden, aangezien deze retry storms kunnen veroorzaken. Daarnaast hebben we de ORM-laag verbeterd om verwerkingslimieten te ondersteunen en, indien nodig, specifieke query digests volledig te blokkeren. Deze gerichte vorm van load shedding maakt snel herstel mogelijk bij plotselinge golven van kostbare query's.

Schemabeheer

Uitdaging: Zelfs een kleine schemawijziging, zoals het aanpassen van een kolomtype, kan leiden tot het volledig herschrijven van de tabel(opent in een nieuw venster). Daarom passen we schemawijzigingen voorzichtig toe: we beperken ze tot lichtgewicht bewerkingen en vermijden wijzigingen die hele tabellen herschrijven.

Oplossing: Alleen lichte schemawijzigingen zijn toegestaan, zoals het toevoegen of verwijderen van bepaalde kolommen waarbij de volledige tabel niet herschreven hoeft te worden. We hanteren een strikte time-out van 5 seconden voor schemawijzigingen. Het aanmaken en verwijderen van indexen met de CONCURRENTLY-optie is toegestaan. Schemawijzigingen zijn beperkt tot bestaande tabellen. Als een nieuwe functie extra tabellen vereist, moeten deze worden ondergebracht in alternatieve sharded systemen zoals Azure Cosmos DB in plaats van PostgreSQL. Bij het backfillen van een tabelveld passen we strikte verwerkingslimieten toe om schrijfpieken te voorkomen. Hoewel dit proces soms meer dan een week kan duren, garandeert het stabiliteit en voorkomt het impact op de productieomgeving.

Resultaten en de weg vooruit

Deze inspanning toont aan dat Azure PostgreSQL met het juiste ontwerp en de juiste optimalisaties geschaald kan worden om de grootste productieworkloads aan te kunnen. PostgreSQL verwerkt miljoenen QPS voor leesintensieve workloads en drijft de meest kritieke producten van OpenAI aan, zoals ChatGPT en het API-platform. We hebben bijna 50 leesreplica's toegevoegd terwijl de replicavertraging nagenoeg nul bleef, we handhaafden low-latency reads over geografisch verspreide regio's, en bouwden voldoende overcapaciteit op om toekomstige groei te ondersteunen.

Deze manier van schalen werkt, terwijl de latency geminimaliseerd blijft en de betrouwbaarheid verbetert. We leveren consistent een p99-latentie van enkele tientallen milliseconden aan de clientzijde en een beschikbaarheid van vijf negens (99,999%). En in de afgelopen 12 maanden hebben we slechts één SEV-0 PostgreSQL-incident gehad (dit gebeurde tijdens de virale lancering(opent in een nieuw venster) van ChatGPT ImageGen, toen het schrijfverkeer plotseling met een factor 10 toenam doordat meer dan 100 miljoen nieuwe gebruikers zich binnen een week aanmeldden).

Hoewel we tevreden zijn met hoe ver PostgreSQL ons heeft gebracht, blijven we de grenzen opzoeken om ervoor te zorgen dat we voldoende ruimte hebben voor toekomstige groei. We hebben de shardable schrijfintensieve workloads al gemigreerd naar onze gesharde systemen zoals Cosmos DB. De resterende schrijfintensieve workloads zijn lastiger te sharden, maar we zijn actief bezig ook deze te migreren om de schrijflast op de primaire PostgreSQL-server verder te verlichten. Daarnaast werken we samen met Azure om cascading replication mogelijk te maken, zodat we veilig kunnen opschalen naar aanzienlijk meer leesreplica's.

Vooruitkijkend blijven we aanvullende benaderingen verkennen om verder te schalen, waaronder sharded PostgreSQL of alternatieve gedistribueerde systemen, naarmate de eisen aan onze infrastructuur blijven groeien.

Auteur

Bohan Zhang

Dankwoord

Speciale dank aan Jon Lee, Sicheng Liu, Chaomin Yu en Chenglong Hao voor hun bijdrage aan dit artikel, en aan het hele team dat heeft geholpen bij het opschalen van PostgreSQL. We willen ook het Azure PostgreSQL-team bedanken voor hun sterke partnerschap.