How to fix autovacuum: VACUUM table (to prevent wraparound) in PostgreSQL

PostgreSQLADVANCEDCRITICALUpdated Dec 22, 2025ยท 131 views

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.

Quick fix

Fixes autovacuum: VACUUM table (to prevent wraparound)

SELECT datname, age(datfrozenxid) AS xid_age,
       pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
  1. Check transaction ID age of all databases
  2. Identify and terminate blocking transactions
  3. Monitor the wraparound vacuum progress
  4. Adjust autovacuum settings to prevent future occurrences
postgresql โ€” error
SELECT datname, age(datfrozenxid) AS xid_age,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY age(datfrozenxid) DESC;
autovacuum: VACUUM table (to prevent wraparound)

What this error means

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.

Typical symptoms

  • Log message showing "autovacuum: VACUUM table_name (to prevent wraparound)"
  • High I/O load from autovacuum processes that cannot be cancelled
  • Query performance degradation while wraparound vacuum runs
  • pg_stat_activity shows autovacuum queries with "(to prevent wraparound)" suffix
  • Warnings about approaching transaction ID wraparound in logs
  • Database refusing new write transactions if critical threshold (2 billion XIDs) is reached

Common causes

  1. Table has not been vacuumed frequently enough, allowing transaction IDs to age beyond autovacuum_freeze_max_age threshold (default 200 million)
  2. Long-running transactions preventing normal autovacuum from freezing old transaction IDs
  3. Autovacuum disabled or configured too conservatively for the database transaction rate
  4. Very large tables that take longer to vacuum than the interval between vacuums
  5. Locks held on tables preventing regular autovacuum from completing
  6. Insufficient autovacuum_max_workers for the number of tables requiring vacuum
  7. Autovacuum throttled by cost-based delay settings causing it to fall behind

How to fix "autovacuum: VACUUM table (to prevent wraparound)"

1Check transaction ID age of all databases

First, identify which tables are closest to wraparound. Connect to PostgreSQL and run:

sql
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:

sql
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.

2Identify and terminate blocking transactions

Wraparound vacuums can be blocked by long-running transactions or locks. Find blocking transactions:

sql
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:

sql
-- 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.

3Monitor the wraparound vacuum progress

Check the autovacuum progress to see how long it will take:

sql
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+:

sql
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.

4Adjust autovacuum settings to prevent future occurrences

To prevent wraparound vacuums, tune autovacuum to run more aggressively. In postgresql.conf or via ALTER SYSTEM:

sql
-- 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:

sql
ALTER TABLE large_table SET (
  autovacuum_vacuum_scale_factor = 0.05,  -- vacuum more frequently
  autovacuum_vacuum_cost_delay = 5        -- vacuum faster
);
5Set up monitoring to detect issues early

Implement monitoring to catch wraparound issues before they become critical. Create a monitoring query:

sql
-- 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).

Advanced notes

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.

Frequently asked questions

What does "autovacuum: VACUUM table (to prevent wraparound)" mean?
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.
How do I fix "autovacuum: VACUUM table (to prevent wraparound)"?
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.
What causes "autovacuum: VACUUM table (to prevent wraparound)"?
Table has not been vacuumed frequently enough, allowing transaction IDs to age beyond autovacuum_freeze_max_age threshold (default 200 million) Long-running transactions preventing normal autovacuum from freezing old transaction IDs Autovacuum disabled or configured too conservatively for the database transaction rate
Is the "autovacuum: VACUUM table (to prevent wraparound)" error serious?
Yes. This is a critical-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading