PostgreSQLXII · WAL, Checkpoints and Crash RecoveryWAL
Checkpoints: what they do and what they cost
What you'll learn
- Explain what a checkpoint does and what it makes possible
- Read the checkpoint log line and pg_stat_checkpointer
- Diagnose checkpoints that are too frequent from evidence
- Balance checkpoint frequency against recovery time deliberately
Prerequisites
Practice
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
A checkpoint writes every dirty buffer to disk and records the WAL position at which it started. Recovery can then begin from that position instead of from the beginning of time, and every WAL segment before it can be recycled.
Both of those consequences matter, and they pull in opposite directions.
What triggers one
| Trigger | Setting | Reported as |
|---|---|---|
| Time elapsed | checkpoint_timeout (300 s) | num_timed |
| WAL volume | max_wal_size (1 GB) | num_requested |
CHECKPOINT command | — | num_requested |
| Clean shutdown | — | num_requested |
| End of recovery | — | num_requested |
The ratio that diagnoses the cluster
$ psql -U postgres -x -c "SELECT * FROM pg_stat_checkpointer"num_timed | 26
num_requested | 54
num_done | 62
restartpoints_timed | 0
restartpoints_req | 0
restartpoints_done | 0
write_time | 2205951
sync_time | 9173
buffers_written | 210247
slru_written | 12654 requested against 26 timed. More than twice as many checkpoints were forced by WAL volume as occurred on schedule.
A timed checkpoint is spread over checkpoint_completion_target × checkpoint_timeout — 270 seconds on defaults — so its writes are gentle.
A requested one arrives when max_wal_size is reached, which on a busy
cluster is far sooner, and the spreading window shrinks with it.
The consequences of frequent forced checkpoints:
More full page writes. Lesson XII-01 measured 50% more WAL in the round after a checkpoint. Doubling the checkpoint rate applies that penalty twice as often, which generates more WAL, which triggers checkpoints sooner. It is a genuine feedback loop.
Less write coalescing. A page dirtied ten times between checkpoints is written once. A page dirtied ten times across five checkpoints is written five times.
Sharper I/O spikes, because each checkpoint has less time to spread its writes.
Reading the log line
ALTER SYSTEM SET log_checkpoints = on; -- on by default since 15
$ grep 'checkpoint complete' /var/log/postgresql/postgresql.logLOG: checkpoint complete: wrote 2790 buffers (17.0%), wrote 3 SLRU buffers;
0 WAL file(s) added, 0 removed, 33 recycled;
write=269.303 s, sync=0.011 s, total=269.392 s;
sync files=42, longest=0.005 s, average=0.001 s;
distance=536114 kB, estimate=536114 kB;
lsn=6/D6CE9F60, redo lsn=6/C8031640
LOG: checkpoint complete: wrote 11585 buffers (70.7%), wrote 1 SLRU buffers;
0 WAL file(s) added, 0 removed, 25 recycled;
write=46.400 s, sync=0.020 s, total=46.476 s;
distance=410246 kB, estimate=577182 kB| Field | Reading |
|---|---|
wrote N buffers (P%) | How much of the pool was dirty. Above ~50% suggests the background writer is not keeping up |
write= | Time spreading the writes. Should approach completion_target × timeout |
sync= | Time in fsync. This is the number that indicates storage trouble |
distance= | WAL generated since the last checkpoint |
estimate= | Smoothed prediction used to size recycling |
recycled / removed | Segments renamed forward for reuse, and deleted |
redo lsn= | Where recovery would start from |
write=269.303 s against a 300-second timeout is 0.898 — exactly
checkpoint_completion_target. The writes were deliberately spread, and
this is what a healthy checkpoint looks like.
sync=0.011 s against write=269.303 s. Almost all of a healthy
checkpoint is spreading, not syncing. When sync_time becomes a
significant fraction, storage is the constraint and no checkpoint
setting will fix it.
The second line is the contrast: write=46.400 s and 70.7% of the pool
dirty. That is a forced checkpoint, arriving early with a large backlog
and less time to place it.
PostgreSQL 18 changed the log line format slightly, reporting shared buffers and SLRU buffers separately. From the release notes:
Add column
pg_stat_checkpointer.slru_writtento report SLRU buffers written … Also, modify the checkpoint server log message to report separate shared buffer and SLRU buffer values.
Alerting or log parsing written against the pre-18 format needs adjusting.
The trade, stated
| Frequent checkpoints | Infrequent checkpoints | |
|---|---|---|
| WAL volume | Higher (full page writes) | Lower |
| I/O smoothness | Spikier | Smoother |
| Write coalescing | Worse | Better |
| Recovery time | Shorter | Longer |
pg_wal size | Smaller | Larger |
Everything except recovery time favours infrequent checkpoints. The usual settings that follow:
ALTER SYSTEM SET checkpoint_timeout = '15min';
ALTER SYSTEM SET max_wal_size = '16GB';
ALTER SYSTEM SET checkpoint_completion_target = '0.9'; -- default since 14
ALTER SYSTEM SET min_wal_size = '2GB';
What to take from this
num_requestedexceedingnum_timedmeansmax_wal_sizeis too small. Measured here at 54 against 26.- Size
max_wal_sizeso a fullcheckpoint_timeoutof WAL fits, from the measured generation rate. - The cost of infrequent checkpoints is recovery time. Calculate it and agree it as a service level.
write=should approachcompletion_target × timeout.sync=becoming significant means storage is the constraint.CHECKPOINTby hand is unspread and heavy, and does not release WAL held by a slot or the archiver.- On a standby, recovery distance is set by the primary’s checkpoint frequency.
Cross-course references
- Linux for Production Sysadmins — Part XLI (Storage Performance) covers measuring the write burst a checkpoint produces, and Part XL (Memory Performance) covers the page cache it flushes through.
- Observability for Production Sysadmins — Part XIII (Rates and
counters) covers the timed-versus-requested checkpoint ratio, which is
the number that says whether
max_wal_sizeis sized correctly.
Quiz
Knowledge check · 6 questions
Q1. pg_stat_checkpointer shows num_timed 26 and num_requested 54. What does this indicate?
Q2. A checkpoint log line reports write=269.303 s and sync=0.011 s on a cluster with checkpoint_timeout of 300 s. How should this be read?
Q3. A promoted standby took far longer to become available than the primary's own crash recovery would have. Its checkpoint_timeout was set to 2 minutes. Why did that not help?
Q4. Which are consequences of making checkpoints less frequent? Select all that apply.
Q5. Issuing CHECKPOINT by hand is an effective response to a pg_wal directory that is filling up.
Q6. How would you choose max_wal_size for a cluster, and what must be agreed before you set it?
Passing score: 75%. Answers are checked in this browser.