Heap Bloat and Vacuum
PostgreSQL vacuum reclaims dead tuples left by MVCC, and when it falls behind — long transactions, aggressive autovacuum settings — tables bloat and performance decays.
Definition
Vacuum is the PostgreSQL garbage collector: it marks space from dead tuple versions as reusable and maintains the visibility map that enables index-only scans. Bloat is the accumulated dead space when vacuum cannot keep up, caused by long-running transactions pinning snapshots, insufficient autovacuum aggressiveness, or high update rates. Bloat inflates scans, dilutes cache, and widens indexes.
Context and scope
Monitored via dead tuple ratios, table and index physical size versus estimated live bytes, and wraparound age. Tuned through autovacuum thresholds, per-table overrides, and manual VACUUM during bulk delete windows.
Common misconceptions
- Vacuum reclaims disk space to the OS — it marks pages reusable inside the file; only aggressive VACUUM FULL rewrites and shrinks
- Autovacuum on by default means protected — defaults are conservative; update-heavy tables routinely need per-table tuning
- Bloat only wastes disk — every scan reads dead rows, every index stays dilated, and index-only scans degrade to heap checks
Anti-patterns
- Leaving analytics transactions open for hours and pinning dead tuples across every hot table
- Deleting millions of rows and assuming space is returned, then watching inserts reuse fragmented pages poorly
- Running VACUUM FULL routinely on large tables and taking exclusive locks instead of fixing autovacuum tuning
Related concepts
Related examples
Authoritative source
https://www.postgresql.org/docs/current/routine-vacuuming.html
What it is
Updates and deletes in PostgreSQL leave dead versions behind; space is not returned immediately. Vacuum sweeps them, updating the free space map and the visibility map. When the sweep cannot keep pace, dead versions accumulate: the table file grows, scans lengthen, and the buffer cache fills with carcasses.
How it works
Autovacuum triggers per table from a threshold of dead tuples scaled by a fraction of live rows. Long transactions keep snapshots that vacuum must respect, so dead tuples they can still see are untouchable. Aggressive operations VACUUM FULL and pg_repack rebuild the table to physically reclaim space, with exclusive or clever lock trade-offs.
When to intervene
Alert on dead tuple ratio and on wraparound age well before thresholds. Bulk deletes deserve a scheduled vacuum. Update-heavy queues warrant per-table autovacuum scale factors far below default.
Pitfalls
Bloat compounds: bloated indexes scatter across more pages, making each subsequent vacuum pass longer. Wraparound protection forces aggressive vacuum that can starve normal traffic if left until the last moment. Rebuilding bloat takes maintenance windows; preventing it takes monitoring.