Las bases de datos de PostgreSQL requieren un mantenimiento periódico
conocido como vacío (vacuuming). Para muchas instalaciones,
es suficiente dejar que el vacío sea realizado por el demonio de
autovacuum, el cual se describe en el Section 24.1.6. Es posible
que debas ajustar los parámetros de autovacuum descritos allí para obtener los mejores
resultados para tu situación. Algunos administradores de bases de datos querrán
complementar o reemplazar las actividades del demonio con comandos
VACUUM gestionados manualmente, que normalmente se ejecutan según una
programación mediante scripts de cron o del Programador
de tareas. Para configurar correctamente el vacío gestionado manualmente,
es esencial comprender los problemas que se analizan en las próximas secciones.
Los administradores que confían en autovacuum aún pueden querer leer superficialmente este
material para ayudarles a entender y ajustar el autovacuum.
El comando VACUUM de
PostgreSQL tiene que procesar cada tabla de forma regular por varias razones:
Cada una de estas razones dicta la realización de operaciones VACUUM
con diferente frecuencia y alcance, como se explica en las siguientes secciones.
Existen dos variantes de VACUUM: el VACUUM
estándar y VACUUM FULL. VACUUM FULL puede reclamar más
espacio en disco pero se ejecuta mucho más lento. Además,
la forma estándar de VACUUM puede ejecutarse en paralelo con las operaciones de
producción de la base de datos. (Los comandos como SELECT,
INSERT, UPDATE y
DELETE continuarán funcionando normalmente, aunque no
podrás modificar la definición de una tabla con comandos como
ALTER TABLE mientras se está ejecutando el vacío).
VACUUM FULL requiere un bloqueo de tipo
ACCESS EXCLUSIVE sobre la tabla en la que está
trabajando y, por lo tanto, no se puede realizar en paralelo con otros usos
de la tabla. Por lo general, los administradores deben esforzarse por
utilizar el VACUUM estándar y evitar VACUUM FULL.
VACUUM crea una cantidad sustancial de tráfico de E/S,
lo que puede causar un rendimiento deficiente para otras sesiones activas.
Hay parámetros de configuración que se pueden ajustar para reducir el
impacto en el rendimiento del vacío en segundo plano; consulta el
Section 19.10.2.
En PostgreSQL, un
UPDATE o un DELETE de una fila no
elimina inmediatamente la versión anterior de la fila.
Este enfoque es necesario para obtener los beneficios del control de
concurrencia multiversión (MVCC, consulta el Chapter 13): la versión de la fila
no debe eliminarse mientras sea potencialmente visible para otras
transacciones. Pero finalmente, una versión de fila obsoleta o eliminada ya no
interesa a ninguna transacción. El espacio que ocupa debe ser entonces
reclamado para su reutilización por nuevas filas, para evitar un crecimiento ilimitado del espacio
en disco requerido. Esto se hace ejecutando VACUUM.
La forma estándar de VACUUM elimina las versiones de fila muertas
en tablas e índices y marca el espacio como disponible para
su futura reutilización. Sin embargo, no devolverá el espacio al sistema
operativo, excepto en el caso especial en el que una o más páginas al
final de una tabla queden completamente vacías y se pueda obtener fácilmente un bloqueo
exclusivo de la tabla. Por el contrario, VACUUM FULL compacta activamente
las tablas escribiendo una versión completamente nueva del archivo de la tabla sin espacio
muerto. Esto minimiza el tamaño de la tabla, pero puede llevar mucho tiempo.
También requiere espacio en disco adicional para la nueva copia de la tabla, hasta
que se complete la operación.
El objetivo habitual del vacío rutinario es realizar VACUUMs
estándar con la frecuencia suficiente para evitar la necesidad de VACUUM FULL. El
demonio de autovacuum intenta funcionar de esta manera y, de hecho,
nunca emitirá un VACUUM FULL. En este enfoque, la idea
no es mantener las tablas en su tamaño mínimo, sino mantener un uso estable
del espacio en disco: cada tabla ocupa un espacio equivalente a su
tamaño mínimo más la cantidad de espacio que se utiliza entre las ejecuciones de vacío.
Aunque se puede usar VACUUM FULL para reducir una tabla a su
tamaño mínimo y devolver el espacio en disco al sistema operativo,
esto no tiene mucho sentido si la tabla volverá a crecer en el
futuro. Por lo tanto, las ejecuciones de VACUUM estándar moderadamente frecuentes son un
enfoque mejor que las ejecuciones poco frecuentes de VACUUM FULL para
mantener las tablas con actualizaciones intensivas.
Algunos administradores prefieren programar el vacío ellos mismos, por ejemplo
haciendo todo el trabajo por la noche cuando la carga es baja.
La dificultad de realizar el vacío según un programa fijo
es que si una tabla tiene un pico inesperado en la actividad de actualización, puede
hincharse hasta el punto de que VACUUM FULL sea realmente necesario
para reclamar espacio. El uso del demonio de autovacuum mitiga este problema,
ya que el demonio programa el vacío dinámicamente en respuesta a la actividad
de actualización. No es aconsejable desactivar el demonio por completo a menos que
tengas una carga de trabajo extremadamente predecible. Un compromiso posible es
configurar los parámetros del demonio para que solo reaccione a una actividad de
actualización inusualmente intensa, evitando así que las cosas se salgan de control,
mientras que se espera que los VACUUMs programados hagan la mayor parte del
trabajo cuando la carga es la habitual.
Para quienes no usan autovacuum, un enfoque típico es programar un
VACUUM en toda la base de datos una vez al día durante un período de bajo uso,
complementado con un vacío más frecuente de las tablas con actualizaciones intensivas según
sea necesario. (Algunas instalaciones con tasas de actualización extremadamente altas vacían
sus tablas más ocupadas con una frecuencia de una vez cada pocos minutos). Si tienes
varias bases de datos en un clúster, no olvides hacer
VACUUM en cada una de ellas; el programa vacuumdb podría ser de ayuda.
El VACUUM simple puede no ser satisfactorio cuando
una tabla contiene una gran cantidad de versiones de filas muertas como resultado de
una actividad masiva de actualización o eliminación. Si tienes una tabla así y
necesitas reclamar el exceso de espacio en disco que ocupa, deberás
utilizar VACUUM FULL, o alternativamente
CLUSTER
o una de las variantes de reescritura de tablas de
ALTER TABLE.
Estos comandos reescriben una copia nueva completa de la tabla y crean
nuevos índices para ella. Todas estas opciones requieren un bloqueo de tipo
ACCESS EXCLUSIVE. Ten en cuenta que
también utilizan temporalmente espacio en disco adicional aproximadamente igual al tamaño
de la tabla, ya que las copias antiguas de la tabla y los índices no se pueden
liberar hasta que las nuevas estén completas.
Si tienes una tabla cuyo contenido completo se elimina de forma periódica,
considera hacerlo con
TRUNCATE en lugar
de usar DELETE seguido de
VACUUM. TRUNCATE elimina el
contenido completo de la tabla inmediatamente, sin requerir un
VACUUM o un VACUUM FULL posterior
para reclamar el espacio en disco ahora no utilizado.
La desventaja es que se violan las reglas estrictas de MVCC.
El planificador de consultas de PostgreSQL se basa en
información estadística sobre el contenido de las tablas para
generar buenos planes para las consultas. Estas estadísticas se recopilan mediante
el comando ANALYZE,
que se puede invocar por sí solo o
como un paso opcional en VACUUM. Es importante contar con
estadísticas razonablemente precisas, de lo contrario, las malas elecciones de planes podrían
degradar el rendimiento de la base de datos.
El demonio de autovacuum, si está habilitado, emitirá automáticamente
comandos ANALYZE cada vez que el contenido de una tabla haya
cambiado lo suficiente. Sin embargo, los administradores podrían preferir confiar
en operaciones ANALYZE programadas manualmente, particularmente
si se sabe que la actividad de actualización en una tabla no afectará a las
estadísticas de las columnas “interesantes”. El demonio programa
ANALYZE estrictamente en función del número de filas
insertadas o actualizadas; no tiene conocimiento de si eso conducirá
a cambios estadísticos significativos.
Las tuplas modificadas en particiones y tablas hijas en una herencia no activan
el análisis en la tabla padre. Si la tabla padre está vacía o cambia rara vez,
es posible que nunca sea procesada por autovacuum y no se recopilarán las estadísticas para
el árbol de herencia en su conjunto. Es necesario
ejecutar ANALYZE en la tabla padre manualmente para
mantener las estadísticas actualizadas.
Al igual que con el vacío para la recuperación de espacio, las actualizaciones frecuentes de las estadísticas
son más útiles para las tablas con actualizaciones intensivas que para las que se actualizan poco.
Pero incluso para una tabla con actualizaciones intensivas, podría no haber necesidad de
actualizar las estadísticas si la distribución estadística de los datos no
está cambiando mucho. Una regla general sencilla es pensar en cuánto cambian
los valores mínimos y máximos de las columnas de la tabla.
Por ejemplo, una columna de tipo timestamp que contiene la hora
de actualización de la fila tendrá un valor máximo en constante aumento a medida que se
agregan y actualizan filas; tal columna probablemente necesitará actualizaciones de estadísticas
más frecuentes que, por ejemplo, una columna que contiene las URL de las páginas
visitadas en un sitio web. La columna de URL podría recibir cambios con la misma
frecuencia, pero la distribución estadística de sus valores probablemente cambia
de forma relativamente lenta.
Es posible ejecutar ANALYZE en tablas específicas e incluso
solo en columnas específicas de una tabla, por lo que existe la flexibilidad de actualizar algunas
estadísticas con más frecuencia que otras si tu aplicación lo requiere.
En la práctica, sin embargo, suele ser mejor analizar toda la
base de datos, porque es una operación rápida. ANALYZE utiliza un
muestreo estadísticamente aleatorio de las filas de una tabla en lugar de leer
cada una de las filas.
Aunque ajustar la frecuencia de ANALYZE por columna puede no ser
muy productivo, podría valer la pena realizar un ajuste
por columna del nivel de detalle de las estadísticas recopiladas por
ANALYZE. Las columnas que se utilizan con frecuencia en las cláusulas WHERE
y que tienen distribuciones de datos altamente irregulares podrían requerir un
histograma de datos de grano más fino que otras columnas. Consulta ALTER TABLE
SET STATISTICS, o cambia el valor predeterminado para todo el clúster usando el parámetro
de configuración default_statistics_target.
Además, de forma predeterminada hay información limitada disponible sobre la selectividad de las funciones. Sin embargo, si creas un objeto de estadísticas o un índice de expresión que utilice una llamada a una función, se recopilarán estadísticas útiles sobre la función, lo que puede mejorar enormemente los planes de consulta que utilicen el índice de expresión.
El demonio de autovacuum no emite comandos ANALYZE para
las tablas foráneas, ya que no tiene forma de determinar con qué frecuencia
podría ser útil. Si tus consultas requieren estadísticas en las tablas foráneas
para una planificación adecuada, es una buena idea ejecutar comandos
ANALYZE gestionados manualmente en esas tablas según un programa adecuado.
El demonio de autovacuum no emite comandos ANALYZE
para las tablas particionadas. Las tablas padres de herencia solo se analizarán si el
padre mismo cambia; los cambios en las tablas hijas no activan el
autoanálisis en la tabla padre. Si tus consultas requieren estadísticas en las
tablas padres para una planificación adecuada, es necesario ejecutar periódicamente un
ANALYZE manual en esas tablas para mantener las estadísticas
actualizadas.
El vacío mantiene un mapa de visibilidad para cada tabla para realizar un seguimiento de qué páginas contienen solo tuplas que se sabe que son visibles para todas las transacciones activas (y todas las transacciones futuras, hasta que la página se modifique nuevamente). Esto tiene dos propósitos. Primero, el vacío mismo puede omitir tales páginas en la próxima ejecución, ya que no hay nada que limpiar.
Segundo, permite a PostgreSQL responder a algunas consultas utilizando solo el índice, sin hacer referencia a la tabla subyacente. Dado que los índices de PostgreSQL no contienen información de visibilidad de tuplas, un escaneo de índice normal recupera la tupla de la tabla para cada entrada de índice coincidente, para verificar si debe ser vista por la transacción actual. Un escaneo de solo índice, por otro lado, verifica primero el mapa de visibilidad. Si se sabe que todas las tuplas de la página son visibles, se puede omitir la recuperación de la tabla. Esto es muy útil en conjuntos de datos grandes donde el mapa de visibilidad puede evitar accesos al disco. El mapa de visibilidad es enormemente más pequeño que la tabla, por lo que se puede almacenar fácilmente en caché incluso cuando la tabla es muy grande.
Las semánticas de transacción MVCC de PostgreSQL dependen de poder comparar números de ID de transacción (XID): una versión de fila con un XID de inserción mayor que el XID de la transacción actual está “en el futuro” y no debería ser visible para la transacción actual. Pero dado que los ID de transacción tienen un tamaño limitado (32 bits), un clúster que funciona durante mucho tiempo (más de 4 mil millones de transacciones) sufriría un desbordamiento de ID de transacción (transaction ID wraparound): el contador de XID vuelve a cero, y de repente las transacciones que estaban en el pasado parecen estar en el futuro, lo que significa que su salida se vuelve invisible. En resumen, una pérdida catastrófica de datos. (En realidad, los datos siguen ahí, pero eso sirve de poco consuelo si no puedes acceder a ellos). Para evitar esto, es necesario vaciar cada tabla de cada base de datos al menos una vez cada dos mil millones de transacciones.
La razón por la que el vacío periódico resuelve el problema es que
VACUUM marcará las filas como congeladas (frozen), indicando que
fueron insertadas por una transacción que se confirmó lo suficientemente en el
pasado como para que los efectos de la transacción de inserción sean visibles
para todas las transacciones actuales y futuras.
Los XIDs normales se
comparan utilizando aritmética módulo-232. Esto significa
que para cada XID normal, hay dos mil millones de XIDs que son
“más antiguos” y dos mil millones que son “más recientes”; otra
forma de decirlo es que el espacio de XID normal es circular y no tiene
punto final. Por lo tanto, una vez que se ha creado una versión de fila con un determinado
XID normal, la versión de fila aparecerá como “en el pasado” para
las siguientes dos mil millones de transacciones, sin importar de qué XID normal estemos
hablando. Si la versión de fila sigue existiendo después de más de dos mil millones de
transacciones, de repente aparecerá como en el futuro. Para evitar esto,
PostgreSQL reserva un XID especial,
FrozenTransactionId, que no sigue las reglas normales de comparación
de XID y siempre se considera más antiguo que cualquier XID normal.
Las versiones de fila congeladas se tratan como si el XID de inserción fuera
FrozenTransactionId, de modo que aparecerán como
“en el pasado” para todas las transacciones normales, independientemente de los problemas
de desbordamiento, por lo que dichas versiones de fila serán válidas hasta que se eliminen, sin importar
cuánto tiempo pase.
En las versiones de PostgreSQL anteriores a la 9.4, la congelación se
implementaba reemplazando el XID de inserción de una fila por
FrozenTransactionId, que era visible en la columna de sistema
xmin de la fila. Las versiones más recientes solo establecen un bit de bandera,
conservando el xmin original de la fila para un posible uso
forense. Sin embargo, las filas con xmin igual a
FrozenTransactionId (2) aún pueden encontrarse en bases de datos que
hayan sido actualizadas mediante pg_upgrade desde versiones anteriores a la 9.4.
Además, los catálogos del sistema pueden contener filas con un xmin igual a
BootstrapTransactionId (1), lo que indica que se insertaron
durante la primera fase de initdb. Al igual que
FrozenTransactionId, este XID especial se trata como más antiguo
que cualquier XID normal.
vacuum_freeze_min_age controla la antigüedad que debe tener un valor de XID antes de que las filas que lo lleven sean congeladas. Aumentar este valor puede evitar trabajo innecesario si las filas que de otro modo se congelarían se modificarán pronto de nuevo, pero disminuir este ajuste aumenta el número de transacciones que pueden transcurrir antes de que la tabla deba vaciarse de nuevo.
VACUUM utiliza el mapa de visibilidad
para determinar qué páginas de una tabla deben escanearse. Normalmente,
omitirá las páginas que no tengan versiones de filas muertas, incluso si esas páginas
todavía tienen versiones de filas con valores antiguos de XID. Por lo tanto, los
VACUUMs normales no siempre congelarán todas las versiones de filas antiguas de la tabla.
Cuando eso sucede, VACUUM eventualmente necesitará realizar un
vacío agresivo (aggressive vacuum), que congelará todos los valores XID
y MXID elegibles no congelados, incluidos los de las páginas totalmente visibles pero no totalmente congeladas.
Si una tabla está acumulando un retraso de páginas totalmente visibles pero no totalmente congeladas, un vacío normal puede optar por escanear páginas descartables en un esfuerzo por congelarlas. Al hacerlo, se reduce el número de páginas que debe escanear el siguiente vacío agresivo. Estas se denominan páginas escaneadas con avidez (eagerly scanned). El escaneo ávido se puede ajustar para intentar congelar más páginas totalmente visibles aumentando vacuum_max_eager_freeze_failure_rate. Incluso si el escaneo ávido ha mantenido al mínimo el número de páginas totalmente visibles pero no totalmente congeladas, la mayoría de las tablas todavía requieren un vacío agresivo periódico. Sin embargo, las páginas congeladas con avidez con éxito pueden omitirse durante un vacío agresivo, por lo que la congelación ávida puede minimizar la sobrecarga de los vacíos agresivos.
vacuum_freeze_table_age
controla cuándo se vacía agresivamente una tabla. Se escanean todas las páginas totalmente visibles
pero no totalmente congeladas si el número de transacciones que han pasado desde el
último escaneo de ese tipo es mayor que vacuum_freeze_table_age menos
vacuum_freeze_min_age. Establecer
vacuum_freeze_table_age a 0 obliga a VACUUM a
utilizar siempre su estrategia agresiva.
El tiempo máximo que una tabla puede permanecer sin vaciar es de dos mil millones de
transacciones menos el valor de vacuum_freeze_min_age en el momento del
último vacío agresivo. Si pasara más tiempo que ese sin vaciarse, podría producirse
una pérdida de datos. Para garantizar que esto no ocurra, se invoca autovacuum en cualquier
tabla que pueda contener filas no congeladas con XIDs más antiguos que la edad especificada
por el parámetro de configuración autovacuum_freeze_max_age. (Esto
sucederá incluso si autovacuum está desactivado).
Esto implica que si una tabla no es vaciada de otra manera, se invocará autovacuum en ella
aproximadamente una vez cada autovacuum_freeze_max_age menos
vacuum_freeze_min_age transacciones. Para las tablas que se vacían
regularmente con fines de reclamación de espacio, esto es de poca importancia. Sin embargo,
para las tablas estáticas (incluidas las tablas que reciben inserciones pero no actualizaciones
o eliminaciones), no hay necesidad de vaciar para reclamar espacio, por lo que puede ser útil
intentar maximizar el intervalo entre autovacuums forzados en tablas estáticas muy grandes.
Obviamente, esto se puede hacer aumentando autovacuum_freeze_max_age o
disminuyendo vacuum_freeze_min_age.
El máximo efectivo para vacuum_freeze_table_age es 0.95 *
autovacuum_freeze_max_age; un valor superior a ese se limitará al máximo.
Un valor superior a autovacuum_freeze_max_age no tendría sentido porque
en ese punto se activaría un autovacuum antidesbordamiento de todos modos, y el multiplicador
de 0.95 deja algo de margen de maniobra para ejecutar un VACUUM manual antes
de que eso ocurra. Como regla general, vacuum_freeze_table_age debería
establecerse en un valor algo inferior a autovacuum_freeze_max_age, dejando
suficiente espacio para que se ejecute un VACUUM programado regularmente
o un autovacuum activado por la actividad normal de eliminación y actualización en esa ventana.
Establecerlo demasiado cerca podría dar lugar a autovacuums antidesbordamiento, a pesar de que
la tabla se haya vaciado recientemente para reclamar espacio, mientras que los valores más bajos
provocan un vacío agresivo más frecuente.
La única desventaja de aumentar autovacuum_freeze_max_age
(y vacuum_freeze_table_age junto con él) es que los subdirectorios
pg_xact y pg_commit_ts del clúster de bases de datos
ocuparán más espacio, porque deben almacenar el estado de confirmación y (si está habilitado
track_commit_timestamp) la marca de tiempo de todas las transacciones
anteriores hasta el horizonte de autovacuum_freeze_max_age. El estado de
confirmación utiliza dos bits por transacción, por lo que si se establece
autovacuum_freeze_max_age a su valor máximo permitido de dos mil millones,
se puede esperar que pg_xact crezca a unos 500 MB y
pg_commit_ts a unos 20 GB. Si esto es insignificante en comparación con
el tamaño total de la base de datos, se recomienda establecer
autovacuum_freeze_max_age en su valor máximo permitido. De lo contrario,
establécelo en función de lo que estés dispuesto a permitir para el almacenamiento de
pg_xact y pg_commit_ts. (El valor predeterminado,
200 millones de transacciones, se traduce en unos 50 MB de almacenamiento para
pg_xact y unos 2 GB de almacenamiento para pg_commit_ts).
Una desventaja de disminuir vacuum_freeze_min_age es que podría hacer que
VACUUM realice un trabajo inútil: congelar una versión de fila es una pérdida
de tiempo si la fila se modifica poco después (lo que hace que adquiera un nuevo XID). Por lo tanto,
el ajuste debe ser lo suficientemente grande como para que las filas no se congelen hasta que sea
improbable que vuelvan a cambiar.
Para realizar el seguimiento de la antigüedad de los XIDs no congelados más antiguos en una base
de datos, VACUUM almacena estadísticas de XID en las tablas de sistema
pg_class y pg_database. En particular, la
columna relfrozenxid de la fila de una tabla en
pg_class contiene el XID no congelado más antiguo que quedaba al final
del VACUUM más reciente que avanzó con éxito el valor de
relfrozenxid (normalmente el VACUUM agresivo más reciente). Del mismo modo,
la columna datfrozenxid de la fila de una base de datos en
pg_database es un límite inferior para los XIDs no congelados que aparecen
en esa base de datos; es simplemente el mínimo de los valores relfrozenxid
de cada tabla dentro de la base de datos. Una forma conveniente de examinar esta información es
ejecutar consultas como:
SELECT c.oid::regclass as table_name,
greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');
SELECT datname, age(datfrozenxid) FROM pg_database;
La columna age mide el número de transacciones desde el XID de corte hasta
el XID de la transacción actual.
Cuando se especifica el parámetro VERBOSE del comando VACUUM,
VACUUM imprime varias estadísticas sobre la tabla. Esto incluye información
sobre cómo avanzaron relfrozenxid y relminmxid,
y la cantidad de páginas recién congeladas. Los mismos detalles aparecen en el registro del
servidor cuando el registro de autovacuum (controlado por log_autovacuum_min_duration) informa sobre una operación VACUUM
ejecutada por autovacuum.
Aunque VACUUM escanea principalmente las páginas que se han modificado desde
el último vacío, también puede escanear con avidez algunas páginas totalmente visibles pero no
totalmente congeladas en un intento de congelarlas, pero el relfrozenxid
solo avanzará cuando se escanee cada página de la tabla que pueda contener XIDs no congelados. Esto
sucede cuando relfrozenxid tiene una antigüedad de más de
vacuum_freeze_table_age transacciones, cuando se utiliza la opción
FREEZE de VACUUM, o cuando todas las páginas que no están
ya totalmente congeladas requieren un vacío para eliminar las versiones de filas muertas. Cuando
VACUUM escanea cada página de la tabla que no está ya totalmente congelada,
debería establecer age(relfrozenxid) en un valor un poco mayor que el ajuste de
vacuum_freeze_min_age que se utilizó (mayor por el número de transacciones iniciadas
desde que comenzó el VACUUM). VACUUM establecerá
relfrozenxid en el XID más antiguo que permanezca en la tabla, por lo que
es posible que el valor final sea mucho más reciente de lo que se requiere estrictamente. Si no se
emite ningún VACUUM que avance el valor de relfrozenxid
en la tabla hasta alcanzar autovacuum_freeze_max_age, pronto se forzará un
autovacuum para la tabla.
Si por alguna razón autovacuum no logra limpiar los XIDs antiguos de una tabla, el sistema comenzará a emitir mensajes de advertencia como este cuando los XIDs más antiguos de la base de datos alcancen los cuarenta millones de transacciones desde el punto de desbordamiento:
WARNING: database "mydb" must be vacuumed within 39985967 transactions HINT: To avoid XID assignment failures, execute a database-wide VACUUM in that database.
(Un VACUUM manual debería solucionar el problema, como sugiere el consejo;
pero ten en cuenta que el VACUUM debe realizarlo un superusuario, de lo contrario
no podrá procesar los catálogos del sistema, lo que le impide avanzar el valor de
datfrozenxid de la base de datos). Si se ignoran estas advertencias,
el sistema se negará a asignar nuevos XIDs una vez que queden menos de tres millones de transacciones
hasta el desbordamiento:
ERROR: database is not accepting commands that assign new XIDs to avoid wraparound data loss in database "mydb" HINT: Execute a database-wide VACUUM in that database.
En esta condición, cualquier transacción que ya esté en progreso puede continuar, pero solo se
pueden iniciar transacciones de solo lectura. Las operaciones que modifiquen registros de la base de
datos o que trunquen relaciones fallarán. El comando VACUUM aún puede ejecutarse
normalmente. Ten en cuenta que, contrariamente a lo que se recomendaba a veces en versiones anteriores,
no es necesario ni deseable detener el postmaster o entrar en modo monopuesto para restaurar la
operación normal. En su lugar, sigue estos pasos:
age(transactionid) sea grande. Tales transacciones deben ser confirmadas
o revertidas.age(backend_xid) o age(backend_xmin) sea grande. Tales
transacciones deben ser confirmadas o revertidas, o la sesión se puede terminar utilizando
pg_terminate_backend.age(xmin) o age(catalog_xmin) sea grande.
En muchos casos, estas ranuras se crearon para la replicación en servidores que ya no existen,
o que han estado inactivos durante mucho tiempo. Si eliminas una ranura de un servidor que aún
existe y que podría intentar conectarse de nuevo a esa ranura, es posible que esa réplica deba
reconstruirse.VACUUM en la base de datos de destino. Un VACUUM
en toda la base de datos es lo más sencillo; para reducir el tiempo necesario, también es posible
emitir comandos VACUUM manuales en las tablas donde
relminxid es más antiguo. No utilices VACUUM FULL
en este escenario, porque requiere un XID y, por lo tanto, fallará, excepto en el modo de
superusuario, donde en su lugar consumirá un XID y aumentará así el riesgo de desbordamiento de
ID de transacción. No utilices tampoco VACUUM FREEZE, porque hará más del
trabajo mínimo requerido para restaurar la operación normal.
In versiones anteriores, a veces era necesario detener el postmaster y realizar el
VACUUM de la base de datos en modo monopuesto (single-user). En los escenarios típicos,
esto ya no es necesario y debe evitarse siempre que sea posible, ya que implica apagar el sistema.
También es más arriesgado, ya que desactiva las salvaguardias contra el desbordamiento de ID de
transacción diseñadas para evitar la pérdida de datos. La única razón para usar el modo monopuesto en este
escenario es si deseas ejecutar un TRUNCATE o un DROP en las tablas
que no necesites para evitar tener que ejecutar VACUUM en ellas. El margen de seguridad de
tres millones de transacciones existe para permitir al administrador hacer esto. Consulta la página de referencia
de postgres para obtener detalles sobre el uso del modo monopuesto.
Los ID de multixact se utilizan para admitir el bloqueo de filas por parte de
múltiples transacciones. Dado que solo hay un espacio limitado en el encabezado de una tupla para
almacenar la información de bloqueo, esa información se codifica como un “ID de transacción
múltiple” (o ID de multixact para abreviar) siempre que haya más de una transacción bloqueando
simultáneamente una fila. La información sobre qué ID de transacción se incluyen en un ID de multixact
determinado se almacena por separado en el subdirectorio pg_multixact, y solo el ID
de multixact aparece en el campo xmax en el encabezado de la tupla. Al igual
que los ID de transacción, los ID de multixact se implementan como un contador de 32 bits y el almacenamiento
correspondiente, lo cual requiere una gestión cuidadosa del envejecimiento, la limpieza del almacenamiento
y la gestión del desbordamiento. Existe un área de almacenamiento independiente que contiene la lista de
miembros de cada multixact, la cual también utiliza un contador de 32 bits y que también debe gestionarse.
La función del sistema pg_get_multixact_members() descrita en
la Table 9.84 se puede utilizar para examinar los ID de transacción
asociados a un ID de multixact.
Cada vez que VACUUM escanea cualquier parte de una tabla, reemplazará cualquier ID
de multixact que encuentre que sea más antiguo que vacuum_multixact_freeze_min_age
por un valor diferente, que puede ser el valor cero, un único ID de transacción o un ID de multixact
más reciente. Para cada tabla, pg_class.relminmxid
almacena el ID de multixact más antiguo posible que todavía aparece en alguna tupla de esa tabla.
Si este valor es más antiguo que vacuum_multixact_freeze_table_age, se fuerza
un vacío agresivo. Como se explicó en la sección anterior, un vacío agresivo significa que solo se
omitirán aquellas páginas que se sabe que están totalmente congeladas. Se puede utilizar
mxid_age() en pg_class.relminmxid
para conocer su antigüedad.
Los VACUUMs agresivos, independientemente de lo que los cause, tienen
garantizado poder avanzar el valor de relminmxid de la tabla.
Finalmente, a medida que se escanean todas las tablas de todas las bases de datos y se avanzan sus valores
de multixact más antiguos, se puede eliminar el almacenamiento en disco de los multixacts más antiguos.
Como dispositivo de seguridad, se producirá un escaneo de vacío agresivo para cualquier tabla cuyo multixact-age sea mayor que autovacuum_multixact_freeze_max_age. Además, si el almacenamiento ocupado por los miembros de multixacts supera los 10 GB aproximadamente, se producirán escaneos de vacío agresivos con más frecuencia para todas las tablas, comenzando con aquellas que tengan la antigüedad de multixact más antigua. Ambos tipos de escaneos agresivos se producirán incluso si autovacuum está desactivado nominalmente. El área de almacenamiento de miembros puede crecer hasta unos 20 GB antes de alcanzar el desbordamiento.
De manera similar al caso de XID, si autovacuum no limpia los MXIDs antiguos de una tabla, el sistema comenzará a emitir mensajes de advertencia cuando los MXIDs más antiguos de la base de datos alcancen los cuarenta millones de transacciones desde el punto de desbordamiento. Y, al igual que en el caso de XID, si estas advertencias se ignoran, el sistema se negará a generar nuevos MXIDs una vez que queden menos de tres millones de transacciones para el desbordamiento.
La operación normal cuando se agotan los MXIDs se puede restaurar de la misma manera que cuando se agotan los XIDs. Sigue los mismos pasos de la sección anterior, pero con las siguientes diferencias:
pg_stat_activity; sin embargo, buscar XIDs antiguos sigue siendo una buena forma
de determinar qué transacciones están causando problemas de desbordamiento de MXID.
PostgreSQL tiene una característica opcional pero muy recomendada llamada
autovacuum, cuyo propósito es automatizar la ejecución de los comandos
VACUUM y ANALYZE. Cuando está habilitado, autovacuum busca
tablas que hayan tenido una gran cantidad de tuplas insertadas, actualizadas o eliminadas. Estas
comprobaciones utilizan la utilidad de recopilación de estadísticas; por lo tanto, no se puede usar
autovacuum a menos que track_counts esté establecido en true.
En la configuración predeterminada, autovacuum está habilitado y los parámetros de configuración relacionados
están configurados adecuadamente.
El “demonio de autovacuum” en realidad consta de múltiples procesos. Hay un proceso de demonio
persistente, llamado lanzador de autovacuum (autovacuum launcher), que se encarga
de iniciar procesos trabajadores de autovacuum (autovacuum worker) para todas las bases
de datos. El lanzador distribuirá el trabajo a lo largo del tiempo, intentando iniciar un trabajador dentro de
cada base de datos cada autovacuum_naptime segundos. (Por lo tanto, si la instalación
tiene N bases de datos, se lanzará un nuevo trabajador cada
autovacuum_naptime/N segundos). Se permite que se ejecute al mismo
tiempo un máximo de autovacuum_max_workers procesos de trabajadores. Si hay más de
autovacuum_max_workers bases de datos para procesar, la siguiente base de datos se procesará
tan pronto como termine el primer trabajador. Cada proceso trabajador comprobará cada tabla dentro de su base
de datos y ejecutará VACUUM y/o ANALYZE según sea necesario. Se puede configurar
log_autovacuum_min_duration para monitorear la actividad de los trabajadores de autovacuum.
Si varias tablas grandes se vuelven elegibles para el vacío en un corto espacio de tiempo, todos los trabajadores de autovacuum podrían ocuparse del vacío de esas tablas durante un largo período. Esto provocaría que otras tablas y bases de datos no se vaciaran hasta que haya un trabajador disponible. No hay límite en el número de trabajadores que pueden estar en una sola base de datos, pero los trabajadores intentan evitar repetir el trabajo que ya han realizado otros trabajadores. Ten en cuenta que el número de trabajadores en ejecución no cuenta para los límites de max_connections o superuser_reserved_connections.
Las tablas cuyo valor de relfrozenxid tiene una antigüedad de más de
autovacuum_freeze_max_age transacciones siempre se vacían (esto también se aplica a
aquellas tablas cuyo freeze max age se ha modificado mediante parámetros de almacenamiento; consulta más abajo).
De lo contrario, si el número de tuplas obsoletas desde el último VACUUM supera el
“umbral de vacío” (vacuum threshold), la tabla se vacía. El umbral de vacío se define como:
vacuum threshold = Minimum(vacuum max threshold, vacuum base threshold + vacuum scale factor * number of tuples)
donde el umbral máximo de vacío es autovacuum_vacuum_max_threshold,
el umbral base de vacío es autovacuum_vacuum_threshold,
el factor de escala de vacío es autovacuum_vacuum_scale_factor,
y el número de tuplas es pg_class.reltuples.
La tabla también se vacía si el número de tuplas insertadas desde el último vacío ha superado el umbral de inserción definido, el cual se define como:
vacuum insert threshold = vacuum base insert threshold + vacuum insert scale factor * number of tuples * percent of table not frozen
donde el umbral base de inserción de vacío es autovacuum_vacuum_insert_threshold,
el factor de escala de inserción de vacío es autovacuum_vacuum_insert_scale_factor,
el número de tuplas es pg_class.reltuples,
y el porcentaje de la tabla no congelada es 1 - pg_class.relallfrozen / pg_class.relpages.
Dichos vacíos pueden permitir que partes de la tabla se marquen como totalmente visibles
(all visible) y también permiten congelar tuplas, lo que puede reducir el trabajo requerido en los vacíos posteriores.
Para las tablas que reciben operaciones INSERT pero no o casi ninguna operación
UPDATE/DELETE, puede ser beneficioso reducir el
autovacuum_freeze_min_age de la tabla, ya que esto puede permitir congelar las tuplas
en vacíos anteriores. El número de tuplas obsoletas y el número de tuplas insertadas se obtienen del sistema de
estadísticas acumulativas; es un recuento eventualmente consistente actualizado por cada operación
UPDATE, DELETE e INSERT. Si el valor de
relfrozenxid de la tabla tiene una antigüedad de más de
vacuum_freeze_table_age transacciones, se realiza un vacío agresivo para congelar las tuplas
antiguas y avanzar relfrozenxid.
Para analyze, se utiliza una condición similar: el umbral, definido como:
analyze threshold = analyze base threshold + analyze scale factor * number of tuples
se compara con el número total de tuplas insertadas, actualizadas o eliminadas desde el último ANALYZE.
Las tablas particionadas no almacenan tuplas directamente y, por consiguiente, no son procesadas por autovacuum.
(Autovacuum procesa las particiones de la tabla al igual que las demás tablas). Desafortunadamente, esto significa
que autovacuum no ejecuta ANALYZE en las tablas particionadas, y esto puede provocar planes
subóptimos para las consultas que hacen referencia a las estadísticas de las tablas particionadas. Puedes solucionar
este problema ejecutando manualmente ANALYZE en las tablas particionadas cuando se rellenan por
primera vez, y de nuevo cada vez que la distribución de datos en sus particiones cambie significativamente.
Autovacuum no puede acceder a las tablas temporales. Por lo tanto, se deben realizar las operaciones adecuadas de vacío y análisis mediante comandos SQL de sesión.
Los umbrales y factores de escala predeterminados se toman de postgresql.conf, pero es posible
sobrescribirlos (y muchos otros parámetros de control de autovacuum) para cada tabla; consulta
Parámetros de almacenamiento para obtener más información. Si se ha modificado un ajuste
mediante los parámetros de almacenamiento de una tabla, se utiliza ese valor al procesar esa tabla; de lo contrario,
se utilizan los ajustes globales. Consulta Section 19.10.1 para obtener más detalles
sobre los ajustes globales.
Cuando se están ejecutando múltiples trabajadores, los parámetros de retardo de coste de autovacuum (consulta
Section 19.10.2) se “equilibran” entre todos los trabajadores
en ejecución, de modo que el impacto total de E/S en el sistema sea el mismo independientemente del número de
trabajadores que se estén ejecutando realmente. Sin embargo, no se consideran en el algoritmo de equilibrio los
trabajadores que procesan tablas cuyos parámetros de almacenamiento autovacuum_vacuum_cost_delay
o autovacuum_vacuum_cost_limit para cada tabla hayan sido establecidos.
Los trabajadores de autovacuum generalmente no bloquean otros comandos. Si un proceso intenta adquirir un bloqueo
que entra en conflicto con el bloqueo SHARE UPDATE EXCLUSIVE mantenido por autovacuum, la adquisición
del bloqueo interrumpirá al autovacuum. Para los modos de bloqueo conflictivos, consulta
Table 13.2. Sin embargo, si el autovacuum se está ejecutando para evitar el
desbordamiento de ID de transacción (es decir, el nombre de la consulta de autovacuum en la vista
pg_stat_activity termina con (to prevent wraparound)), el autovacuum
no se interrumpe automáticamente.
La ejecución regular de comandos que adquieren bloqueos conflictivos con un bloqueo
SHARE UPDATE EXCLUSIVE (por ejemplo, ANALYZE) puede evitar eficazmente que los
autovacuums se completen.