PostgreSQL, ese sistema de base de datos que a veces suena a pieza de museo, está en el corazón de ChatGPT y la API de OpenAI. ¿Sorprendido? A mí también me llamó la atención. OpenAI logró soportar cientos de millones de usuarios con una sola primaria y casi 50 réplicas de lectura, pero no fue por casualidad: hubo optimizaciones, ingeniería rigurosa y decisiones prácticas difíciles.
Qué pasó y por qué importa
En un año la carga en PostgreSQL creció más de 10x. OpenAI necesitó sostener millones de consultas por segundo para 800 millones de usuarios. La estrategia principal: mantener una arquitectura de un solo nodo primario para escrituras y offload de lecturas a réplicas. ¿Por qué no dividir todo desde el inicio? Porque shardar PostgreSQL de aplicaciones existentes implica cambiar cientos de endpoints y puede tomar meses o años.
Pero trabajar con una sola primaria trae riesgos: ráfagas de escrituras, consultas pesadas y fallas en la cache pueden saturar la primaria y desencadenar ciclos de reintentos que empeoran todo. La historia muestra que con ingeniería y prudencia, PostgreSQL puede escalar mucho más de lo que muchos pensaban, especialmente para cargas dominadas por lecturas.
Desafíos principales y las soluciones que aplicaron
-
Problema: escrituras intensas y
MVCCcausando write amplification y bloat.- Solución: migraron cargas de trabajo shardables con mucha escritura a sistemas shardeados como Azure CosmosDB. Para nuevas cargas, la regla por defecto es usar sistemas shardeados. Además arreglaron bugs en la aplicación que generaban escrituras redundantes y aplicaron "lazy writes" y límites de tasa en backfills.
-
Problema: consultas caras que saturaban CPU, por ejemplo joins de muchas tablas.
- Solución: optimización constante de SQL, evitar joins multi-tabla cuando sea posible y mover lógica compleja al lado de la aplicación. Revisar lo que generan los ORMs es clave. También aplicaron timeouts como
idle_in_transaction_session_timeoutpara evitar queries inactivos que bloquean autovacuum.
- Solución: optimización constante de SQL, evitar joins multi-tabla cuando sea posible y mover lógica compleja al lado de la aplicación. Revisar lo que generan los ORMs es clave. También aplicaron timeouts como
-
Problema: punto único de falla en la primaria.
- Solución: separar la mayor parte de lecturas a réplicas; la primaria corre en modo HA con un hot standby listo para promoción rápida. De este modo, si la primaria cae, las lecturas críticas siguen sirviéndose y el impacto se reduce.
-
Problema: "noisy neighbors" que consumen recursos compartidos.
- Solución: aislar cargas en instancias dedicadas y segmentar en prioridades (alta y baja). Así, una característica que genera carga no degrada otras funcionalidades.
-
Problema: tormentas de conexiones (límite de 5 000 en Azure PostgreSQL).
- Solución: desplegaron
PgBouncerpara pooling de conexiones. Resultado práctico: el tiempo de conexión promedio pasó de 50 ms a 5 ms en sus benchmarks. Además, co-locaron proxies, clientes y réplicas por región para reducir latencia y uso de conexiones inter-región.
- Solución: desplegaron
-
Problema: oleadas de cache misses que empujan muchísimas lecturas a la base.
- Solución: locking y leasing en la capa de cache. Solo un lector se encarga de repoblar la cache cuando hay miss, mientras los demás esperan. Esto evita que miles de requests golpeen PostgreSQL al mismo tiempo.
-
Problema: replicación WAL hacia muchas réplicas genera presión en la primaria.
- Solución: probar cascaded replication donde réplicas intermedias retransmiten WAL a otras réplicas. Esto permite escalar réplicas sin sobrecargar la primaria, aunque añade complejidad operativa y retos de failover.
-
Problema: picos repentinos que agotan CPU, I/O o conexiones.
- Solución: limitación de tasa en múltiples capas (aplicación, pooler, proxy, query). También bloquear digest de consultas problemáticas desde el ORM cuando hace falta para aliviar rápido.
-
Problema: cambios de esquema costosos que reescriben tablas completas.
- Solución: permitir solo cambios ligeros, aplicar un timeout estricto de 5 segundos para operaciones de esquema, y mover tablas nuevas a sistemas shardeados. Backfills se hacen con límites de tasa y pueden durar semanas, pero evitan impactos en producción.
En resumen: mitigan cada amenaza con controles en varias capas. No hay bala de plata: hay muchas balas pequeñas bien apuntadas.
Resultados y números que importan
- Carga de PostgreSQL creció 10x en un año, pero con optimizaciones mantuvieron latencias bajas.
- Soportan millones de QPS para cargas dominadas por lecturas, con casi 50 réplicas distribuidas globalmente.
- Latencia p99 del cliente en dígitos bajos de milisegundos y disponibilidad de cinco nueves en producción.
- Solo un SEV-0 de PostgreSQL en los últimos 12 meses, durante el lanzamiento viral de ChatGPT ImageGen cuando la escritura subió más de 10x y 100 millones de usuarios se registraron en una semana.
- Con
PgBouncer, el tiempo promedio de conexión cayó de 50 ms a 5 ms en sus pruebas.
Lecciones prácticas que puedes aplicar si manejas sistemas críticos
-
Observabilidad y límites: instrumenta métricas de consultas y aplica límites en múltiples capas para cortar un pico antes de que inunde la base.
-
Revisa lo que generan los ORMs: muchas sorpresas vienen del SQL automático. A veces conviene escribir la consulta a mano o fragmentarla.
-
Cache con control: usar locking o leasing en cache evita cascadas de lecturas cuando la cache falla.
-
Aislar por prioridad: separar tráfico crítico del no crítico reduce riesgo al lanzar nuevas funciones.
-
Evita cambios de esquema agresivos en producción: planifica backfills y operaciones que se puedan aplicar en pequeños pasos.
Qué sigue para OpenAI y para la tecnología en general
OpenAI sigue migrando las cargas de escritura más fáciles de shardear a CosmosDB y está probando cascaded replication para no sobrecargar la primaria. Más adelante no descartan shardar PostgreSQL o explorar sistemas distribuidos alternativos si la demanda lo exige.
Si tienes una arquitectura que hoy depende mucho de una base relacional, la conclusión es clara: con diseño, disciplina y herramientas correctas puedes estirar mucho a PostgreSQL, pero también debes tener rutas de escape para escribir cuando el patrón de carga lo pida.
