TL;DR — Resumen Rápido
Domina el ajuste de VACUUM y autovacuum en PostgreSQL para eliminar el bloat, prevenir el desbordamiento de XID y maximizar el rendimiento de tu base de datos.
VACUUM en PostgreSQL es una de las operaciones de mantenimiento más incomprendidas y a la vez más críticas en cualquier despliegue productivo. Sin un ajuste adecuado, las tablas acumulan silenciosamente tuplas muertas que inflan el almacenamiento, degradan el rendimiento de las consultas y, en el peor de los casos, desencadenan un desbordamiento catastrófico del ID de transacción que deja la base de datos completamente fuera de servicio. Esta guía cubre todo lo necesario para configurar autovacuum correctamente y mantener las bases de datos en producción en estado óptimo.
Requisitos previos
- PostgreSQL 13 o superior (la mayoría de ejemplos aplican desde PostgreSQL 10+)
psqlo un cliente SQL con acceso de nivel DBA- Capacidad para editar
postgresql.confo usarALTER SYSTEM - Familiaridad básica con los conceptos de almacenamiento en PostgreSQL
Por qué existe VACUUM: MVCC y tuplas muertas
PostgreSQL utiliza Control de Concurrencia Multiversión (MVCC) para gestionar lecturas y escrituras concurrentes sin bloqueos. Cada UPDATE o DELETE no modifica los datos en su lugar — en cambio, marca la versión anterior de la fila como muerta y escribe una nueva versión. Esto hace que las lecturas no bloqueen, pero deja atrás tuplas muertas: versiones de filas que ya no son visibles para ninguna transacción pero que siguen ocupando espacio en disco.
Con el tiempo, las tuplas muertas causan tres problemas graves:
- Bloat de tabla: El archivo físico de la tabla crece aunque el conteo lógico de filas permanezca constante. Una tabla con 10 millones de filas activas podría ocupar el espacio de 50 millones en disco.
- Bloat de índice: Los índices B-tree acumulan punteros a tuplas muertas, incrementando su tamaño y el costo de escaneo.
- Desbordamiento de ID de transacción: PostgreSQL usa IDs de transacción de 32 bits. Después de aproximadamente 2,000 millones de transacciones, los IDs dan la vuelta. PostgreSQL fuerza una parada de emergencia en toda la base de datos si VACUUM no puede congelar transacciones antiguas a tiempo.
VACUUM resuelve los tres problemas escaneando páginas de tablas, marcando tuplas muertas como espacio reutilizable y avanzando el horizonte de congelación para transacciones antiguas.
Tipos de VACUUM comparados
| Comando | Bloqueo | Recuperación de espacio | Caso de uso |
|---|---|---|---|
VACUUM | Ninguno (comparte con lecturas/escrituras) | Marca espacio para reutilizar, no encoge el archivo | Mantenimiento rutinario, autovacuum |
VACUUM ANALYZE | Ninguno | Igual + actualiza estadísticas del planificador | Después de inserciones/borrados masivos |
VACUUM FULL | Exclusivo (tabla bloqueada) | Reescribe la tabla, devuelve espacio al SO | Recuperación puntual de bloat severo |
VACUUM FREEZE | Ninguno | Fuerza la congelación de todos los XID antiguos | Prevención de desbordamiento |
Usa VACUUM FULL solo como paso de recuperación puntual en tablas muy infladas — mantiene un bloqueo exclusivo que bloquea todas las lecturas y escrituras durante su ejecución. Para reescrituras de tablas en línea, utiliza pg_repack.
El daemon autovacuum: parámetros principales
Autovacuum se ejecuta como un daemon en segundo plano que lanza procesos worker para vacuumar tablas automáticamente. Los parámetros clave en postgresql.conf:
# Con qué frecuencia el launcher verifica si hay trabajo
autovacuum_naptime = 1min
# Máximo de workers concurrentes de autovacuum
autovacuum_max_workers = 3
# Activa vacuum cuando tuplas muertas > threshold + scale_factor * n_live_tup
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2 # 20% de la tabla
# Activa ANALYZE cuando las tuplas modificadas superan esto
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.1 # 10% de la tabla
# Congela filas con esta antigüedad de transacciones
autovacuum_freeze_max_age = 200000000
El punto crítico de ajuste es autovacuum_vacuum_scale_factor. El valor predeterminado de 0.2 significa que autovacuum se activa cuando el 20% de las filas de una tabla están muertas. Para una tabla de 100 filas, eso está bien. Para una tabla de 50 millones de filas, autovacuum no se activará hasta que se hayan acumulado 10 millones de tuplas muertas — un problema severo de bloat.
Valores globales recomendados para sistemas de alta escritura:
autovacuum_vacuum_scale_factor = 0.05
autovacuum_analyze_scale_factor = 0.02
autovacuum_max_workers = 5
autovacuum_naptime = 30s
Configuración de autovacuum por tabla
La técnica de ajuste más poderosa es configurar los parámetros de autovacuum por tabla, sobreescribiendo los valores globales solo donde sea necesario:
-- Para una tabla de muy alta escritura (p.ej., log de eventos/auditoría)
ALTER TABLE eventos SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_threshold = 500,
autovacuum_analyze_scale_factor = 0.005,
autovacuum_vacuum_cost_delay = 2
);
-- Para una tabla de consulta casi estática (vacuum menos agresivo)
ALTER TABLE codigos_pais SET (
autovacuum_vacuum_scale_factor = 0.5,
autovacuum_vacuum_threshold = 1000
);
Los ajustes por tabla se almacenan en pg_class.reloptions y sobreviven a los reinicios. Visualízalos con:
SELECT relname, reloptions
FROM pg_class
WHERE reloptions IS NOT NULL;
Monitoreo del bloat de tablas
Usando pg_stat_user_tables
-- Top 20 tablas por conteo de tuplas muertas
SELECT
schemaname,
relname AS nombre_tabla,
n_live_tup,
n_dead_tup,
round(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS pct_muertas,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
Detectar tablas cercanas al desbordamiento de XID
-- Tablas con los IDs de transacción sin congelar más antiguos
SELECT
schemaname,
relname,
age(relfrozenxid) AS antiguedad_xid,
pg_size_pretty(pg_total_relation_size(oid)) AS tamano_tabla
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = relnamespace
WHERE relkind = 'r'
ORDER BY age(relfrozenxid) DESC
LIMIT 20;
Cualquier tabla con antiguedad_xid superior a 1,500 millones necesita atención inmediata.
Monitorear el progreso del VACUUM activo
SELECT
p.pid,
p.relid::regclass AS nombre_tabla,
p.phase,
p.heap_blks_total,
p.heap_blks_scanned,
p.heap_blks_vacuumed,
p.num_dead_tuples,
a.query_start
FROM pg_stat_progress_vacuum p
JOIN pg_stat_activity a ON a.pid = p.pid;
Medir el bloat real con pgstattuple
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT
table_len,
tuple_count,
dead_tuple_count,
dead_tuple_len,
round(dead_tuple_percent, 1) AS pct_muertas,
free_space,
round(free_percent, 1) AS pct_libre
FROM pgstattuple('public.pedidos');
Un dead_tuple_percent superior al 10–15% en una tabla consultada frecuentemente es señal de investigar por qué el autovacuum no está al día.
Prevención del desbordamiento de XID
El desbordamiento es el modo de falla más peligroso de PostgreSQL. La secuencia de eventos:
- En
autovacuum_freeze_max_age(200M XID), autovacuum inicia congelación agresiva en la tabla. - En
vacuum_freeze_min_age(50M XID) desde el XID sin congelar más antiguo, VACUUM empieza a congelar filas. - A 1,000 millones de XID antes del desbordamiento, PostgreSQL empieza a registrar advertencias en el log.
- A 10 millones de XID restantes, PostgreSQL deja de aceptar nuevas transacciones y requiere
VACUUM FREEZEpara recuperarse.
Monitorea la antigüedad a nivel de base de datos:
SELECT
datname,
age(datfrozenxid) AS antiguedad_xid,
pg_size_pretty(pg_database_size(datname)) AS tamano_bd
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
Recuperación de emergencia si una base de datos está cerca del desbordamiento:
# Debe ejecutarse como superusuario; puede tardar horas en bases de datos grandes
vacuumdb --all --freeze --verbose --analyze
VACUUM FULL vs pg_repack
Cuando una tabla está severamente inflada, VACUUM FULL es el instrumento contundente:
-- Bloquea todas las lecturas y escrituras hasta completarse
VACUUM FULL VERBOSE pedidos;
Para sistemas de producción que no pueden tolerar tiempo de inactividad, usa pg_repack:
# Instalar
sudo apt install postgresql-16-repack
# Reempaquetar una tabla en línea (sin bloqueo exclusivo en la tabla activa)
pg_repack -h localhost -U postgres -d mibd -t pedidos
# Reempaquetar solo los índices de una tabla
pg_repack -h localhost -U postgres -d mibd --only-indexes -t pedidos
Problemas comunes y resolución
Autovacuum eliminado por transacciones de larga duración: Autovacuum no puede eliminar tuplas muertas que aún son visibles para alguna transacción abierta. Busca transacciones obsoletas:
SELECT pid, now() - xact_start AS duracion, query, state
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY duracion DESC
LIMIT 10;
“Autovacuum: preventing wraparound” en los logs: Significa que autovacuum cambió a modo de emergencia, ignorando el throttle de costo. No bloquees ni elimines estos procesos de vacuum.
VACUUM se ejecuta indefinidamente en tablas grandes: Divide el trabajo en operaciones más pequeñas ejecutando VACUUM en particiones individuales, o programa ventanas de mantenimiento para VACUUM FULL en tablas que no pueden particionarse.
Referencia de ajuste por tamaño de base de datos
| Tamaño de BD | autovacuum_max_workers | autovacuum_vacuum_scale_factor | autovacuum_vacuum_cost_limit |
|---|---|---|---|
| < 10 GB | 3 (predeterminado) | 0.05 | 200 (predeterminado) |
| 10–100 GB | 4–5 | 0.02 | 400 |
| 100 GB – 1 TB | 6–8 | 0.01 | 600 |
| > 1 TB | 8–10 + por tabla | 0.005 en tablas activas | 800 |
Comparativa entre sistemas de bases de datos
| Característica | PostgreSQL VACUUM | MySQL OPTIMIZE TABLE | SQL Server Index Rebuild | Oracle Segment Shrink |
|---|---|---|---|---|
| Bloqueos requeridos | Ninguno (regular), Exclusivo (FULL) | Exclusivo | En línea (Enterprise) | Exclusivo de fila |
| Herramienta de reescritura en línea | pg_repack | pt-online-schema-change | Rebuild en línea | SHRINK SPACE COMPACT |
| Automático | Daemon autovacuum | Manual o event scheduler | Mantenimiento automático de índices | No automático |
| Riesgo de desbordamiento | Sí (XID) | No | No | No |
| Visibilidad del bloat | pg_stat_user_tables, pgstattuple | information_schema | sys.dm_db_index_physical_stats | DBA_SEGMENTS |
Resumen
- VACUUM recupera el espacio de tuplas muertas de las actualizaciones y borrados MVCC; sin él, las tablas se inflan y el desbordamiento de XID eventualmente detiene la base de datos.
- Autovacuum es la solución automática pero requiere ajuste — el valor predeterminado
autovacuum_vacuum_scale_factor = 0.2es demasiado alto para tablas grandes. - Usa configuración por tabla con
ALTER TABLE SETpara tablas de alta escritura en lugar de cambiar los valores globales. - Monitorea
pg_stat_user_tablesparan_dead_tupylast_autovacuum, ypg_databaseparaage(datfrozenxid). - Usa pg_repack en lugar de
VACUUM FULLen tablas de producción para evitar tiempo de inactividad. - Mantén
age(datfrozenxid)muy por debajo de 1,500 millones en todas las bases de datos para prevenir emergencias de desbordamiento.