Saltar para o conteúdo principal
OpenAI

22 de janeiro de 2026

Engenharia

PostgreSQL para alimentar 800 milhões de utilizadores do ChatGPT

Por Bohan Zhang, membro da equipa técnica

A carregar…

Durante anos, o PostgreSQL tem sido um dos sistemas de dados mais críticos e essenciais para o funcionamento de produtos fundamentais como o ChatGPT e a API da OpenAI. À medida que a nossa base de utilizadores cresce rapidamente, as exigências sobre as nossas bases de dados também têm aumentado exponencialmente. Ao longo do último ano, a nossa carga de PostgreSQL cresceu mais de 10 vezes e continua a aumentar rapidamente.

Os nossos esforços para melhorar a nossa infraestrutura de produção e sustentar este crescimento revelaram uma nova perceção: o PostgreSQL pode ser dimensionado para suportar de forma fiável cargas de trabalho com um grande volume de leitura muito maiores do que muitos imaginavam ser possível. O sistema (inicialmente criado por uma equipa de cientistas da Universidade da Califórnia, Berkeley) permitiu-nos suportar um tráfego global massivo com uma única instância primária do servidor Azure PostgreSQL Flexible(abre numa nova janela) e quase 50 réplicas de leitura distribuídas por várias regiões do mundo. Esta é a história de como escalámos o PostgreSQL na OpenAI para suportar milhões de consultas por segundo para 800 milhões de utilizadores através de otimizações rigorosas e engenharia sólida; abordaremos também as principais aprendizagens que tivemos ao longo do percurso.

Falhas no nosso projeto inicial

Após o lançamento do ChatGPT, o tráfego cresceu a um ritmo sem precedentes. Para o suportar, implementámos rapidamente otimizações extensivas tanto na aplicação como nas camadas da base de dados PostgreSQL, aumentámos a escala aumentando o tamanho das instâncias e expandimos adicionando mais réplicas de leitura. Esta arquitetura serviu-nos durante bastante tempo. Com melhorias contínuas, continua a oferecer uma margem suficiente para o crescimento futuro.

Pode parecer surpreendente que uma arquitetura com um único servidor principal consiga satisfazer as exigências à escala da OpenAI; no entanto, fazer isto funcionar na prática não é simples. Já assistimos a vários SEVs causados por sobrecarga do Postgres, e normalmente seguem o mesmo padrão: um problema a montante provoca um aumento súbito da carga na base de dados, como falhas generalizadas no cache devido a um problema na camada de cache, um pico de junções complexas que saturam a CPU, ou uma tempestade de escritas provocada pelo lançamento de uma nova funcionalidade. À medida que a utilização de recursos aumenta, a latência das consultas sobe e os pedidos começam a falhar devido ao tempo limite. As tentativas de repetição amplificam ainda mais a carga, desencadeando um ciclo vicioso com potencial para degradar todos os serviços do ChatGPT e da API.

Diagrama de escalamento de carga

Embora o PostgreSQL seja escalável para as nossas cargas de trabalho com grande volume de leitura, ainda encontramos desafios durante períodos de elevado tráfego de escrita. Isto deve-se, em grande medida, à implementação do controlo de concorrência multiversão (MVCC) do PostgreSQL, que o torna menos eficiente para cargas de trabalho com grande volume de escrita. Por exemplo, quando uma consulta atualiza um tuplo ou mesmo um único campo, a linha inteira é copiada para criar uma nova versão. Sob cargas de escrita elevadas, isto resulta numa amplificação significativa das operações de escrita. Também aumenta a amplificação de leitura, pois as consultas têm de percorrer várias versões de tuplos (tuplos mortos) para obter a mais recente. O MVCC introduz desafios adicionais, como o inchaço de tabelas e índices, o aumento da sobrecarga de manutenção de índices e o ajuste complexo do autovacuum. (Está disponível uma análise aprofundada destas questões num blogue que escrevi com o Prof. Andy Pavlo, da Universidade Carnegie Mellon, intitulado "A parte do PostgreSQL que mais odiamos"(abre numa nova janela), citado(abre numa nova janela) na página da Wikipédia sobre o PostgreSQL.)

Escalar o PostgreSQL para milhões de QPS

Para mitigar estas limitações e reduzir a pressão de escrita, migrámos, e continuamos a migrar, cargas de trabalho fragmentáveis (ou seja, que podem ser particionadas horizontalmente) e com elevada intensidade de escrita para sistemas fragmentados, como o Azure Cosmos DB, otimizando a lógica da aplicação para minimizar escritas desnecessárias. Também já não permitimos a adição de novas tabelas à implementação atual do PostgreSQL. As novas cargas de trabalho passam, por defeito, a ser executadas nos sistemas fragmentados.

Mesmo com a evolução da nossa infraestrutura, o PostgreSQL manteve-se sem partição, com uma única instância primária a servir todas as operações de escrita. A principal justificação é que o particionamento das cargas de trabalho das aplicações existentes seria extremamente complexo e demorado, exigindo alterações em centenas de endpoints de aplicações e podendo demorar meses ou até anos. Uma vez que as nossas cargas de trabalho são predominantemente de leitura e implementámos otimizações extensivas, a arquitetura atual ainda oferece uma ampla margem para suportar o crescimento contínuo do tráfego. Embora não descartemos a possibilidade de fragmentar o PostgreSQL no futuro, esta não é uma prioridade a curto prazo, dada a margem de manobra suficiente que temos para o crescimento atual e futuro.

Nas secções seguintes, vamos aprofundar os desafios que enfrentámos e as otimizações extensivas que implementámos para os resolver e prevenir futuras falhas, levando o PostgreSQL ao seu limite e escalando-o para milhões de consultas por segundo (QPS).

Reduzir a carga no servidor principal

Desafio: com apenas um escritor, uma configuração com um único servidor principal não consegue escalar as operações de escrita. Picos elevados de escrita podem rapidamente sobrecarregar o servidor principal e afetar serviços como o ChatGPT e a nossa API.

Solução: minimizamos ao máximo a carga no servidor principal — tanto nas leituras como nas escritas — para garantir que tem capacidade suficiente para lidar com picos de escrita. O tráfego de leitura é transferido para réplicas sempre que possível. No entanto, algumas consultas de leitura devem permanecer no servidor principal porque fazem parte de transações de escrita. Para estes casos, o nosso foco é garantir que são eficientes e evitar consultas lentas. Para o tráfego de escrita, migrámos cargas de trabalho fragmentáveis e com alta intensidade de escrita para sistemas fragmentados, como o Azure Cosmos DB. As cargas de trabalho que são mais difíceis de fragmentar, mas que ainda assim geram um elevado volume de escrita, demoram mais tempo a migrar, e esse processo ainda está em curso. Também otimizámos agressivamente as nossas aplicações para reduzir a carga de escrita; por exemplo, corrigimos erros nas aplicações que provocavam escritas redundantes e introduzimos escritas preguiçosas (lazy writes), quando apropriado, para suavizar os picos de tráfego. Além disso, ao preencher retroativamente campos das tabelas, aplicamos limites de taxa rigorosos para evitar uma pressão excessiva de escrita.

Otimização de consultas

Desafio: identificámos diversas consultas dispendiosas no PostgreSQL. No passado, picos repentinos de volume nestas consultas consumiam grandes quantidades de CPU, tornando os pedidos do ChatGPT e da API mais lentos.

Solução: algumas consultas dispendiosas, como as que unem muitas tabelas, podem degradar significativamente ou mesmo deitar abaixo todo o serviço. Precisamos de otimizar continuamente as consultas PostgreSQL para garantir que são eficientes e evitar anti-padrões comuns de Processamento de Transações Online (OLTP). Por exemplo, uma vez identificámos uma consulta extremamente onerosa que unia 12 tabelas, onde picos nesta consulta foram responsáveis por alertas de vulnerabilidade grave (SEVs) anteriores. Devemos evitar junções complexas de múltiplas tabelas sempre que possível. Se forem necessárias junções, aprendemos a considerar a possibilidade de dividir a consulta e mover a lógica complexa de junção para a camada de aplicação. Muitas destas consultas problemáticas são geradas por frameworks de Mapeamento Objeto-Relacional (ORMs), por isso é importante rever cuidadosamente o SQL que produzem e garantir que se comporta como esperado. Também é comum encontrar consultas inativas de longa duração no PostgreSQL. Configurar os tempos limite como idle_in_transaction_session_timeout é essencial para evitar que bloqueiem o autovacuum.

Mitigação de ponto único de falha

Desafio: se uma réplica de leitura falhar, o tráfego poderá ainda ser encaminhado para outras réplicas. No entanto, depender de um único escritor significa ter um único ponto de falha — se falhar, todo o serviço é afetado.

Solução: a maioria dos pedidos mais críticos envolve apenas consultas de leitura. Para mitigar o ponto único de falha no servidor principal, transferimos essas leituras do escritor para as réplicas, garantindo que esses pedidos continuem a ser servidos mesmo que o servidor principal falhe. Embora as operações de escrita continuem a falhar, o impacto é reduzido; já não é um erro SEV0, uma vez que as operações de leitura continuam disponíveis.

Para mitigar falhas no servidor principal, operamos o servidor principal em modo de Alta Disponibilidade (HA) com uma réplica “hot standby”, que está continuamente sincronizada e sempre pronta para assumir o serviço do tráfego. Se o servidor principal falhar ou precisar de ser retirado de serviço para manutenção, podemos rapidamente promover a réplica standby para minimizar o tempo de indisponibilidade. A equipa do Azure PostgreSQL fez um trabalho significativo para garantir que estas trocas de servidor (failovers) permanecem seguras e fiáveis, mesmo sob uma carga muito elevada. Para lidar com falhas nas réplicas de leitura, implantamos múltiplas réplicas em cada região com capacidade suficiente de reserva, garantindo que a falha de uma única réplica não cause uma interrupção regional.

Isolamento da carga de trabalho

Desafio: frequentemente deparamos-nos com situações em que determinados pedidos consomem uma quantidade desproporcional de recursos nas instâncias PostgreSQL. Isto pode resultar num desempenho degradado para outras cargas de trabalho a correr nas mesmas instâncias. Por exemplo, o lançamento de uma nova funcionalidade pode introduzir consultas ineficientes que consomem intensamente a CPU do PostgreSQL, atrasando os pedidos de outras 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 do proxy postgreSQL

Cada réplica de leitura tem a sua própria implementação no Kubernetes a executar vários pods PgBouncer. Executamos várias implementações Kubernetes por trás do mesmo serviço Kubernetes, que distribui o tráfego pelos 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(abre numa nova janela), 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 replicação em cascata do 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(abre numa nova janela). 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(abre numa nova janela) 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

Agradecimentos

Um agradecimento especial a Jon Lee, Sicheng Liu, Chaomin Yu e Chenglong Hao, que contribuíram para esta publicação, e a toda a equipa que ajudou a escalar o PostgreSQL. Gostaríamos também de agradecer à equipa do Azure PostgreSQL pela sua sólida parceria.