Explain PostgreSQL's MVCC, WAL, and VACUUM.
PostgreSQL's MVCC, WAL, and VACUUM form an interlocking system. Understanding how they interact is essential for diagnosing performance problems in production.
**MVCC (Multi-Version Concurrency Control)**
PostgreSQL never overwrites tuples in place. Instead, each `UPDATE` or `DELETE` creates a new version of the tuple and marks the old version as dead. Each tuple has hidden system columns: `xmin` (transaction ID that created it) and `xmax` (transaction ID that deleted/updated it, or 0 if live).
When a query runs, PostgreSQL determines its **snapshot** — the set of transaction IDs that are committed and visible at that point in time. A tuple is visible if `xmin` is committed and in the snapshot, and `xmax` is either 0 or not yet committed. This gives each transaction a consistent view of the data without taking read locks.
Consequences: readers never block writers, writers never block readers. This is PostgreSQL's concurrency strength. Isolation levels (Read Committed, Repeatable Read, Serializable) differ in when snapshots are taken.
**WAL (Write-Ahead Log)**
Before any data page change is written to disk, PostgreSQL writes a record to the WAL. This ensures that on crash, the WAL can be replayed to recover committed transactions that hadn't been flushed to the main data files. `fsync` on WAL commits guarantees durability (the D in ACID).
WAL also enables **streaming replication** — standby servers replay the WAL stream to stay in sync. `wal_level` controls how much information is logged (minimal, replica, logical). Logical replication uses logical decoding of the WAL to replicate data changes at a higher abstraction level.
**VACUUM**
Because MVCC leaves dead tuple versions on disk, PostgreSQL needs a process to reclaim that space. `VACUUM` scans tables for dead tuples (where `xmax` is a committed, no-longer-visible transaction) and marks their space as reusable. `VACUUM FULL` rewrites the table to disk, returning space to the OS — but it takes an exclusive lock.
`autovacuum` runs automatically based on the fraction of dead tuples (`autovacuum_vacuum_scale_factor`). **Transaction ID wraparound** is the critical reason VACUUM must not be disabled: PostgreSQL uses 32-bit transaction IDs. After ~2 billion transactions, IDs wrap around. VACUUM freezes old tuples (setting `xmin` to a special frozen transaction ID) to prevent this. Tables that aren't vacuumed will eventually trigger `autovacuum` in an emergency freeze mode, and if ignored, PostgreSQL will refuse writes to protect data integrity.
`pg_stat_user_tables` tracks `n_dead_tup` and `last_vacuum` — monitor these to catch tables where autovacuum isn't keeping up (common on high-write tables).
Correctly explains MVCC's snapshot mechanism (xmin/xmax), explains WAL as write-ahead logging for durability/recovery, and explains VACUUM as reclaiming dead tuples.
Covers MVCC visibility rules, WAL durability and replication role, VACUUM dead tuple reclamation, transaction ID wraparound risk, autovacuum tuning, and can name monitoring queries.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment