Why dead tuples accumulate
PostgreSQL's MVCC design means every UPDATE creates a new row version and marks the old one as dead. Dead tuples can't just disappear — older transactions might still be looking at them. Once no transaction can possibly need them, they're garbage; VACUUM is the garbage collector.
What VACUUM actually does
- Marks dead tuples as reusable space (regular VACUUM).
- Updates the visibility map, which enables index-only scans.
- Updates statistics if combined with ANALYZE.
- VACUUM FULL rewrites the entire table to actually shrink it on disk — locks the table; rarely needed.
Autovacuum is the default and usually right
The autovacuum daemon wakes up periodically and vacuums tables when their dead-tuple ratio exceeds a threshold. For most workloads, leaving autovacuum at defaults is correct. The corrections come for hot tables where defaults under-vacuum (tune autovacuum_vacuum_scale_factor down per table).
How to spot vacuum problems
If a table's n_dead_tup in pg_stat_user_tables is in the millions, autovacuum isn't keeping up. Either the table is updated faster than autovacuum can clean, or autovacuum is being blocked by long-running transactions.