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+)
  • psql o un cliente SQL con acceso de nivel DBA
  • Capacidad para editar postgresql.conf o usar ALTER 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:

  1. 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.
  2. Bloat de índice: Los índices B-tree acumulan punteros a tuplas muertas, incrementando su tamaño y el costo de escaneo.
  3. 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

ComandoBloqueoRecuperación de espacioCaso de uso
VACUUMNinguno (comparte con lecturas/escrituras)Marca espacio para reutilizar, no encoge el archivoMantenimiento rutinario, autovacuum
VACUUM ANALYZENingunoIgual + actualiza estadísticas del planificadorDespués de inserciones/borrados masivos
VACUUM FULLExclusivo (tabla bloqueada)Reescribe la tabla, devuelve espacio al SORecuperación puntual de bloat severo
VACUUM FREEZENingunoFuerza la congelación de todos los XID antiguosPrevenció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:

  1. En autovacuum_freeze_max_age (200M XID), autovacuum inicia congelación agresiva en la tabla.
  2. En vacuum_freeze_min_age (50M XID) desde el XID sin congelar más antiguo, VACUUM empieza a congelar filas.
  3. A 1,000 millones de XID antes del desbordamiento, PostgreSQL empieza a registrar advertencias en el log.
  4. A 10 millones de XID restantes, PostgreSQL deja de aceptar nuevas transacciones y requiere VACUUM FREEZE para 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 BDautovacuum_max_workersautovacuum_vacuum_scale_factorautovacuum_vacuum_cost_limit
< 10 GB3 (predeterminado)0.05200 (predeterminado)
10–100 GB4–50.02400
100 GB – 1 TB6–80.01600
> 1 TB8–10 + por tabla0.005 en tablas activas800

Comparativa entre sistemas de bases de datos

CaracterísticaPostgreSQL VACUUMMySQL OPTIMIZE TABLESQL Server Index RebuildOracle Segment Shrink
Bloqueos requeridosNinguno (regular), Exclusivo (FULL)ExclusivoEn línea (Enterprise)Exclusivo de fila
Herramienta de reescritura en líneapg_repackpt-online-schema-changeRebuild en líneaSHRINK SPACE COMPACT
AutomáticoDaemon autovacuumManual o event schedulerMantenimiento automático de índicesNo automático
Riesgo de desbordamientoSí (XID)NoNoNo
Visibilidad del bloatpg_stat_user_tables, pgstattupleinformation_schemasys.dm_db_index_physical_statsDBA_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.2 es demasiado alto para tablas grandes.
  • Usa configuración por tabla con ALTER TABLE SET para tablas de alta escritura en lugar de cambiar los valores globales.
  • Monitorea pg_stat_user_tables para n_dead_tup y last_autovacuum, y pg_database para age(datfrozenxid).
  • Usa pg_repack en lugar de VACUUM FULL en 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.

Artículos Relacionados