53 VACUUM and ANALYZE
PostgreSQL uses multi-version concurrency control (MVCC): an UPDATE does not overwrite a row, it writes a new version and marks the old one dead. A DELETE only marks rows dead — they stay on disk. VACUUM reclaims that space and prevents transaction-ID wraparound. ANALYZE updates the planner’s statistics so it makes good decisions. PostgreSQL runs both automatically via autovacuum, but understanding what they do — and when to nudge them — is part of being a competent operator.
What you’ll learn
- Why MVCC produces dead rows and what VACUUM does about them
- The difference between
VACUUM,VACUUM FULL, andVACUUM ANALYZE - What
ANALYZEactually computes - Reading
pg_stat_user_tablesto see autovacuum activity - The transaction-ID-wraparound horizon and why you cannot ignore it
Example
MVCC is what lets PostgreSQL achieve high concurrency: writers do not block readers and vice versa, because each transaction sees a consistent snapshot. The cost is dead rows. VACUUM walks the table, marks dead rows’ space as free for reuse, and updates the visibility map (a per-page bitmap that lets index-only scans avoid heap visibility checks on all-visible pages).
Plain VACUUM normally makes space reusable within the table. It can also truncate empty pages at the end of the table and return that space to the operating system. VACUUM FULL rewrites the entire table in a new file, releasing the old one — it reclaims space but takes an ACCESS EXCLUSIVE lock and is slow. Use it rarely; the routine maintenance is autovacuum.
ANALYZE samples the table to update pg_statistic. The planner uses these statistics — number of distinct values, most common values, histograms — to estimate row counts and pick plans. Stale statistics produce bad plans.
PostgreSQL’s autovacuum daemon runs VACUUM and ANALYZE automatically based on per-table thresholds (autovacuum_vacuum_scale_factor, autovacuum_analyze_scale_factor). Autovacuum handles routine maintenance, but its progress and thresholds need monitoring, especially after bulk changes and on large or frequently updated tables.
Run the example
To run:
Common pitfalls
- Disabling autovacuum to “save resources” — almost always a mistake. The dead rows pile up, queries slow down, and the eventual catch-up is much worse than steady-state autovacuum overhead.
- Running
VACUUM FULLon a busy table — it blocks all reads and writes for the duration. Usepg_repack(extension) for online compaction. - Forgetting to
ANALYZEafter a bulk import — the planner has no statistics for the new rows and may pick terrible plans until autovacuum catches up. RunANALYZE schema.table;manually after big imports. - Long-running transactions block autovacuum from cleaning up rows visible to them. A forgotten
BEGIN;in psql can prevent vacuuming across the whole database — watchpg_stat_activityforstate = 'idle in transaction'.
pg_stat_user_tables.n_dead_tup is the canonical “do I need to vacuum?” metric. A high ratio of n_dead_tup / n_live_tup on a hot table is a sign autovacuum cannot keep up — tune the per-table thresholds with ALTER TABLE t SET (autovacuum_vacuum_scale_factor = 0.02);.
Run a bunch of UPDATEs on a table, then look at n_dead_tup in pg_stat_user_tables. Run VACUUM and re-check. Then run EXPLAIN ANALYZE on a query before and after ANALYZE to see if the estimates changed.
Downloads
SQL file: vacuum-and-analyze.sql · All lesson files · Setup guide