Cómo una columna sin índice mató nuestra base de datos
Un solo índice faltante convirtió una API de 8 ms en una pesadilla de 8 segundos.
Realizamos una prueba de carga antes de un lanzamiento importante. El desarrollo local manejaba 100 filas sin problemas. En staging simulamos 500 usuarios contra 1,5 millones de filas.
Todo falló.
- Los tiempos de respuesta de la API saltaron de 45 ms a 8.000 ms.
- El uso de CPU de la base de datos alcanzó el 100 %.
- Los pools de conexiones se agotaron.
- Las solicitudes agotaron el tiempo de espera.
El culpable fue una consulta simple que obtenía el historial de pedidos por user_id y status.
Al ejecutar EXPLAIN ANALYZE en PostgreSQL, se mostró un Sequential Scan. Al no haber un índice en user_id, el motor leyó los 1,5 millones de filas en cada solicitud.
Con 100 solicitudes concurrentes, la base de datos escaneó 150 millones de filas a la vez.
La solución tomó cinco minutos. Creamos un índice compuesto en (user_id, status, created_at DESC) usando CREATE INDEX CONCURRENTLY para que la tabla permaneciera en línea.
Resultados:
- El tiempo de la consulta bajó de 8.150 ms a 0,14 ms.
- La latencia de la API cayó de 8 segundos a 12 ms.
- El uso de CPU bajó del 100 % a menos del 8 %.
Lecciones aprendidas:
- Las pruebas locales engañan; 100 filas no son un sustituto de un millón.
- Indexa las claves foráneas; la mayoría de los ORM omiten esto.
- Ejecuta
EXPLAIN ANALYZE; la base de datos señala el problema. - Optimiza las consultas antes de comprar más hardware.
No escales los servidores primero. Escala las consultas.
