Cómo recuperar estadísticas e índices corruptos en PostgreSQL que dejaron consultas lentas

Por · Publicado el · 7 min de lectura

Grande salão de estação ferroviária movimentado com pessoas e iluminação natural
Metáfora visual para consultas lentas, congestionamento e necessidade de otimização Foto: jplenio / Pixabay

¿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 caso

Ver también