Ir al contenido principal
OpenAI

22 de enero de 2026

Ingeniería

Escalar PostgreSQL para atender a 800 mill. usuarios de ChatGPT

Por Bohan Zhang, miembro del personal técnico

Cargando…

Durante años, PostgreSQL ha sido uno de los sistemas de datos más críticos en segundo plano, que ha impulsado productos clave como ChatGPT y la API de OpenAI. A medida que nuestra base de usuarios crece con rapidez, las demandas sobre nuestras bases de datos también aumentan de forma exponencial. Durante el último año, nuestra carga de PostgreSQL se ha multiplicado por más de 10 y sigue aumentando con rapidez.

Nuestros esfuerzos por mejorar nuestra infraestructura de producción para sostener este crecimiento revelaron un nuevo hallazgo: PostgreSQL puede escalar para soportar de forma fiable cargas de trabajo con predominio de lectura mucho mayores de lo que muchos creían posible. El sistema (creado inicialmente por un equipo de científicos de la Universidad de California, Berkeley) nos ha permitido dar soporte a un tráfico global masivo con una única instancia principal de servidor flexible de Azure PostgreSQL(se abre en una ventana nueva) y casi 50 réplicas de lectura distribuidas en varias regiones del mundo. Esta es la historia de cómo hemos escalado PostgreSQL en OpenAI para soportar millones de consultas por segundo para 800 millones de usuarios mediante optimizaciones rigurosas y una ingeniería sólida; también compartiremos las principales lecciones que aprendimos en el camino.

Fallos en nuestro diseño inicial

Tras el lanzamiento de ChatGPT, el tráfico creció a un ritmo sin precedentes. Para respaldarlo, implementamos con rapidez amplias optimizaciones tanto en la capa de aplicación como en la de bases de datos PostgreSQL. Escalamos en vertical aumentando el tamaño de las instancias y en horizontal añadiendo más réplicas de lectura. Esta arquitectura nos ha funcionado bien durante mucho tiempo. Con las mejoras continuas, sigue proporcionando un amplio margen para el crecimiento futuro.

Puede parecer sorprendente que una arquitectura de primario único pueda satisfacer las exigencias de la escala de OpenAI; sin embargo, conseguirlo en la práctica no es sencillo. Hemos visto varios incidentes de gravedad (SEV) causados por la sobrecarga de Postgres, y a menudo siguen el mismo patrón: un problema en una capa superior provoca un pico repentino en la carga de la base de datos, como fallos de caché generalizados por un fallo en la capa de caché, un aumento de costosas uniones múltiples que saturan la CPU o una ráfaga de escritura por el lanzamiento de una nueva funcionalidad. A medida que sube la utilización de recursos, la latencia de las consultas aumenta y las solicitudes empiezan a agotar su tiempo de espera. Los reintentos amplifican aún más la carga, lo que desencadena un círculo vicioso con el potencial de degradar todos los servicios de ChatGPT y API.

Diagrama de escalado de carga

Aunque PostgreSQL escala bien para nuestras cargas de trabajo con predominio de lectura, seguimos enfrentando retos durante períodos de alto tráfico de escritura. Gran parte de la causa está en la implementación del control de concurrencia multiversión (MVCC) de PostgreSQL, que lo hace menos eficiente para cargas de trabajo intensivas en escritura. Por ejemplo, cuando una consulta actualiza una tupla o incluso un solo campo, se copia toda la fila para crear una nueva versión. Con cargas de escritura elevadas, el resultado es una amplificación de escritura significativa. También incrementa la amplificación de lectura, ya que las consultas deben escanear múltiples versiones de tuplas (tuplas muertas) para obtener la más reciente. MVCC introduce retos adicionales, como el crecimiento descontrolado (bloat) de tablas e índices, un mayor coste de mantenimiento de índices y una configuración compleja de autovacuum. (Puedes encontrar un análisis en profundidad sobre estos problemas en un blog que escribí junto con el Prof. Andy Pavlo en la Universidad Carnegie Mellon titulado The Part of PostgreSQL We Hate the Most(se abre en una ventana nueva), citado(se abre en una ventana nueva) en la página de Wikipedia de PostgreSQL).

Escalar PostgreSQL a millones de QPS

Para mitigar estas limitaciones y reducir la presión de escritura, hemos migrado, y seguimos migrando, las cargas de trabajo fragmentables (es decir, las que se pueden distribuir en particiones en horizontal) e intensivas en escritura a sistemas fragmentados como Azure Cosmos DB, optimizando la lógica de la aplicación para minimizar la escritura innecesaria. Tampoco permitimos ya añadir nuevas tablas a la implementación actual de PostgreSQL. Las nuevas cargas de trabajo se asignan por defecto a los sistemas fragmentados.

Aunque nuestra infraestructura ha evolucionado, PostgreSQL sigue sin fragmentar, con una única instancia primaria que gestiona todas las escrituras. La razón principal es que fragmentar las cargas de trabajo existentes de la aplicación sería muy complejo y llevaría mucho tiempo, ya que requeriría cambios en cientos de puntos de acceso de la aplicación y podría tardar meses o incluso años. Dado que nuestras cargas de trabajo son principalmente de lectura y hemos implementado amplias optimizaciones, la arquitectura actual sigue ofreciendo un amplio margen para soportar el crecimiento continuo del tráfico. Aunque no descartamos fragmentar PostgreSQL en el futuro, no es una prioridad a corto plazo, dado el margen suficiente que tenemos para el crecimiento actual y futuro.

