Cómo recuperar estadísticas e índices corruptos en PostgreSQL que dejaron consultas lentas
Por Kevin Marcondes · Publicado el · 7 min de lectura
¿Las consultas se volvieron lentas y sospechas de estadísticas malas o índices corruptos? Aquí tienes un camino práctico: confirmar el diagnóstico, regenerar estadísticas con ANALYZE, reconstruir índices con bajo impacto y evitar que vuelva a ocurrir. Muestro señales típicas, comandos seguros y cuándo usar REINDEX CONCURRENTLY o recrear índices.
Señales de que el cuello de botella es estadística o índice (no la aplicación)
Antes de cambiar código, verifica si el optimizador perdió el rumbo. Estadísticas imprecisas e índices dañados hacen que el plan se salga de pista, eligiendo Seq Scan costosos o filtros ineficaces.
Busca estos síntomas en producción:
- El plan cambió de Index Scan a Seq Scan sin una alteración proporcional de los datos.
- Las estimaciones de filas (rows) en EXPLAIN difieren mucho del real.
- El índice casi no se usa (idx_scan baja) después de una operación de mantenimiento o un incidente.
- La lentitud comenzó justo después de un pico de updates/deletes o un crash del servidor.
- Tablas con muchos dead tuples en pg_stat_user_tables.
Causas comunes: por qué las estadísticas y los índices se degradan
Entender el origen ayuda a evitar repetir el problema. Estas son causas frecuentes y plausibles en PostgreSQL:
- Autovacuum/autoanalyze desactivado o mal calibrado en tablas grandes.
- Carga con muchas actualizaciones, dejando índices hinchados (bloat).
- Creación de índice interrumpida; el índice queda no disponible o inválido.
- Crash, fallos de disco o cache de escritura no fiable durante mantenimiento.
- Upgrade parcial u operaciones DDL sin ventana y sin rollback claro.
Diagnóstico rápido y seguro en producción
Confirma la hipótesis antes de alterar. Usa EXPLAIN para ver el plan, revisa dead tuples, índices inválidos y valida la integridad de B-Trees con amcheck.
EXPLAIN ANALYZE ejecuta la consulta. En rutas críticas, ejecútalo primero sin ANALYZE. Evita pruebas pesadas en horario pico.
-- 1) Inspecione o plano da consulta problemática
EXPLAIN (ANALYZE, BUFFERS)
SELECT /* sua query aqui */ 1;
-- 2) Tabelas com muitos dead tuples (candidatas a VACUUM/ANALYZE)
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
-- 3) Índices inválidos ou não prontos
SELECT c.relname AS index_name, i.indisvalid, i.indisready
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE NOT i.indisvalid OR NOT i.indisready;
-- 4) Verifique a integridade de B-Trees (exige superuser)
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check(indexrelid)
FROM pg_index
WHERE indisvalid;
Recuperar estadísticas: ANALYZE y pequeños ajustes
Cuando el plan falla por mala estimación, regenera estadísticas. ANALYZE y VACUUM (ANALYZE) se ejecutan online y no bloquean lecturas/escrituras normales. En tablas enormes, prioriza las más usadas.
Si columnas tienen distribución irregular, aumenta la muestra por columna. Ajustes finos reducen regresiones futuras.
-- Atualize estatísticas nas tabelas críticas
ANALYZE VERBOSE schema.tabela_mais_lenta;
-- Opcional, se há muitos dead tuples
VACUUM (ANALYZE, VERBOSE) schema.tabela_mais_lenta;
-- Aumente a qualidade das estatísticas de uma coluna skewed
ALTER TABLE schema.tabela_mais_lenta
ALTER COLUMN col_filtro SET STATISTICS 200; -- 1–10000
-- Tornar a amostragem padrão mais rica (recarrega config)
ALTER SYSTEM SET default_statistics_target = 200;
SELECT pg_reload_conf();
Recuperar índices: REINDEX con bajo impacto y alternativas
Si amcheck detecta problemas o el índice está inválido, reconstrúyelo. Prefiere minimizar bloqueos en tablas calientes.
REINDEX CONCURRENTLY reduce impacto, pero no cubre todos los casos. Cuando no esté disponible, crea un índice nuevo en paralelo y luego elimina el antiguo. REINDEX a nivel de base puede bloquear; úsalo con criterio.
- Índice levemente hinchado: REINDEX CONCURRENTLY en el índice o en la tabla.
- Índice inválido/corrupto: recrea con CREATE INDEX CONCURRENTLY y luego DROP.
- Catálogos del sistema afectados: REINDEX SYSTEM, planificando ventana y pruebas.
| Acción | Impacto de bloqueo | Cuándo usar |
|---|---|---|
| ANALYZE | Mínimo | Estimaciones incorrectas, plan cambiado sin modificación de datos |
| VACUUM (ANALYZE) | Bajo | Muchos dead tuples y uso intensivo de escritura |
| REINDEX CONCURRENTLY | Bajo a moderado | Bloat/error en B-Tree de tablas críticas |
| CREATE INDEX CONCURRENTLY + DROP | Bajo a moderado | Reemplazar índice problemático sin bloquear writes |
| REINDEX {TABLE|SCHEMA|DATABASE|SYSTEM} | Alto | Múltiples índices afectados; requiere ventana de mantenimiento |
-- Reconstrução com menor bloqueio (PostgreSQL >= 12)
REINDEX INDEX CONCURRENTLY schema.idx_cliente_cpf;
-- Ou na tabela inteira
REINDEX TABLE CONCURRENTLY schema.clientes;
-- Alternativa compatível: recriar e trocar
CREATE INDEX CONCURRENTLY idx_cliente_cpf_v2 ON schema.clientes (cpf);
-- Depois que o v2 estiver pronto e usado:
DROP INDEX CONCURRENTLY schema.idx_cliente_cpf;
Verificación post-corrección y rollback seguro
Después de ANALYZE/REINDEX, valida que el plan volvió al esperado y que el índice se usa. Compara tiempos y estimativas antes/después.
Mantén un camino de vuelta: nombres de índice versionados facilitan rollback. Evita borrar el índice antiguo antes de confirmar la mejora bajo carga real.
| Métrica | Dónde ver | Indicador de éxito |
|---|---|---|
| Plan (EXPLAIN) | EXPLAIN (ANALYZE, BUFFERS) | Estimaciones cercanas a la realidad; menos Seq Scan |
| Uso del índice | pg_stat_all_indexes.idx_scan | Incremento consistente en el índice nuevo |
| Dead tuples | pg_stat_user_tables.n_dead_tup | Disminución tras VACUUM (ANALYZE) |
| Tiempo de la query | Métrica de APM o logs | Reducción estable, sin picos anómalos |
-- Reavalie o plano e o tempo de execução
EXPLAIN (ANALYZE, BUFFERS)
SELECT /* sua query aqui */ 1;
-- Índices mais usados pós-ajuste
SELECT relname AS index_name, idx_scan
FROM pg_stat_all_indexes
ORDER BY idx_scan DESC
LIMIT 10;
Prevención: autovacuum, rutinas y cambios controlados
Evita nueva degradación manteniendo estadísticas frescas e índices saludables. Automatiza verificaciones, ajusta umbrales y controla los DDL.
Programa rutinas fuera del pico. Monitorea tendencias para actuar antes de la crisis.
- Calibra autovacuum para tablas grandes (scale_factor y thresholds menores).
- Programa ANALYZE dirigido tras cargas masivas.
- Automatiza amcheck periódico en índices críticos.
- Estandariza DDL con versionado de base para cambios seguros.
- Investiga jobs que causan bloqueos recurrentes antes de mantenimientos.
-- Exemplo: tornar o autovacuum mais agressivo em uma tabela quente
ALTER TABLE schema.clientes SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02,
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_threshold = 500
);
Preguntas frecuentes
¿Cómo saber si la lentitud es por estadísticas malas o por SQL ineficiente?
Compara el plan actual con el esperado vía EXPLAIN. Si las estimaciones divergen mucho del real y el plan cambió sin modificación de datos, regenera estadísticas. Si el plan sigue malo aun con buenas estimaciones, revisa la query y los índices existentes.
¿ANALYZE y VACUUM bloquean la aplicación?
ANALYZE y VACUUM (sin FULL) se ejecutan online, sin bloquear lecturas y escrituras normales. Hay locks leves y cortos para metadatos. En tablas muy activas, ejecútalos fuera del pico y monitoriza el impacto.
¿Cuándo usar REINDEX CONCURRENTLY?
Úsalo cuando haya bloat o sospecha de corrupción en índices B-Tree de tablas críticas y necesites reducir bloqueos. Si el índice está inválido o ausente, crea uno nuevo con CREATE INDEX CONCURRENTLY y elimina el antiguo después.
¿Debo reindexar toda la base?
Solo con justificación fuerte y ventana de mantenimiento. REINDEX en toda la base puede causar bloqueos amplios. Prefiere atacar índices o tablas específicas y escala a schema/database/system solo si es necesario.
¿Autovacuum activado no debería evitar esto?
Ayuda, pero la configuración por defecto puede ser conservadora para tablas grandes y workloads intensos. Ajusta scale factors y thresholds, ejecuta ANALYZE dirigido tras cargas masivas y monitoriza amcheck y uso de índices.
¿Puedo borrar pg_statistic para reconstruir desde cero?
No. pg_statistic es un catálogo del sistema. El camino correcto es ejecutar ANALYZE (o VACUUM ANALYZE) y, si hace falta, aumentar la muestra con default_statistics_target o SET STATISTICS por columna.
¿Necesitas recuperar rendimiento sin parar el sistema?
Si quieres un plan seguro para ANALYZE, REINDEX y ajustes de autovacuum con bajo impacto, hablemos sobre tu caso y prioricemos las acciones de mayor beneficio.
Hablar sobre mi casoVer también
- API REST con picos de latencia por queries N+1 y GC en PHP — cómo diagnosticar y corregir
- Error 'Too many connections' en PostgreSQL durante pico de facturación — guía práctica de diagnóstico y corrección
- Reducir costos migrando Magento a OpenMage con plan de rollback y pruebas de aceptación
- Guía práctica: cómo recuperar una tabla eliminada en Postgres con PITR sin downtime