PostgreSQLXII · WAL, Checkpoints and Crash RecoveryWAL
Write-ahead logging: the durability contract
What you'll learn
- State the write-ahead rule and what it guarantees
- Explain what full_page_writes protects against and why it costs what it does
- Distinguish the settings that trade latency from those that void the contract
- Choose a wal_level from what the cluster actually needs
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
Part VI established that a commit waits for one fsync. This part is
about what that fsync is writing and why it is enough.
The rule
A change is written to durable WAL before the data page it describes is written to disk.
That is the whole contract, and everything else in this part is a consequence of it.
It works because the WAL is sequential and the data pages are not. One sequential write, synced once, records what happened to a hundred scattered pages. Those pages can then be written whenever it is convenient — by a checkpoint, by the background writer, or by a backend that needs the buffer — because if the server dies first, the WAL contains everything needed to redo them.
$ docker kill --signal=KILL rbpg-stor && docker start rbpg-storLOG: database system was interrupted; last known up at 2026-08-27 21:01:01 UTC
LOG: database system was not properly shut down; automatic recovery in progress
LOG: redo starts at 7/CB35CEA8
LOG: invalid record length at 7/CF4461A0: expected at least 24, got 0
LOG: redo done at 7/CF446178 system usage: CPU: user: 0.09 s, system: 0.03 s, elapsed: 0.12 s
LOG: database system is ready to accept connections
-- SELECT count(*) FROM crashtest;
300000Every committed row survived. 65 MB of WAL was replayed in 0.12 seconds.
What full_page_writes protects against
A PostgreSQL page is 8 KiB. Storage typically writes in 512-byte or 4 KiB units. If the power fails mid-write, the page on disk can be part old and part new — a torn page.
WAL cannot repair that, because a WAL record describes a change to a page and assumes the rest of the page is intact. Applying “set byte 1200 to X” to a half-written page produces something worse.
full_page_writes solves it: the first modification of a page after
each checkpoint writes the entire page to WAL. Recovery can then
restore the whole page rather than patching it.
$ psql -U postgres -c "CHECKPOINT" -c "UPDATE churn_default SET v=v+1 WHERE id BETWEEN 1 AND 20000" # repeated, measuring pg_current_wal_lsn and wal_fpi round 1: WAL 9217 kB full page images: 473
round 2: WAL 6123 kB full page images: 2
round 3: WAL 6138 kB full page images: 250% more WAL in the first round, and 473 full page images against 2.
Three consequences that come up repeatedly:
WAL volume is not a flat function of write volume. It spikes after every checkpoint. A monitoring alert on WAL rate will fire on the spike, not on a problem.
Shorter checkpoint intervals mean more spikes. This is the largest
argument against setting checkpoint_timeout very low, and lesson
XII-04 develops the trade.
Freezing is disproportionately expensive, because it modifies every page. Lesson VIII-05 measured a first freeze generating roughly the table’s own size in WAL, and this is the mechanism.
The settings, and which ones void the contract
| Setting | Trades | Voids the contract? |
|---|---|---|
synchronous_commit = off | Loses recent commits on crash | No — bounded, recoverable |
wal_compression | CPU for WAL volume | No |
wal_buffers | Memory for fewer flushes | No |
commit_delay | Latency for batching | No |
full_page_writes = off | Torn page protection | Yes, on ordinary storage |
fsync = off | The write-ahead rule itself | Yes |
The first four are engineering decisions with understood costs. The last
two are the removal of the property that makes recovery work, and lesson
VI-06 made the distinction: synchronous_commit = off risks a bounded
number of recent transactions, fsync = off risks unbounded corruption.
wal_level
| Level | Records enough for | Cost |
|---|---|---|
minimal | Crash recovery only | Least WAL |
replica (default) | Crash recovery, archiving, physical replication | Moderate |
logical | All of the above, plus logical decoding | Most |
minimal is a genuine optimisation for a cluster that will never be
replicated, archived or backed up physically — a build-time data
transformation, a scratch instance. It also disallows WAL archiving
and replication entirely, so a cluster set to minimal cannot have a
standby added without a restart, and cannot be backed up with
pg_basebackup.
logical is required for logical replication and for change-data-capture
tools. It adds row-level detail to WAL and therefore volume, so setting
it “just in case” is a real, ongoing cost.
Changing wal_level requires a restart. Choose it when the cluster is
built, and revisit it only with a maintenance window.
What to take from this
- The rule: WAL is durable before the data page it describes. That is what makes crash recovery possible.
- Measured: 300,000 committed rows survived a SIGKILL; 65 MB replayed in 0.12 s.
invalid record lengthat the end of redo is normal, not corruption.full_page_writescosts 50% more WAL in the round after a checkpoint. Usewal_compressionrather than turning it off.fsync = offandfull_page_writes = offremove the guarantee. Everything else on the list trades latency or CPU.- An LSN is a byte offset. Subtracting two of them answers most WAL questions.
Cross-course references
- Linux for Production Sysadmins — Part XIII (Disks and block devices) covers whether a flush reaches durable media, which is the assumption the whole contract rests on.
- Ceph & Distributed Storage — Part XII (BlueStore) covers the same write-ahead idea implemented one layer down, and Part II (Storage performance fundamentals) covers measuring the flush latency it costs.
Quiz
Knowledge check · 6 questions
Q1. During an incident the recovery log shows 'invalid record length at 7/CF4461A0: expected at least 24, got 0' followed by 'redo done'. What should be concluded?
Q2. Why does the first UPDATE of a set of pages after a checkpoint generate substantially more WAL than identical updates that follow?
Q3. A cluster is set to wal_level = minimal to reduce WAL volume. What has this precluded?
Q4. Which settings trade latency, CPU or memory without removing the durability guarantee? Select all that apply.
Q5. An LSN such as 7/CF446178 is a byte offset into the WAL stream, so subtracting two LSNs gives the number of bytes between them.
Q6. Explain the write-ahead rule and why it makes a scattered set of page writes recoverable from one sequential log.
Passing score: 75%. Answers are checked in this browser.