Autovacuum, Bloat and Freeze
Understand how PostgreSQL cleans up dead row versions, what happens when it can't, and how to tune autovacuum for high-churn tables — all hands-on with a real stand.
Every UPDATE in PostgreSQL leaves a dead row behind. Autovacuum cleans them up, but only if the thresholds let it, no long transaction is in the way, and the table isn't already bloated beyond recovery. This course walks the whole lifecycle hands-on: we stand up a real database, churn half a million rows, watch autovacuum kick in after we lower its threshold with a reloption, block it with a long-running transaction, then measure bloat directly from pg_stat_user_tables and pg_relation_size. We finish with transaction ID wraparound — the freeze counter every production DBA watches — and build a single monitoring query that surfaces all of it at once.
What you'll build
- Read the autovacuum threshold formula and predict when cleanup runs
- Set per-table reloptions to tune autovacuum for high-churn tables
- Identify long transactions that block dead-tuple removal via pg_stat_activity
- Measure table bloat from pg_stat_user_tables and pg_relation_size
- Read age(relfrozenxid) and understand the wraparound risk
- Run VACUUM FREEZE and observe the freeze counter drop to zero
- Use a single monitoring query as a production autovacuum health check
Contents
- A database for watching autovacuum
- Start it
- Connect with psql
- The events table — the churn engine
- The accounts table — a quieter neighbour
- Create the tables
- Seed the events table, deterministically
- Seed the accounts table
- Load the data
- Confirm what we are sitting on
- Refresh the statistics baseline
- The threshold formula in one query
- Read the thresholds on the live database
- The churn script — dead tuples on demand
- Create the first batch of dead tuples
- Read the dead-tuple counter
- Lower the threshold with a reloption
- Apply the reloption to the live table
- Confirm the reloption is stored
- Run churn again to push clearly past the new threshold
- Wait for autovacuum to run
- A script that holds a transaction open
- Open the long transaction in the background
- Create new dead tuples while the transaction is open
- VACUUM can see the dead tuples but cannot remove them
- Find the culprit in pg_stat_activity
- Terminate the blocking session
- VACUUM can now remove the dead rows
- Measuring table bloat directly
- Accumulate visible bloat
- Read the bloat after churning
- Index bloat — a separate problem
- The freeze age — the wraparound clock
- Current freeze age on a fresh database
- Reset the freeze counter with VACUUM FREEZE
- Freeze age dropped to zero
- The production monitoring query
- Run the dashboard on the current state
- The full picture: long transaction, churn, and the dashboard