Salta al contingut principal
OpenAI

22 de gener del 2026

Enginyeria

Escalar PostgreSQL per donar servei a 800 milions d’usuaris de ChatGPT

Per Bohan Zhang, membre del personal tècnic

S'està carregant…

Durant anys, PostgreSQL ha estat un dels sistemes de dades més crítics que, entre bastidors, impulsen productes bàsics com ChatGPT i l’API d’OpenAI. A mesura que la nostra base d’usuaris creix ràpidament, les exigències sobre les nostres bases de dades també han augmentat exponencialment. Durant l’últim any, la nostra càrrega de PostgreSQL ha crescut més de 10 vegades i continua augmentant ràpidament.

Els nostres esforços per avançar la infraestructura de producció per sostenir aquest creixement ens van revelar una nova idea: PostgreSQL es pot escalar per donar suport de manera fiable a càrregues molt més grans amb predomini de lectura del que molts pensaven possible fins ara. El sistema (creat inicialment per un equip de científics de la Universitat de Califòrnia, Berkeley) ens ha permès donar suport a un trànsit global massiu amb una sola instància de servidor flexible d’Azure PostgreSQL(s'obre en una finestra nova) primària i gairebé 50 rèpliques de lectura repartides per múltiples regions del món. Aquesta és la història de com hem escalat PostgreSQL a OpenAI per donar suport a milions de consultes per segon per a 800 milions d’usuaris mitjançant optimitzacions rigoroses i una enginyeria sòlida; també compartirem les conclusions clau que hem après pel camí.

Esquerdes en el nostre disseny inicial

Després del llançament de ChatGPT, el trànsit va créixer a un ritme sense precedents. Per donar-hi suport, vam implementar ràpidament àmplies optimitzacions tant a la capa d’aplicació com a la de la base de dades PostgreSQL, vam escalar verticalment augmentant la mida de la instància i horitzontalment afegint més rèpliques de lectura. Aquesta arquitectura ens ha funcionat bé durant molt de temps. Amb millores contínues, continua oferint prou marge per al creixement futur.

Pot semblar sorprenent que una arquitectura amb una sola primària pugui satisfer les exigències de l’escala d’OpenAI; tanmateix, fer que això funcioni a la pràctica no és senzill. Hem vist diversos SEV causats per la sobrecàrrega de Postgres, i sovint segueixen el mateix patró: un problema aigües amunt provoca un pic sobtat de càrrega a la base de dades, com ara errors generalitzats de memòria cau per una fallada de la capa de memòria cau, un augment de joins costosos entre múltiples taules que saturen la CPU, o una tempesta d’escriptures arran del llançament d’una funcionalitat nova. A mesura que augmenta l’ús de recursos, puja la latència de les consultes i les sol·licituds comencen a esgotar el temps d’espera. Els reintents amplifiquen encara més la càrrega i desencadenen un cercle viciós amb el potencial de degradar tot ChatGPT i els serveis de l’API.

diagrama d’escalat de càrrega

Tot i que PostgreSQL escala bé per a les nostres càrregues amb predomini de lectura, encara ens trobem amb dificultats durant períodes de trànsit elevat d’escriptura. Això es deu en gran part a la implementació del control de concurrència multiversió (MVCC) de PostgreSQL, que el fa menys eficient per a càrregues amb moltes escriptures. Per exemple, quan una consulta actualitza una tupla o fins i tot un sol camp, es copia tota la fila per crear-ne una versió nova. Amb càrregues d’escriptura elevades, això provoca una amplificació d’escriptura significativa. També augmenta l’amplificació de lectura, ja que les consultes han d’escanejar múltiples versions de tupla (tuples mortes) per recuperar la més recent. L’MVCC introdueix altres dificultats, com ara la inflor de taules i índexs, més sobrecàrrega de manteniment dels índexs i un ajust complex de l’autovacuum. (Podeu trobar una anàlisi en profunditat d’aquests problemes en un blog que vaig escriure amb el prof. Andy Pavlo de la Carnegie Mellon University anomenat La part de PostgreSQL que més odiem(s'obre en una finestra nova), citada(s'obre en una finestra nova) a la pàgina de la Viquipèdia de PostgreSQL.)

Escalar PostgreSQL fins a milions de QPS

Per mitigar aquestes limitacions i reduir la pressió d’escriptura, hem migrat, i continuem migrant, les càrregues shardables (és a dir, càrregues que es poden particionar horitzontalment) amb moltes escriptures a sistemes fragmentats com Azure Cosmos DB, tot optimitzant la lògica de l’aplicació per minimitzar les escriptures innecessàries. A més, ja no permetem afegir taules noves al desplegament actual de PostgreSQL. Les càrregues noves van per defecte als sistemes fragmentats.

Tot i que la nostra infraestructura ha evolucionat, PostgreSQL s’ha mantingut sense fragmentar, amb una sola instància primària que atén totes les escriptures. El motiu principal és que fragmentar les càrregues existents de l’aplicació seria molt complex i requeriria molt de temps, amb canvis en centenars de punts finals de l’aplicació i un procés que podria durar mesos o fins i tot anys. Com que les nostres càrregues són principalment de lectura i hem implementat optimitzacions àmplies, l’arquitectura actual encara ofereix prou marge per continuar absorbint el creixement del trànsit. Tot i que no descartem fragmentar PostgreSQL en el futur, no és una prioritat a curt termini atès el marge suficient que tenim per al creixement actual i futur.

A les seccions següents aprofundirem en les dificultats amb què ens vam trobar i en les optimitzacions àmplies que vam implementar per abordar-les i evitar interrupcions futures, portant PostgreSQL al límit i escalant-lo fins a milions de consultes per segon (QPS).

Reducció de la càrrega sobre la primària

Repte: Amb un sol escriptor, una configuració amb una sola primària no pot escalar les escriptures. Pics intensos d’escriptura poden sobrecarregar ràpidament la primària i afectar serveis com ChatGPT i la nostra API.

Solució: Minimitzem tant com podem la càrrega sobre la primària —tant de lectura com d’escriptura— per garantir que tingui prou capacitat per gestionar pics d’escriptura. El trànsit de lectura es deriva a rèpliques sempre que és possible. Tanmateix, algunes consultes de lectura han de romandre a la primària perquè formen part de transaccions d’escriptura. En aquests casos, ens centrem a garantir que siguin eficients i a evitar consultes lentes. Pel que fa al trànsit d’escriptura, hem migrat les càrregues shardables amb moltes escriptures a sistemes fragmentats com Azure CosmosDB. Les càrregues més difícils de fragmentar però que encara generen un volum alt d’escriptura triguen més a migrar, i aquest procés continua en marxa. També hem optimitzat de manera agressiva les nostres aplicacions per reduir la càrrega d’escriptura; per exemple, hem corregit errors d’aplicació que provocaven escriptures redundants i hem introduït escriptures diferides, quan era adequat, per suavitzar els pics de trànsit. A més, quan fem backfill de camps de taula, apliquem límits de taxa estrictes per evitar una pressió d’escriptura excessiva.

Optimització de consultes

Repte: Vam identificar diverses consultes costoses a PostgreSQL. En el passat, els pics sobtats de volum d’aquestes consultes consumien grans quantitats de CPU i alentien tant ChatGPT com les sol·licituds de l’API.

Solució: Unes poques consultes costoses, com les que uneixen moltes taules, poden degradar significativament o fins i tot fer caure tot el servei. Hem d’optimitzar contínuament les consultes de PostgreSQL per garantir que siguin eficients i evitin anti patrons comuns del processament de transaccions en línia (OLTP). Per exemple, una vegada vam identificar una consulta extremadament costosa que unia 12 taules, i els pics d’aquesta consulta van ser responsables de SEV d’alta gravetat en el passat. Hem d’evitar els joins complexos entre múltiples taules sempre que sigui possible. Si els joins són necessaris, hem après a considerar descompondre la consulta i traslladar la lògica de joins complexos a la capa d’aplicació. Moltes d’aquestes consultes problemàtiques les generen marcs de mapatge objecte-relacional (ORM), per la qual cosa és important revisar amb cura l’SQL que produeixen i assegurar-se que es comporta com s’espera. També és habitual trobar consultes inactives de llarga durada a PostgreSQL. Configurar temps d’espera com idle_in_transaction_session_timeout és essencial per evitar que bloquegin l’autovacuum.

Mitigació del punt únic de fallada

Repte: Si cau una rèplica de lectura, el trànsit encara es pot encaminar a altres rèpliques. Tanmateix, dependre d’un sol escriptor significa tenir un únic punt de fallada: si cau, se’n ressent tot el servei.

Solució: La majoria de les sol·licituds crítiques només impliquen consultes de lectura. Per mitigar el punt únic de fallada de la primària, vam descarregar aquestes lectures de l’escriptor cap a rèpliques, de manera que aquestes sol·licituds puguin continuar prestant servei fins i tot si la primària cau. Tot i que les operacions d’escriptura continuarien fallant, l’impacte es redueix; ja no és un SEV0 perquè les lectures continuen disponibles.

Per mitigar les fallades de la primària, executem la primària en mode d’alta disponibilitat (HA) amb una espera activa, una rèplica sincronitzada contínuament que sempre està a punt per assumir el servei del trànsit. Si la primària cau o s’ha de desconnectar per manteniment, podem promocionar ràpidament l’espera per minimitzar el temps d’inactivitat. L’equip d’Azure PostgreSQL ha fet una feina significativa per garantir que aquests failovers continuïn sent segurs i fiables fins i tot amb càrregues molt altes. Per gestionar les fallades de les rèpliques de lectura, despleguem diverses rèpliques a cada regió amb prou marge de capacitat, assegurant que la fallada d’una sola rèplica no provoqui una interrupció regional.

Aïllament de càrregues

Repte: Sovint ens trobem situacions en què determinades sol·licituds consumeixen una quantitat desproporcionada de recursos a les instàncies de PostgreSQL. Això pot provocar una degradació del rendiment d’altres càrregues que s’executen a les mateixes instàncies. Per exemple, el llançament d’una funcionalitat nova pot introduir consultes ineficients que consumeixen molta CPU de PostgreSQL i alenteixen les sol·licituds d’altres funcionalitats crítiques.

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.

diagrama del proxy de PostgreSQL

Cada rèplica de lectura té el seu propi desplegament de Kubernetes, amb diversos pods de PgBouncer en execució. Executem diversos desplegaments de Kubernetes darrere del mateix Kubernetes Service, que distribueix el trànsit entre els pods.

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(s'obre en una finestra nova), 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.

diagrama de replicació en cascada de 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(s'obre en una finestra nova). 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(s'obre en una finestra nova) 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

Agraïments

Un agraïment especial a Jon Lee, Sicheng Liu, Chaomin Yu i Chenglong Hao, que han contribuït a aquesta publicació, i a tot l’equip que ha ajudat a escalar PostgreSQL. També volem donar les gràcies a l’equip d’Azure PostgreSQL per la seva sòlida col·laboració.