En las siguientes secciones, profundizaremos en los retos a los que nos enfrentamos y en las amplias optimizaciones que implementamos para abordarlos y prevenir futuras interrupciones, con las que llevamos PostgreSQL al límite y lo escalamos a millones de consultas por segundo (QPS).

Reducir la carga en el primario

Reto: con un solo escritor, una configuración de primario único no puede escalar las escrituras. Los picos de escritura intensos pueden sobrecargar con rapidez el primario y afectar a servicios como ChatGPT y nuestra API.

Solución: minimizamos la carga en el primario tanto como sea posible, tanto en lecturas como en escrituras, para asegurar que tenga capacidad suficiente para manejar los picos de escritura. El tráfico de lectura se descarga a las réplicas siempre que es posible. Sin embargo, algunas consultas de lectura deben permanecer en el primario porque forman parte de transacciones de escritura. En esos casos, nos centramos en asegurar que sean eficientes y en evitar consultas lentas. Para el tráfico de escritura, hemos migrado las cargas de trabajo fragmentables e intensivas en escritura a sistemas fragmentados como Azure CosmosDB. Las cargas de trabajo que son más difíciles de fragmentar, pero que aún generan un alto volumen de escritura, tardan más en migrarse, y ese proceso sigue en curso. También optimizamos de forma agresiva nuestras aplicaciones para reducir la carga de escritura; por ejemplo, corregimos errores de la aplicación que causaban escrituras redundantes e introdujimos escrituras diferidas, cuando era apropiado, para suavizar los picos de tráfico. Además, al rellenar campos de tabla, aplicamos límites de tasa estrictos para evitar una presión de escritura excesiva.

Optimización de consultas

Reto: identificamos varias consultas costosas en PostgreSQL. En el pasado, los picos repentinos de volumen en estas consultas consumían grandes cantidades de CPU, lo que ralentizaba tanto ChatGPT como las solicitudes de API.

Solución: unas pocas consultas costosas, como las que unen muchas tablas, pueden degradar de forma significativa o incluso tumbar todo el servicio. Necesitamos optimizar de forma continua las consultas de PostgreSQL para asegurar que sean eficientes y evitar los antipatrones comunes del procesamiento de transacciones en línea (OLTP). Por ejemplo, en una ocasión identificamos una consulta muy costosa que unía 12 tablas, cuyos picos fueron responsables de SEV de alta gravedad en el pasado. Debemos evitar las uniones complejas de varias tablas siempre que sea posible. Si las uniones son necesarias, aprendimos a considerar la posibilidad de dividir la consulta y trasladar la lógica compleja de uniones a la capa de aplicación. Muchas de estas consultas problemáticas las generan los frameworks de mapeo objeto-relacional (ORM), por lo que es importante revisar con cuidado el SQL que producen y asegurar que se comporte como se espera. También es común encontrar consultas inactivas de larga duración en PostgreSQL. Configurar tiempos de espera como idle_in_transaction_session_timeout es esencial para evitar que bloqueen el autovacuum.

Mitigación del punto único de fallo

Reto: si una réplica de lectura se cae, el tráfico aún puede redirigirse a otras réplicas. Sin embargo, depender de un único escritor significa tener un único punto de fallo: si falla, todo el servicio se ve afectado.

Solución: la mayoría de las solicitudes críticas solo implican consultas de lectura. Para mitigar el punto único de fallo en el primario, trasladamos esas lecturas del escritor a las réplicas, lo que asegura que esas solicitudes puedan seguir atendiéndose aunque el primario falle. Aunque las operaciones de escritura seguirían fallando, el impacto se reduce; ya no es un SEV0, ya que las lecturas siguen disponibles.

Para mitigar los fallos del primario, lo ejecutamos en modo de alta disponibilidad (HA) con espera activa, una réplica sincronizada de forma continua que siempre está lista para asumir el tráfico. Si el primario se cae o necesita desconectarse para mantenimiento, podemos promover con rapidez la espera para minimizar el tiempo de inactividad. El equipo de Azure PostgreSQL ha hecho un trabajo significativo para asegurar que estas conmutaciones por error sigan siendo seguras y fiables incluso bajo una carga muy alta. Para gestionar los fallos de las réplicas de lectura, desplegamos varias réplicas en cada región con suficiente margen de capacidad, lo que asegura que el fallo de una sola réplica no cause una interrupción regional.

Aislamiento de cargas de trabajo

Reto: a menudo nos encontramos con situaciones en las que ciertas solicitudes consumen una cantidad desproporcionada de recursos en instancias de PostgreSQL. El resultado puede ser un rendimiento degradado para otras cargas de trabajo que se ejecutan en las mismas instancias. Por ejemplo, el lanzamiento de una nueva funcionalidad puede introducir consultas ineficientes que consumen mucha CPU de PostgreSQL, lo que ralentiza las solicitudes de otras funcionalidades críticas.

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 de proxy de PostgreSQL

Cada réplica de lectura tiene su propio despliegue de Kubernetes que ejecuta varios pods de PgBouncer. Ejecutamos varios despliegues de Kubernetes detrás del mismo servicio de Kubernetes, que distribuye el tráfico entre los 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(se abre en una ventana nueva), 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ón 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(se abre en una ventana nueva). 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(se abre en una ventana nueva) 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

Agradecimientos

Un agradecimiento especial a Jon Lee, Sicheng Liu, Chaomin Yu y Chenglong Hao, quienes contribuyeron a esta publicación, y a todo el equipo que ayudó a escalar PostgreSQL. También nos gustaría agradecer al equipo de Azure PostgreSQL por su sólida colaboración.