Build Your Own PostgreSQL
Vacuum and the Cost of History
What You Will Learn
- Understand history has weight in a PostgreSQL-like database
- Understand cleanup has to prove safety in a PostgreSQL-like database
- Understand cleanup loop in a PostgreSQL-like database
- Understand immediate erasure would break readers in a PostgreSQL-like database
History has weight
MVCC lets the database keep old tuple versions so readers can see a stable past. Vacuum is the process that decides when the past can be physically removed. Without vacuum, every update and delete would leave permanent debris: dead heap tuples, stale index entries, and eventually transaction identifiers old enough to become dangerous. Vacuum is not casual housekeeping. It is the mechanism that keeps versioned storage from filling up with its own history.
A tuple becomes dead when no possible snapshot can still see it. Proving that requires knowledge of active transactions. If a long-running transaction began before a delete committed, it may still need the old version. Vacuum must respect that reader even if every newer transaction would ignore the tuple. The process array, transaction horizons, and visibility rules all feed into the cleanup decision.
Cleanup has to prove safety
When vacuum scans a heap page, it identifies dead tuple versions, marks their space reusable, and may update page-level metadata. It also coordinates with indexes. An index may contain entries pointing to dead heap tuples, so those entries need removal or marking depending on the index method. Vacuum is more than a heap cleaner. It reconciles several structures in one pass.
Cleanup loop
Immediate erasure would break readers
Immediate cleanup sounds attractive until a concurrent reader still needs the old version. If the database removed old versions as soon as an update or delete committed, the writer would have to wait for every possible old reader, which destroys much of MVCC's advantage. Vacuum decouples logical deletion from physical reclamation. Writers move on, and cleanup happens once the system proves it is safe.
Lazy cleanup fails in the other direction. Dead tuples consume space, make scans slower, and leave indexes full of entries that lead nowhere useful. There is also the problem of transaction identifiers wrapping around. Since tuple visibility depends on comparing transaction ages, the system must prevent old IDs from being mistaken for new ones. Freezing marks old tuples in a way that no longer depends on their original transaction ID. Vacuum is the path to that safety.
The background worker
Autovacuum exists because humans cannot reliably schedule this by hand. Workloads vary by table, hour, and deployment. Some tables are append-only and need little cleanup. Others churn constantly. The database monitors update and delete activity and launches vacuum workers when thresholds are crossed. The goal is to clean aggressively enough to control bloat and wraparound risk without taking too much I/O and CPU from foreground queries.
Bloat is what history leaves behind in the open. A table can have far more allocated pages than live rows require because dead space is scattered or not yet reusable. Indexes can bloat when deleted entries leave pages sparse but not easily merged. Vacuum can reuse space within existing files, but it often does not shrink files back to the operating system. Rewriting or rebuilding may be needed for severe cases. The architectural point is that deletion in an MVCC database is a process rather than a single moment.
Visibility maps help vacuum and index-only scans. A page marked all-visible contains tuples visible to every transaction, so some operations can skip per-tuple checks. A page marked all-frozen has transaction metadata old enough to be safe from wraparound concerns. These maps turn repeated global questions into page-level facts. They must be maintained carefully because a wrong all-visible mark could let a query return data it should not see.
Vacuum also has to be careful around locks. It should not block ordinary reads and writes for long periods. It may skip pages or relations when stronger locks are unavailable, then return later. Some forms of vacuum are lightweight and concurrent. Others, such as full rewrites, require stronger locks and cause more disruption. The system offers multiple cleanup intensities because not every mess justifies that level of disruption.
The best vacuum is the one users rarely notice. It runs in the background, advances horizons, clears dead entries, freezes old tuples, and leaves enough free space for future changes. When it falls behind, the whole database starts to feel heavier. Queries touch more pages. Index scans recheck more dead entries. Storage grows. Emergency wraparound protection can force unpleasant pauses.
Into recovery
Vacuum shows that the cost of fast concurrent history is paid later. The next related question is what happens when history is interrupted by a crash. If the server stops midstream, the database needs a known place to begin replay and a way to rebuild pages to a coherent state. That is the work of checkpoints and crash recovery.
Cleanup keeps the live system healthy. Recovery rebuilds the interrupted system into a state you can trust.