PostgreSQL
Vacuum, autovacuum y bloat
Vacuum y autovacuum para retirar versiones muertas, evitar wraparound y controlar bloat, con métricas y configuración según carga.
- Última actualización
- Actualizada
- Nivel
- Profundización
PostgreSQL
Vacuum y autovacuum para retirar versiones muertas, evitar wraparound y controlar bloat, con métricas y configuración según carga.
UPDATE y DELETE dejan versiones que snapshots antiguos todavía podrían necesitar. Cuando ya no son visibles, vacuum puede limpiar referencias y hacer que el espacio se reutilice.
UPDATE / DELETE
→ versiones obsoletas
→ esperar a que ningún snapshot las necesite
→ VACUUM limpia y marca espacio reusableSin vacuum, tablas e índices crecen, index-only scans pierden eficacia y los transaction IDs se acercan a wraparound.
PostgreSQL no sobrescribe todas las filas in-place. Esta decisión mejora concurrencia, pero crea mantenimiento obligatorio.
Vacuum resuelve cuatro necesidades:
ANALYZE es una responsabilidad relacionada pero distinta: recopila estadísticas del planner.
VACUUM app.orders;Conceptualmente:
Vacuum normal permite lecturas y escrituras concurrentes, aunque consume I/O/CPU y toma locks leves.
VACUUM (ANALYZE) app.orders;Combina limpieza y estadísticas. No significa que siempre deban ejecutarse juntas; autovacuum decide triggers separados para vacuum y analyze.
Si una tabla de 100 GB elimina 50 GB distribuidos, vacuum marca ese espacio para nuevos inserts/updates dentro de la relación.
El sistema operativo sigue viendo un archivo similar:
archivo 100 GB
→ 50 GB de páginas/espacio potencialmente reutilizableEsto suele ser correcto: reutilizar evita crecer de nuevo.
Para devolver espacio al OS se necesita truncar páginas libres al final cuando es posible, reescribir o herramientas especializadas.
VACUUM FULL app.orders;Reescribe la tabla compacta y toma ACCESS EXCLUSIVE durante la operación.
Costes:
Úsalo tras eventos excepcionales donde devolver espacio justifica downtime; no como tarea diaria.
Autovacuum launcher inicia workers que procesan tablas según actividad.
Trigger conceptual:
threshold + scale_factor × reltuplesVacuum:
autovacuum_vacuum_threshold
+ autovacuum_vacuum_scale_factor × rowsAnalyze usa sus propios parámetros.
Tabla con 500 millones y scale factor 0.2:
100 millones de cambios antes del trigger aproximadoPuede ser demasiado tarde.
Configuración por tabla:
ALTER TABLE app.orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_threshold = 50000,
autovacuum_analyze_scale_factor = 0.005,
autovacuum_analyze_threshold = 10000
);Los valores dependen de tasa de cambio, duración de vacuum y hardware.
Versiones modernas también consideran inserts para vacuum, importante en tablas append-only para freeze y visibility map.
No asumas que una tabla sin updates nunca necesita vacuum.
Parámetros globales controlan:
Demasiado conservador:
Demasiado agresivo:
El objetivo es mantenimiento continuo y predecible, no esperar una crisis.
Una tuple no puede eliminarse si algún snapshot todavía podría verla.
Bloqueadores:
idle in transaction.Ajustar autovacuum no supera un horizonte retenido. Primero elimina la causa.
“Bloat” suele describir espacio físico mucho mayor que los datos útiles por:
No todo espacio libre es malo. Si la tabla vuelve a usarlo, una reescritura solo generará trabajo y después crecerá otra vez.
Fuentes aproximadas:
pg_stat_user_tables.n_dead_tup.pg_total_relation_size.pgstattuple.n_dead_tup es estimación y cero no demuestra ausencia de bloat histórico.
HOT updates reducen índice bloat cuando no cambian columnas indexadas y hay espacio en page.
Fillfactor menor deja espacio:
ALTER TABLE app.products SET (fillfactor = 80);Trade-off:
Mide n_tup_hot_upd frente a n_tup_upd.
Vacuum marca páginas all-visible/all-frozen.
Beneficios:
Una tabla con writes constantes puede tener pocas pages all-visible aunque vacuum funcione correctamente.
Vacuum limpia índices, pero la estrategia puede adaptarse según volumen de dead tuples y parámetros/versiones.
En tablas con muchos índices, index vacuuming puede dominar tiempo. Eliminar índices innecesarios reduce mantenimiento.
Vacuum normal no toma Access Exclusive, pero puede esperar/conflictuar con ciertos DDL y ser cancelado por locks más fuertes.
Si migraciones frecuentes lo interrumpen, el mantenimiento puede quedarse atrás.
Transaction IDs se comparan en espacio circular. PostgreSQL debe congelar tuples antiguas.
Métrica:
SELECT datname, age(datfrozenxid)
FROM pg_database
ORDER BY age(datfrozenxid) DESC;Si se acerca al límite, PostgreSQL lanza anti-wraparound vacuums con prioridad. Ignorarlo puede causar protección que rechaza nuevas transacciones.
Row locks y shared lockers usan MultiXact IDs, que también requieren mantenimiento/freeze.
Monitorea edades y mensajes de autovacuum; no solo XID normal.
Vacuum marca tuples suficientemente antiguas como congeladas.
Parámetros como vacuum_freeze_min_age, vacuum_freeze_table_age y límites autovacuum controlan cuándo.
Reducirlos demasiado genera trabajo repetido; aumentarlos demasiado reduce margen de seguridad.
Diagnóstico:
SELECT
pid,
usename,
state,
xact_start,
backend_xmin,
now() - xact_start AS age,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;Una transacción read-only vieja puede bloquear cleanup.
Configura idle_in_transaction_session_timeout para proteger la aplicación, con excepciones controladas para tareas legítimas.
Un slot inactivo puede retener WAL hasta llenar disco. Esto no es exactamente table bloat, pero forma parte del mismo problema de horizontes retenidos.
Consulta:
SELECT
slot_name,
slot_type,
active,
restart_lsn,
confirmed_flush_lsn,
wal_status
FROM pg_replication_slots;La eliminación de un slot pierde la capacidad del consumidor de continuar; requiere decisión operacional.
hot_standby_feedback puede reducir cancelaciones de queries en réplica, pero retener tuples en primary y aumentar bloat.
Es un trade-off entre consultas largas en standby y mantenimiento del primary.
SELECT
relname,
n_live_tup,
n_dead_tup,
last_vacuum,
last_autovacuum,
vacuum_count,
autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;Progreso:
SELECT * FROM pg_stat_progress_vacuum;Logs con log_autovacuum_min_duration ayudan a observar duración, tuples, páginas y cancelaciones.
Señales:
Opciones:
Eliminar 200 millones genera dead tuples y WAL.
Alternativas según dominio:
Después, vacuum necesita recuperar espacio. Un DELETE gigante en una transacción retiene todas las versiones hasta commit y puede saturar réplicas.
Cada partition se mantiene separadamente. Ventajas:
Costes:
Índices pueden necesitar reconstrucción por:
REINDEX INDEX CONCURRENTLY app.orders_idx;CONCURRENTLY reduce bloqueo, necesita espacio y tiene restricciones. No sustituye solucionar churn/autovacuum.
Pueden reescribir tablas con menor downtime usando triggers/tablas auxiliares, pero:
No son magia; planifica rollback.
Workload:
Problema: 50 millones de expiradas y archivo grande.
Diseño:
expires_at.Puede justificar reescritura para devolver espacio.
Espacio libre se reutiliza; compactarla puede ser inútil.
Riesgo de bloat y wraparound; anti-wraparound puede intervenir aun así.
Autovacuum no gestiona temp de otras sesiones; analiza/vacuum según necesidad de la sesión.
También necesitan vacuum para MVCC/freeze durante operación.
Puede retrasar cleanup; monitorea.
Convierte mantenimiento continuo en crisis.
Causa locks y reescritura sin solucionar raíz.
Puede saturar I/O.
Una transacción/slot puede impedir progreso.
Es estimación.
Puede ser útil para churn futuro.
Almacenamiento interno de PostgreSQL conecta vacuum con pages, heap, TOAST, buffers y checkpoints.