Pasar al contenido principal
OpenAI

22 de enero de 2026

Ingeniería

Escalar PostgreSQL para asistir a 800 millones de usuarios

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, operando en segundo plano, que impulsa productos clave como ChatGPT y la API de OpenAI. A medida que nuestra base de usuarios crece rápidamente, las exigencias sobre nuestras bases de datos también han aumentado exponencialmente. Durante el último año, nuestra carga de PostgreSQL se ha multiplicado por más de diez veces, y sigue aumentando rápidamente.

Nuestros esfuerzos por mejorar nuestra infraestructura de producción para sostener este crecimiento revelaron un nuevo hallazgo: PostgreSQL puede escalarse para soportar de manera confiable cargas de trabajo de lectura intensiva mucho más grandes de lo que muchos pensaban posible. El sistema (creado inicialmente por un equipo de científicos de la Universidad de California, Berkeley) nos ha permitido manejar un tráfico global masivo con una sola Azure PostgreSQL flexible server instance(se abre en una nueva ventana) principal y casi 50 réplicas de lectura distribuidas en múltiples regiones a nivel mundial. 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 e ingeniería sólida; también compartiremos las lecciones clave que aprendimos en el camino.

Grietas en nuestro diseño inicial

Después del lanzamiento de ChatGPT, el tráfico creció a un ritmo sin precedentes. Para respaldarlo, implementamos rápidamente optimizaciones extensas tanto en la aplicación como en las capas de la base de datos PostgreSQL, escalamos verticalmente al aumentar el tamaño de la instancia y escalamos horizontalmente al agregar más réplicas de lectura. Esta arquitectura nos ha servido bien por mucho tiempo. Con mejoras continuas, sigue proporcionando un amplio margen para el crecimiento futuro.

Puede parecer sorprendente que una arquitectura de un solo primario pueda satisfacer las demandas de la escala de OpenAI; sin embargo, hacer que esto funcione en la práctica no es sencillo. Hemos visto varios SEVs causados por sobrecarga de Postgres, y a menudo siguen el mismo patrón: un problema aguas arriba provoca un pico repentino en la carga de la base de datos, como fallos generalizados de caché por una falla en la capa de caché, un aumento de uniones costosas de múltiples tablas que saturan la CPU, o una tormenta de escrituras por el lanzamiento de una nueva funcionalidad. A medida que aumenta la utilización de recursos, la latencia de las consultas se incrementa y las solicitudes comienzan a expirar. Los reintentos amplifican aún más la carga, desencadenando un círculo vicioso con el potencial de degradar todos los servicios de ChatGPT y API.

Diagrama de escala de carga

