PostgreSQL Vacuum and Autovacuum Tuning
February 13, 2026
|
PostgreSQL
Performance
Tune autovacuum and prevent wraparound.
Understanding VACUUM and Autovacuum
PostgreSQL uses MVCC (Multi-Version Concurrency Control) — updates and deletes don't remove old row versions immediately. VACUUM reclaims this dead space. Without regular vacuuming, tables bloat, queries slow down, and eventually you risk transaction ID wraparound — a catastrophic event that forces the database into read-only mode.
What VACUUM Does
- Marks dead tuples (deleted/updated rows) as reusable space
- Updates the visibility map for index-only scans
- Freezes old transaction IDs to prevent wraparound
VACUUM FULL— rewrites the entire table to reclaim disk space (requires exclusive lock)
Autovacuum Configuration
# postgresql.conf — recommended settings
autovacuum = on
autovacuum_max_workers = 5 # Default 3, increase for busy DBs
autovacuum_naptime = 30s # Check every 30 seconds
autovacuum_vacuum_threshold = 50 # Minimum dead tuples before vacuum
autovacuum_vacuum_scale_factor = 0.05 # Default 0.2 (20%), reduce to 5%
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.02 # Default 0.1, reduce to 2%
autovacuum_vacuum_cost_delay = 2ms # Default 2ms (reduce for faster vacuum)
autovacuum_vacuum_cost_limit = 1000 # Default 200 (increase for faster vacuum)
Per-Table Tuning for Hot Tables
-- For tables with heavy UPDATE/DELETE traffic
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01, -- Vacuum after 1% dead tuples
autovacuum_vacuum_threshold = 100,
autovacuum_analyze_scale_factor = 0.005
);
Monitoring Vacuum Activity
-- Check dead tuples and last vacuum time
SELECT schemaname, relname, n_dead_tup, n_live_tup,
round(n_dead_tup::numeric / GREATEST(n_live_tup, 1) * 100, 2) as dead_pct,
last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
Preventing Transaction ID Wraparound
-- Monitor oldest unfrozen transaction
SELECT datname, age(datfrozenxid) as xid_age,
current_setting('autovacuum_freeze_max_age')::int as freeze_max_age
FROM pg_database
ORDER BY xid_age DESC;
If xid_age approaches autovacuum_freeze_max_age (default 200 million), PostgreSQL forces aggressive anti-wraparound vacuums that can impact performance.
Eazy SaaS Tip: Reduce
autovacuum_vacuum_scale_factorto 0.05 and increaseautovacuum_vacuum_cost_limitto 1000. These two changes dramatically improve autovacuum's ability to keep up with busy tables.