Skip to content
dbexplore

Glossary: Write-ahead log and checkpoints

Checkpoint: timed versus requested

Also called: scheduled checkpoint, forced checkpoint.

Definition, revised in place. Last updated .

A checkpoint writes every dirty buffer to disk so that recovery can start from a known point. A timed checkpoint is one the schedule asked for: checkpoint_timeout elapsed, and the server began one on purpose, spreading the writes across the fraction of the interval set by checkpoint_completion_target. A requested checkpoint is one something else forced, almost always because the write-ahead log has grown past max_wal_size since the last one. The server counts the two separately because they mean different things.

Why the ratio is worth a panel

A timed checkpoint is paced. The server knows how long it has and dribbles the writes out over most of the interval, so the I/O is spread and the effect on query latency is small. A requested checkpoint is not paced in the same way: the log filled up, the server has to catch up, and the write burst lands on whatever else the disk was doing.

So the useful signal is not how many checkpoints happened, it is what share of them nobody asked for. A cluster sitting mostly on timed checkpoints is a cluster whose write volume fits inside the budget it was given. A cluster where requested checkpoints dominate is one whose log size is too small for its write rate, and the usual answer is to raise max_wal_size rather than to lengthen the timeout. Raising the timeout on a cluster that is already checkpointing on log pressure changes nothing, because the timeout is not what is triggering them.

The exception worth knowing: a CHECKPOINT command, a base backup and a shutdown all count as requested. A daily backup window that adds one requested checkpoint a day is not a symptom of anything.

Reading the two counters

From PostgreSQL 17 the counters live in the checkpointer’s own view, under names that changed with the move.

SELECT num_timed,
       num_requested,
       round(100.0 * num_requested / nullif(num_timed + num_requested, 0), 1) AS percent_requested
FROM pg_stat_checkpointer;
 num_timed | num_requested | percent_requested 
-----------+---------------+-------------------
         0 |             2 |             100.0
(1 row)

Every checkpoint on that server was requested, because it had been running for minutes rather than for the interval a timed checkpoint waits out. A freshly restarted cluster reads this way for an hour and it means nothing.

On 16 and earlier the same two counters are checkpoints_timed and checkpoints_req in the background writer view, and the rename is not a rename you can ignore: the old column names are gone rather than aliased. That change, what it does to an exporter, and the version-conditional query it needs are on the page for the checkpointer view in PostgreSQL 17.

Both counters are cumulative since the last statistics reset, so a single reading tells you almost nothing. Take the ratio over a window, not over the lifetime of the cluster.

Where to take it next

Tuning the log budget is a write-path decision and it interacts with everything else on that path, including how much of the log is full-page images and how the newer majors account for the I/O. The guide on what PostgreSQL 18 changed in monitoring covers where the write-ahead log counters moved and what that does to a panel built on the old ones. If checkpoints are being requested because vacuum is rewriting more of the heap than it should, the cause is upstream of the checkpointer and the xmin horizon is the thing to check first.

Where the size trigger actually sits on your settings is not max_wal_size, and the checkpoint pressure calculator prints the real distance alongside the crash recovery time that same distance implies, which is the half of the trade that usually goes unsaid.

Put every Postgres you run on autopilot.

We onboard teams in small batches. Tell us about your fleet and we will reach out when a seat opens. One email, no drip campaign.