Demistificando Vacuum & Bloat
PostgreSQL nunca sobrescribe una fila en su lugar cuando la UPDATE o DELETEs.
En su lugar, deja la versión antigua atrás y escribe una nueva, que es la base de cómo la base de datos proporciona a cada transacción una instantánea consistente de los datos.
Esa versión antigua se convierte en una tupla muerta, y las tuplas muertas son la materia prima de lo que esta sección llama bloat.
Vacuum y bloat suenan como dos temas separados, pero en realidad son un solo mecanismo visto desde extremos opuestos: vacuum es el proceso de limpieza y bloat es lo que se acumula cuando la limpieza se queda atrás.
Esta página construye el modelo mental que el resto de la sección de vacuum-bloat-storage asume que ya tienes.
Resumen
- El diseño MVCC de PostgreSQL deja versiones de filas muertas atrás en cada actualización y eliminación, y vacuum es el proceso de fondo que recupera ese espacio y protege el clúster del agotamiento del ID de transacción.
- Por qué Importa: Omitir o sintonizar incorrectamente vacuum conduce a tablas infladas (bloated), escaneos de índices más lentos, desperdicio de disco y, en el peor de los casos, un apagado de emergencia para prevenir el wraparound del ID de transacción.
- Conceptos Clave: tupla muerta, MVCC, mapa de visibilidad, autovacuum, horizonte de congelación, bloat.
- Cuándo Usar: Cada tabla de producción necesita este modelo mental: explica por qué las tablas crecen después de las eliminaciones, por qué los escaneos solo de índice a veces dejan de funcionar y por qué deshabilitar autovacuum es casi siempre la decisión incorrecta.
- Limitaciones / Compensaciones: Vacuum recupera espacio para reutilizarlo dentro del archivo existente, pero no reduce el archivo en disco en el caso común, y las configuraciones agresivas de vacuum intercambian I/O y CPU por limpieza.
- Temas Relacionados: Visibilidad MVCC, sintonización de autovacuum, wraparound de ID de transacción, bloat de tablas e índices.
Fundamentos
MVCC, siglas de control de concurrencia multiversión, es la razón por la que PostgreSQL puede permitir que una transacción lea una tabla mientras otra la actualiza sin que ninguna de las dos se bloquee. Cada versión de fila lleva metadatos ocultos que registran qué transacción la creó y, si ha sido reemplazada, qué transacción la eliminó.
Cuando una transacción lee una tabla, PostgreSQL le entrega las versiones de fila que eran visibles en el momento en que se tomó su instantánea, ignorando las versiones creadas después y las versiones ya eliminadas antes.
Eso es potente para la concurrencia, pero significa que un DELETE no borra realmente nada en el instante en que se ejecuta.
La versión antigua de la fila permanece físicamente presente en el archivo de la tabla hasta que nada más pueda necesitarla, momento en el cual se convierte en una tupla muerta elegible para limpieza. Vacuum es el proceso que recorre las páginas de una tabla, identifica las tuplas muertas que ninguna transacción abierta puede ver más, y marca ese espacio como reutilizable para futuras inserciones y actualizaciones. Una analogía simple es un hotel que nunca demuele una habitación desocupada: simplemente marca la habitación como "vacía" para que el próximo huésped pueda mudarse, y vacuum es el pase de limpieza que cambia el estado de una habitación de ocupada a vacía.
Crucialmente, el trabajo de vacuum es hacer que el espacio sea reutilizable dentro del edificio existente, no demoler y reconstruir el edificio más pequeño.
Esa única distinción explica casi todas las sorpresas que los principiantes encuentran con vacuum y bloat.
Mecánicas e Interacciones
El trabajo más visible de Vacuum es recuperar el espacio de las tuplas muertas, pero su trabajo más crítico para la seguridad es prevenir el wraparound del ID de transacción, y ambas responsabilidades se ejecutan a través del mismo escaneo.
Los IDs de transacción de PostgreSQL son un contador finito que se reinicia (wraps), y los metadatos de visibilidad de cada fila están marcados con el ID de transacción que la creó.
Si se permitiera que el contador se reiniciara mientras las filas antiguas todavía hicieran referencia a un ID de transacción no congelado, esas filas podrían aparecer repentinamente como si vinieran del futuro y desaparecer de la vista.
Vacuum previene eso congelando periódicamente las versiones de filas antiguas, marcándolas como permanentemente visibles para que su ID de transacción original ya no importe.
Autovacuum decide cuándo actuar sobre una tabla utilizando una fórmula de umbral simple: se activa una vez que las tuplas muertas exceden un umbral base más un factor de escala multiplicado por el recuento de filas vivas de la tabla.
Esa fórmula es la razón por la que autovacuum_vacuum_scale_factor importa más a medida que una tabla crece: el umbral predeterminado es trivial en una tabla de 10 filas, pero en una tabla de 50 millones de filas permite que se acumulen decenas de millones de filas muertas antes de que autovacuum siquiera considere actuar.
-- Comprobación conceptual del disparador que realiza autovacuum por tabla:
-- se ejecuta cuando n_dead_tup >= vacuum_threshold + vacuum_scale_factor * n_live_tup
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
WHERE relname = 'orders';El mapa de visibilidad es la otra mitad de la historia de las mecánicas, rastreando qué páginas contienen solo tuplas visibles para todas las transacciones.
Una página marcada como completamente visible en ese mapa permite que un escaneo solo de índice responda a una consulta solo desde el índice, omitiendo por completo un viaje al heap.
Vacuum es lo que actualiza el mapa de visibilidad, por lo que una tabla que pasa demasiado tiempo sin vacuum pierde la elegibilidad para escaneos solo de índice, incluso cuando sus índices están de otra manera saludables.
Las transacciones de larga duración interactúan mal con todo esto, porque vacuum no puede eliminar una tupla muerta que la instantánea de una transacción más antigua aún podría necesitar ver.
Una sola sesión inactiva en transacción dejada abierta durante horas puede retener el horizonte de congelación de una tabla completa, y a veces de una base de datos completa, sin importar cuán agresivamente se sintonice autovacuum.
El bloat en sí mismo es simplemente la acumulación de tuplas muertas y fragmentación de páginas que supera la capacidad de vacuum para limpiarlo, y se manifiesta de manera diferente en tablas que en índices.
Consideraciones Avanzadas y Aplicaciones
El bloat de tablas y el bloat de índices no son el mismo fallo, y rara vez se solucionan con la misma herramienta.
El bloat de tablas significa que las páginas contienen tuplas muertas que vacuum puede marcar como reutilizables en su lugar, lo que mantiene el tamaño del archivo estable una vez que vacuum se pone al día.
El bloat de índices es estructural: una página B-tree que se ha dividido y vaciado parcialmente por eliminaciones no se fusiona automáticamente de nuevo en un árbol compacto, por lo que un índice puede permanecer inflado incluso después de que la tabla subyacente esté limpia.
Esa asimetría es la razón por la que el tamaño en disco de una tabla puede parecer bien mientras sus índices se inflan silenciosamente hasta varias veces su tamaño necesario. El almacenamiento TOAST añade otra complicación, ya que los valores de columna grandes se mueven de la tabla principal a una tabla TOAST separada con su propio ciclo de vida independiente de vacuum y bloat. A escala, la pregunta práctica cambia de "¿está funcionando vacuum?" a "¿está vacuum manteniéndose al día?", y eso es un problema de capacidad tanto como un problema de configuración.
Una tabla bajo una intensa rotación de actualizaciones o eliminaciones necesita un factor de escala más bajo y un límite de costo más alto que los valores predeterminados del clúster, porque esos valores predeterminados están sintonizados para la tabla mediana, no para la más activa.
PostgreSQL 18.4 todavía expone pg_stat_progress_vacuum para observar la fase y el progreso de un vacuum en curso, lo que es importante al decidir si esperar a que termine o intervenir.
Cuando el bloat ya ha ocurrido, las opciones de remediación intercambian disponibilidad por exhaustividad de diferentes maneras.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
VACUUM simple | En línea, sin bloqueo exclusivo, seguro de ejecutar en cualquier momento | No reduce el archivo en disco | Limpieza de tuplas muertas de rutina |
VACUUM FULL | Compacta completamente la tabla a su tamaño mínimo | Toma un bloqueo de acceso exclusivo, bloqueando todo acceso | Encogimiento raro, solo en ventanas de mantenimiento |
pg_repack | Reconstruye la tabla en línea con un bloqueo mínimo | Necesita espacio adicional temporal en disco durante la reconstrucción | Encogimiento en producción sin tiempo de inactividad |
REINDEX CONCURRENTLY | Reconstruye un índice inflado sin bloquear lecturas/escrituras | Corrige solo el bloat de índices, no el bloat de tablas | Índices inflados en tablas activas |
| Particionamiento + detach | Elimina datos antiguos instantáneamente sin necesidad de vacuum en las filas eliminadas | Requiere una clave de partición que coincida con el patrón de retención | Tablas de series temporales o con muchas inserciones |
Deshabilitar autovacuum en una tabla ocupada para "reducir la carga" es uno de los errores más costosos que un equipo puede cometer, porque las tuplas muertas y las filas no congeladas continúan acumulándose invisiblemente hasta que el rendimiento se degrada drásticamente o la salvaguarda de wraparound obliga a PostgreSQL a un modo de solo lectura.
Los proveedores de nube administrada generalmente dejan autovacuum activado por defecto y exponen las mismas GUCs por tabla, por lo que este modelo mental se transfiere directamente, ya sea que el clúster sea autoalojado o administrado.
Conceptos Erróneos Comunes
- "DELETE libera espacio en disco inmediatamente." La fila se convierte primero en una tupla muerta, y solo vacuum hace que ese espacio sea reutilizable, e incluso entonces el archivo rara vez se reduce: un concepto erróneo que proviene de cómo la mayoría del otro software trata la eliminación como inmediata y final.
- "VACUUM FULL es solo un VACUUM más exhaustivo." Es una operación fundamentalmente diferente que reescribe toda la tabla bajo un bloqueo exclusivo, no una versión más fuerte del vacuum de rutina, razón por la cual nunca debe ser una respuesta predeterminada al bloat.
- "Autovacuum en ejecución significa que el bloat es imposible." Que autovacuum se esté ejecutando no garantiza que se esté manteniendo al día con la tasa de escritura, y una tabla aún puede inflarse constantemente si el crecimiento de tuplas muertas supera la frecuencia de vacuum.
- "Desactivar autovacuum mejora el rendimiento." Elimina I/O visible a corto plazo mientras acumula silenciosamente tuplas muertas y riesgo de wraparound que cuestan mucho más de arreglar después de lo que jamás costó la sobrecarga de vacuum.
- "Vacuum solo se trata de recuperar espacio." Congelar filas para prevenir el wraparound del ID de transacción es al menos tan importante como la recuperación de espacio, y PostgreSQL fuerza un vacuum agresivo por esa razón, incluso en una tabla sin problema de bloat.
Preguntas Frecuentes
¿Por qué DELETE no reduce mi tabla en disco?
Porque el diseño MVCC de PostgreSQL deja la versión antigua de la fila en su lugar como una tupla muerta; vacuum luego marca ese espacio como reutilizable para nuevas filas, pero reutilizar espacio dentro del archivo no es lo mismo que devolver bytes al sistema operativo.
¿Cuál es la diferencia entre VACUUM y VACUUM FULL?
VACUUMsimple recupera espacio de tuplas muertas para su reutilización sin un bloqueo exclusivo, por lo que las lecturas y escrituras normales continúan.VACUUM FULLreescribe toda la tabla en un archivo nuevo y compacto bajo un bloqueo de acceso exclusivo que bloquea todo acceso hasta que finaliza.
¿Cómo decide autovacuum cuándo ejecutarse en una tabla?
Compara el recuento de tuplas muertas de la tabla con una fórmula de umbral, un umbral base más un factor de escala multiplicado por el recuento de filas vivas, y activa un vacuum una vez que las tuplas muertas cruzan esa línea.
¿Por qué el escaneo solo de índice deja de funcionar en una tabla que pensé que estaba bien?
Los escaneos solo de índice dependen del mapa de visibilidad que muestra una página como completamente visible para todas las transacciones, y solo vacuum actualiza ese mapa, por lo que una tabla que ha pasado demasiado tiempo sin vacuum pierde esa elegibilidad incluso si el índice en sí está saludable.
¿Es el bloat de tablas el mismo problema que el bloat de índices?
No: el bloat de tablas es espacio de tuplas muertas que vacuum puede recuperar en su lugar, mientras que el bloat de índices es fragmentación estructural por divisiones de páginas B-tree que vacuum solo generalmente no puede deshacer, razón por la cual los índices inflados a menudo necesitan REINDEX CONCURRENTLY por separado.
¿Qué es el wraparound del ID de transacción y por qué vacuum lo previene?
El contador de ID de transacción de PostgreSQL es finito y se reinicia (wraps), por lo que vacuum congela periódicamente las versiones de filas antiguas para hacer que su visibilidad sea independiente de ese contador, evitando un escenario donde las filas antiguas parecerían provenir del futuro una vez que el contador se reinicia.
¿Puede una transacción de larga duración realmente bloquear vacuum en una tabla completa?
Sí: vacuum no puede eliminar una tupla muerta que la instantánea de una transacción más antigua podría necesitar, por lo que una sola sesión inactiva en transacción dejada abierta durante horas puede retener la limpieza y la congelación en toda la tabla que tocó.
¿Debería deshabilitar autovacuum en una tabla alguna vez?
Casi nunca para toda la tabla: deshabilitarlo elimina la salvaguarda contra el bloat y el wraparound, y el enfoque estándar es sintonizar la configuración por tabla como el factor de escala y el límite de costo en lugar de desactivarlo.
¿Qué tiene que ver TOAST con el bloat?
Los valores de columna grandes se mueven de la tabla principal a una tabla TOAST separada con su propio ciclo de vida independiente de vacuum y bloat, por lo que el heap principal de una tabla puede parecer limpio mientras su almacenamiento TOAST está inflado, o viceversa.
¿Cómo sé si vacuum realmente se está manteniendo al día con mi carga de escritura?
Observe n_dead_tup en relación con n_live_tup y last_autovacuum en pg_stat_user_tables con el tiempo: una relación que sigue aumentando o un last_autovacuum que se queda cada vez más atrás, ambos indican que vacuum está perdiendo terreno.
¿Cuál es la forma menos disruptiva de arreglar una tabla de producción inflada?
pg_repack es generalmente la opción más segura, ya que reconstruye la tabla en línea con mucho menos bloqueo que VACUUM FULL, a costa de necesitar espacio adicional temporal en disco durante la reconstrucción.
¿Este modelo de vacuum se aplica de la misma manera en la nube administrada de PostgreSQL?
Sí: los proveedores administrados ejecutan la misma maquinaria de vacuum y autovacuum de PostgreSQL bajo el capó y exponen las mismas GUCs por tabla, por lo que el modelo mental y las palancas de sintonización descritas aquí se transfieren directamente.
Relacionados
- Fundamentos de Vacuum - comandos prácticos y consultas de monitoreo para el trabajo diario de vacuum
- Sintonización de Autovacuum - factores de escala y límites de costo para mantener vacuum por delante de la carga de escritura
- Wraparound de ID de Transacción - el mecanismo de congelación y la respuesta de emergencia al wraparound
- Bloat de Tablas e Índices - medición del bloat y elección de una ruta de remediación
- Visibilidad MVCC - el mecanismo de versiones de fila que hace que las tuplas muertas sean inevitables
Versiones de Stack: Esta página fue escrita para PostgreSQL 18.4 (estable 18, mantenimiento 17).