PostgreSQL autovacuum is performing an emergency VACUUM operation to freeze old transaction IDs and prevent transaction ID wraparound, which could cause database shutdown if not addressed.
This message indicates that PostgreSQL's autovacuum process has initiated a special VACUUM operation to prevent transaction ID (XID) wraparound. PostgreSQL uses 32-bit transaction IDs, which wrap around after approximately 4 billion transactions. To prevent data corruption and database shutdown, PostgreSQL must periodically "freeze" old transaction IDs by marking them as permanently visible to all transactions. When a table's oldest unfrozen transaction ID approaches the wraparound threshold (controlled by autovacuum_freeze_max_age, default 200 million transactions), PostgreSQL launches an aggressive autovacuum that cannot be cancelled or interrupted. This is a protective measure - if the database reaches 2 billion unfrozen transactions, PostgreSQL will halt all write operations to prevent catastrophic data loss. This autovacuum runs with higher priority than regular maintenance vacuums and will scan the entire table to freeze transaction IDs, which can be I/O intensive and time-consuming on large tables.
First, identify which tables are closest to wraparound. Connect to PostgreSQL and run:
SELECT datname, age(datfrozenxid) AS xid_age,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY age(datfrozenxid) DESC;Then check individual tables:
SELECT c.oid::regclass AS table_name,
greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS age,
pg_size_pretty(pg_total_relation_size(c.oid)) AS size
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm')
ORDER BY age DESC
LIMIT 20;Tables with age approaching 200 million (or your configured autovacuum_freeze_max_age) need immediate attention.
Wraparound vacuums can be blocked by long-running transactions or locks. Find blocking transactions:
SELECT pid, usename, state, backend_xid, backend_xmin,
now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE state != 'idle'
AND (backend_xid IS NOT NULL OR backend_xmin IS NOT NULL)
ORDER BY age(backend_xmin) DESC NULLS LAST;If you find old transactions blocking vacuum, terminate them carefully:
-- Try gentle termination first
SELECT pg_cancel_backend(pid);
-- If that doesn't work after a few seconds
SELECT pg_terminate_backend(pid);Replace pid with the process ID of the blocking transaction. Coordinate with application teams before terminating production transactions.
Check the autovacuum progress to see how long it will take:
SELECT pid, now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE query LIKE '%autovacuum%wraparound%'
AND query NOT LIKE '%pg_stat_activity%';For more detailed progress on PostgreSQL 13+:
SELECT p.pid, p.datname, p.relid::regclass AS table_name,
p.phase, p.heap_blks_total, p.heap_blks_scanned,
p.heap_blks_vacuumed,
round(100.0 * p.heap_blks_scanned /
NULLIF(p.heap_blks_total, 0), 1) AS percent_complete
FROM pg_stat_progress_vacuum p
JOIN pg_stat_activity a ON p.pid = a.pid
WHERE a.query LIKE '%wraparound%';Do NOT cancel wraparound vacuums unless absolutely necessary - they will restart from the beginning.
To prevent wraparound vacuums, tune autovacuum to run more aggressively. In postgresql.conf or via ALTER SYSTEM:
-- Increase number of autovacuum workers
ALTER SYSTEM SET autovacuum_max_workers = 4; -- default is 3
-- Reduce autovacuum delay for faster processing
ALTER SYSTEM SET autovacuum_vacuum_cost_delay = 10; -- default is 2ms
-- Increase cost limit for faster vacuuming
ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 1000; -- default is 200
-- For very large databases, consider adjusting freeze age
-- (Only increase if you have good monitoring)
-- ALTER SYSTEM SET autovacuum_freeze_max_age = 300000000;
-- Apply changes
SELECT pg_reload_conf();For specific problem tables, you can set per-table autovacuum parameters:
ALTER TABLE large_table SET (
autovacuum_vacuum_scale_factor = 0.05, -- vacuum more frequently
autovacuum_vacuum_cost_delay = 5 -- vacuum faster
);Implement monitoring to catch wraparound issues before they become critical. Create a monitoring query:
-- Alert when any table is over 80% of autovacuum_freeze_max_age
SELECT c.oid::regclass AS table_name,
greatest(age(c.relfrozenxid), age(t.relfrozenxid)) AS age,
setting::bigint AS freeze_max_age,
round(100.0 * greatest(age(c.relfrozenxid), age(t.relfrozenxid)) /
setting::bigint, 1) AS percent_to_wraparound
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
CROSS JOIN pg_settings s
WHERE c.relkind IN ('r', 'm')
AND s.name = 'autovacuum_freeze_max_age'
AND greatest(age(c.relfrozenxid), age(t.relfrozenxid)) >
setting::bigint * 0.8
ORDER BY age DESC;Set up alerts in your monitoring system (Prometheus, Datadog, CloudWatch, etc.) to notify you when transaction ID age exceeds 160 million (80% of default threshold).
Understanding Transaction ID Wraparound: PostgreSQL's 32-bit transaction IDs wrap around after 4,294,967,296 transactions. The system uses a modular arithmetic system where transaction IDs are only compared within a 2-billion transaction window. Beyond this window, the system cannot determine which transaction is older, risking data corruption.
Freezing Process: When VACUUM freezes a row, it sets special hint bits (HEAP_XMIN_FROZEN) that indicate the row is visible to all transactions regardless of their transaction ID. This removes the row from wraparound considerations. The frozen XID value is stored in pg_class.relfrozenxid for each table.
Emergency Shutdown Protection: If transaction IDs reach within 1 million of the wraparound point (about 2.1 billion transactions), PostgreSQL enters read-only mode and refuses all write operations. This prevents the catastrophic scenario where old rows become invisible due to wraparound.
Partitioning Strategy: For very large tables (>100GB), consider partitioning to allow autovacuum to work on smaller chunks concurrently. Each partition maintains its own relfrozenxid, and autovacuum can process multiple partitions simultaneously with separate workers.
Cost-Based Vacuum Delay: The autovacuum_vacuum_cost_delay and autovacuum_vacuum_cost_limit parameters throttle vacuum I/O to reduce impact on query performance. However, for wraparound prevention, these delays are reduced by autovacuum_vacuum_cost_delay/2, making wraparound vacuums faster than regular maintenance vacuums.
Manual VACUUM vs Autovacuum: You can manually run VACUUM FREEZE on tables to immediately freeze old XIDs, but this locks the table and can be very I/O intensive. It's generally better to let autovacuum handle it unless you're in a critical situation approaching shutdown.
Monitoring on Managed Services: On AWS RDS, use CloudWatch metrics like "MaximumUsedTransactionIDs". On Google Cloud SQL, check the transaction ID utilization recommender. On Azure Database for PostgreSQL, monitor the "transaction_id_wraparound" alert.
vacuum failsafe triggered
How to fix "vacuum failsafe triggered" in PostgreSQL
PANIC: could not write to file
How to fix PANIC: could not write to file in PostgreSQL
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL
ERROR 42501: must be owner of table
How to fix "must be owner of table" in PostgreSQL
trigger cannot change partition destination
How to fix "Trigger cannot change partition destination" in PostgreSQL