Aunque PostgreSQL escala bien para nuestras cargas de trabajo con predominio de lecturas, todavía enfrentamos desafíos durante períodos de alto tráfico de escritura. Esto se debe en gran medida a la implementación de control de concurrencia multiversión (MVCC) de PostgreSQL, lo 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. Bajo cargas pesadas de escritura, esto resulta en una amplificación significativa de escritura. También aumenta la amplificación de lectura, ya que las consultas deben escanear múltiples versiones de tuplas (tuplas obsoletas) para recuperar la más reciente. MVCC introduce desafíos adicionales, como el crecimiento excesivo de tablas e índices, una mayor sobrecarga de mantenimiento de índices y un ajuste complejo de autovacuum. (Puedes encontrar un análisis en profundidad de estos problemas en un blog que escribí con el Prof. Andy Pavlo en la Universidad Carnegie Mellon llamado The Part of PostgreSQL We Hate the Most(se abre en una nueva ventana), citado(se abre en una nueva ventana) 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 los elementos que se pueden fragmentar (p. ej., cargas de trabajo que se pueden particionar horizontalmente), cargas de trabajo intensivas en escritura en sistemas fragmentados como Azure Cosmos DB, optimizando la lógica de la aplicación para minimizar las escrituras innecesarias. Tampoco permitimos agregar nuevas tablas a la implementación actual de PostgreSQL. Las nuevas cargas de trabajo por defecto se asignan a los sistemas fragmentados.

Aun cuando nuestra infraestructura ha evolucionado, PostgreSQL ha permanecido sin fragmentar, con una sola instancia primaria que maneja todas las escrituras. La razón principal es que dividir las cargas de trabajo existentes de la aplicación sería muy complejo y llevaría mucho tiempo, requiriendo cambios en cientos de puntos de acceso de la aplicación y podría tomar meses o incluso años. Dado que nuestras cargas de trabajo son principalmente de lectura y hemos implementado optimizaciones extensas, la arquitectura actual todavía ofrece un amplio margen para soportar el crecimiento continuo del tráfico. Aunque no descartamos la posibilidad de fragmentar PostgreSQL en el futuro, no es una prioridad inmediata debido al margen suficiente que tenemos para el crecimiento presente y futuro.

En las siguientes secciones, exploraremos los desafíos que enfrentamos y las extensas optimizaciones que implementamos para resolverlos y prevenir futuras interrupciones, llevando a PostgreSQL al límite y escalándolo a millones de consultas por segundo (QPS).

Reducir la carga en el sistema principal

Desafío: Con un solo escritor, una configuración de un único primario no puede escalar las escrituras. Los picos intensos de escritura pueden sobrecargar rápidamente el sistema principal y afectar servicios como ChatGPT y nuestra API.

Solución: Minimizamos la carga en el primario tanto como sea posible, tanto lecturas como escrituras, para garantizar que tenga suficiente capacidad para manejar picos de escritura. El tráfico de lectura se transfiere a réplicas siempre que sea posible. Sin embargo, algunas consultas de lectura deben permanecer en el primario porque son parte de transacciones de escritura. Para ellos, nos enfocamos en garantizar que sean eficientes y en evitar consultas lentas. Para el tráfico de escritura, hemos migrado cargas de trabajo que se pueden fragmentar y que requieren mucha escritura a sistemas fragmentados como Azure CosmosDB. Las cargas de trabajo que son más difíciles de fragmentar, pero que aún así generan un alto volumen de escrituras, tardan más en migrar, y ese proceso todavía está en curso. También optimizamos agresivamente 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, imponemos límites de tasa estrictos para evitar una presión de escritura excesiva.

Optimización de consultas

Desafío: 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 a ChatGPT como a las solicitudes de API.

Solución: Unas pocas consultas costosas, como las que unen muchas tablas, pueden degradar significativamente o incluso colapsar todo el servicio. Necesitamos optimizar continuamente las consultas de PostgreSQL para asegurarnos de que sean eficientes y evitar patrones negativos comunes en el procesamiento de transacciones en línea (OLTP). Por ejemplo, en una ocasión identificamos una consulta extremadamente costosa que unía 12 tablas, donde los picos en esta consulta fueron responsables de SEVs de alta gravedad en el pasado. Deberíamos evitar las uniones complejas de varias tablas siempre que sea posible. Si las uniones son necesarias, aprendimos a considerar dividir la consulta y mover la lógica compleja de uniones a la capa de aplicación. Muchas de estas consultas problemáticas son generadas por frameworks de Object-Relational Mapping (ORMs), por lo que es importante revisar cuidadosamente el SQL que producen y asegurarse de 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 de un único punto de falla

Desafío: Si una réplica de lectura falla, el tráfico aún puede redirigirse a otras réplicas. Sin embargo, depender de un solo redactor significa tener un único punto de falla: si falla, todo el servicio se ve afectado.

Solución: La mayoría de las solicitudes críticas solo requieren consultas de lectura. Para mitigar el punto único de falla en el primario, transferimos esas lecturas del escritor a las réplicas, asegurando que esas solicitudes puedan seguir atendiéndose incluso si el primario falla. Aunque las operaciones de escritura seguirían fallando, el impacto se reduce; ya no es un SEV0, ya que las operaciones de lectura siguen disponibles.

Para mitigar fallas primarias, ejecutamos el primario en modo de alta disponibilidad (HA) con un 'standby' caliente, una réplica continuamente sincronizada que siempre está lista para asumir el tráfico. Si el servidor principal se cae o necesita desconectarse para mantenimiento, podemos promover rápidamente el servidor de respaldo para minimizar el tiempo de inactividad. El equipo de Azure PostgreSQL ha hecho un trabajo significativo para asegurar que estas conmutaciones por error se mantengan seguras y confiables incluso bajo una carga muy alta. Para manejar fallas de réplicas de lectura, desplegamos múltiples réplicas en cada región con suficiente margen de capacidad, asegurando que la falla de una sola réplica no cause una interrupción regional.

Aislamiento de carga de trabajo

Desafío: A menudo nos encontramos con situaciones en las que ciertas solicitudes consumen una cantidad desproporcionada de recursos en instancias de PostgreSQL. Esto puede llevar a un rendimiento degradado para otras cargas de trabajo que se ejecutan en las mismas instancias. Por ejemplo, el lanzamiento de una nueva función puede introducir consultas ineficientes que consumen mucho la CPU de PostgreSQL, ralentizando las solicitudes de otras funciones 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 múltiples implementaciones 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 nueva ventana), 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 nueva ventana). 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 nueva ventana) 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

Agradecimientos especiales 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 queremos agradecer al equipo de Azure PostgreSQL por su sólida colaboración.