Saltar a contenido

Optimización de Rendimiento

Casi toda "lentitud de base de datos" que nos llega no es lentitud de la base: es una consulta sin índice, un ORM emitiendo trescientas consultas por petición, o un conjunto de datos que dejó de caber en memoria. Cambiar el servidor por uno más grande enmascara el problema unos meses y devuelve la misma cuenta después.

El método es siempre el mismo: medir, encontrar las pocas consultas que explican la mayor parte del tiempo, corregir la causa, medir de nuevo.

Capas donde se acumula la latencia: aplicación, pool de conexiones, planificador, caché e I/O de disco

El orden importa: corregir una capa abarata la de abajo. El camino inverso no funciona.


1. Medir antes de tocar

Sin números, optimizar es adivinar.

Fuente Qué revela
pg_stat_statements (PostgreSQL) Consultas por tiempo total acumulado — el ranking que importa
performance_schema / slow query log (MySQL) Consultas lentas y las ejecutadas muchas veces
Métricas de sistema CPU, latencia de I/O, memoria, conexiones activas
Métricas de replicación Lag, que delata escritura pesada o réplica subdimensionada
Trazas en la aplicación Dónde gasta tiempo la petición: base, red o el propio código

El tiempo total gana al tiempo individual

Una consulta de 2 segundos ejecutada 10 veces al día importa menos que una de 20 ms ejecutada 200 mil veces. Ordene por tiempo total acumulado, no por la consulta más lenta.


2. La aplicación: el problema más barato de corregir

  • N+1 — una consulta para listar, más una por ítem. Es el patrón más común y el de mayor ganancia: se resuelve con JOIN o carga por lotes.
  • SELECT * — trae columnas que nadie usa, impide el índice de cobertura e infla la red.
  • Paginación con OFFSET altoOFFSET 100000 hace que la base recorra y descarte 100 mil filas. La paginación por clave (keyset) no tiene ese costo.
  • Ausencia de lotes — mil INSERT individuales cuestan mucho más que un INSERT con mil filas.
  • Transacción larga — retiene recursos, retrasa la limpieza de versiones antiguas y aumenta el lag de las réplicas.

3. Plan de ejecución

Leer el plan es lo que separa una corrección de un intento.

-- PostgreSQL: con tiempo real y páginas leídas, no solo la estimación
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ;

-- MySQL
EXPLAIN ANALYZE SELECT ... ;

Qué buscar:

  • Recorrido secuencial en tabla grande — falta un índice, o el índice existe y no puede usarse (función sobre la columna, tipo incompatible, LIKE '%algo').
  • Estimación muy lejos de lo real — estadísticas desactualizadas. Ejecute ANALYZE antes de concluir cualquier otra cosa.
  • Ordenación en disco — memoria de trabajo insuficiente para el ORDER BY/GROUP BY.
  • Método de junción equivocado — casi siempre consecuencia de una mala estimación, no del planificador.

4. Índices

Un buen índice resuelve; demasiados índices cuestan en cada escritura.

  • Índice compuesto en el orden correcto — columnas de igualdad primero, rango al final. Un índice en (cliente_id, creado_en) sirve a la consulta por cliente y por cliente + período; al revés no.
  • Índice de cobertura — cuando el índice contiene todas las columnas de la consulta, la base ni siquiera toca la tabla.
  • Índice parcial — en PostgreSQL, indexar solo las filas que importan (WHERE status = 'activo') reduce tamaño y costo de mantenimiento.
  • Los índices no usados son pérdida — ocupan disco, encarecen cada INSERT e inflan la copia de seguridad. Las estadísticas de uso muestran cuáles nunca se leyeron.
  • Índice duplicado(a) es redundante cuando existe (a, b).

5. Memoria y caché

Motor Parámetro central Regla de partida
MySQL / InnoDB innodb_buffer_pool_size ~60–70% de la RAM en servidor dedicado
PostgreSQL shared_buffers + caché del SO ~25% en shared_buffers, contando con la caché del sistema
PostgreSQL work_mem Por operación de ordenación — cuidado: se multiplica por conexión
ClickHouse Límite de memoria por consulta Evita que una agregación tumbe el nodo

La señal a seguir es la tasa de acierto de caché. Cuando cae de forma sostenida, el conjunto activo creció más allá de la RAM — y el siguiente paso es memoria o particionamiento, no otro índice.


6. Conexiones

A las bases de datos no les gustan miles de conexiones. Cada una consume memoria y disputa CPU.

  • Pool obligatorioPgBouncer en PostgreSQL, ProxySQL en MySQL, o el pool del propio framework, bien configurado.
  • Menos conexiones, más throughput — pasado cierto punto, agrandar el pool empeora el rendimiento: la cola se traslada del pool al interior de la base, donde es más cara.
  • Separar lectura de escritura — dirigir la lectura a las réplicas alivia al primario y es el camino más directo para escalar lectura.

7. Almacenamiento e I/O

Lo que queda después de todo es físico:

  • latencia de escritura del volumen, especialmente para el log de transacciones;
  • IOPS disponibles frente a las exigidas en el pico;
  • separación entre volumen de datos y volumen de log;
  • compresión, que cambia CPU por I/O — casi siempre un buen negocio en carga analítica.

Ganancias típicas

Corrección Ganancia observada
Índice ausente en consulta de alta frecuencia 10× a 1000× en la consulta
Eliminación de N+1 5× a 50× en la latencia de la petición
buffer pool dimensionado para el conjunto activo 2× a 20× en carga de lectura
Pool de conexiones corregido Fin de los picos de error y latencia más estable
Lectura dirigida a réplica Alivio proporcional al volumen de lectura

Un servidor más grande es la última alternativa, no la primera

Aumentar el hardware antes de mirar el plan de ejecución resuelve por un tiempo y cobra después — con una cuenta mayor y el mismo problema.


Páginas Relacionadas