A checkpoint establishes a starting point for PostgreSQL crash recovery. It synchronizes the data-file changes required for that checkpoint and records the corresponding recovery information. WAL must reach durable storage before dependent data pages are written. This ordering allows committed work to survive without forcing every modified table page to disk at each commit, while recovery can replay later changes when necessary.
When it breaks
A checkpoint is not a transaction commit. Data pages can contain versions written by transactions that remain uncommitted; visibility and transaction status still determine which rows readers can see. Checkpoint completion does not make those writes visible or turn a raw copy of changing database files into a valid base backup. Concurrent activity can also dirty pages again, so completion does not mean the buffer pool stays completely clean.
WAL retention has other constraints. max_wal_size is a soft target associated with automatic checkpoint scheduling, not a hard disk-usage limit. Archive failure and replication-slot retention can keep WAL beyond it. min_wal_size concerns recycling a reserve of WAL files, not a second threshold that forces a checkpoint. Misreading those settings can hide a growing archive backlog behind apparently reasonable checkpoint intervals.
Check it quickly
Use two fresh psql -X -v ON_ERROR_STOP=1 sessions, A and B, on one quiet, disposable PostgreSQL 18 instance. Use an administrative role for the explicit checkpoint and control-file information. Keep the normal fsync and synchronous-commit settings enabled. In B, create a counter and save the current checkpoint location:
SHOW fsync;
SHOW synchronous_commit;
CREATE SCHEMA checkpoint_demo;
CREATE TABLE checkpoint_demo.items (id integer PRIMARY KEY,n integer);
INSERT INTO checkpoint_demo.items VALUES (1,10);
CREATE TABLE checkpoint_demo.baseline AS
SELECT checkpoint_lsn FROM pg_control_checkpoint();
Both displayed settings should be on. The initial committed counter is ten. In A, change it inside a transaction that remains open:
BEGIN;
UPDATE checkpoint_demo.items SET n=99 WHERE id=1 RETURNING n;
A receives ninety-nine from its own update. Do not commit it. In B, request a checkpoint and inspect what an ordinary reader can see:
CHECKPOINT;
SELECT c.checkpoint_lsn>b.checkpoint_lsn AS checkpoint_advanced,
(SELECT n FROM checkpoint_demo.items WHERE id=1) AS visible_value
FROM checkpoint_demo.baseline AS b CROSS JOIN pg_control_checkpoint() AS c;
The checkpoint location advances, but B still reads ten. The checkpoint has completed while A’s transaction remains undecided. This separates maintenance progress from business commit status. In A, verify its own version is still visible, then discard it:
SELECT n FROM checkpoint_demo.items WHERE id=1;
ROLLBACK;
A reads ninety-nine before rollback. B can now confirm the original ten and perform a normal committed update, with checkpoint locations captured immediately around that operation:
SELECT n FROM checkpoint_demo.items WHERE id=1;
UPDATE checkpoint_demo.baseline
SET checkpoint_lsn=(SELECT checkpoint_lsn FROM pg_control_checkpoint());
UPDATE checkpoint_demo.items SET n=11 WHERE id=1;
SELECT c.checkpoint_lsn=b.checkpoint_lsn AS no_new_checkpoint,
(SELECT n FROM checkpoint_demo.items WHERE id=1) AS committed_value
FROM checkpoint_demo.baseline AS b CROSS JOIN pg_control_checkpoint() AS c;
DO $$ BEGIN
IF (SELECT n FROM checkpoint_demo.items WHERE id=1) IS DISTINCT FROM 11 THEN
RAISE EXCEPTION 'checkpoint/transaction boundary result mismatch';
END IF;
END $$;
DROP SCHEMA checkpoint_demo CASCADE;
The results are ten, followed by a true unchanged-checkpoint comparison and committed value eleven in the tested short schedule. The commit succeeded without requesting another checkpoint. An independently scheduled checkpoint can change that comparison in a busy instance; it does not change the meaning of commit. The assertion checks the final row value, and the fixture is removed afterward.
This drill verifies transaction visibility and checkpoint boundaries. It does not measure recovery duration or simulate a storage device losing acknowledged writes. The durability contract still depends on enabled durability settings and storage that honors synchronization requests. Use separate crash-recovery rehearsals to validate operational recovery time against the workload you actually retain.
Tune against recovery and foreground work
Longer checkpoint intervals can reduce repeated flushing and, with full-page writes enabled, avoid some repeated full-page-image logging. They can also leave more work to replay after a crash. Frequent checkpoints can increase write traffic and latency. Fast local storage alone is not evidence that a larger interval meets a particular recovery objective.
checkpoint_completion_target spreads ordinary checkpoint work across a target fraction of the interval; its usual 0.9 value is a pacing target, not a guarantee against every latency spike. Measure checkpoint duration, requested versus timed checkpoints, WAL growth, and foreground latency together. In PostgreSQL 18, checkpoint counters and write/sync times are in pg_stat_checkpointer; the background-writer view describes a different component. Investigate repeated WAL-driven requests and retention causes before changing limits, and compare recovery tests before crediting a tuning change as successful.
Sources
- PostgreSQL 18 WAL introduction, write-ahead ordering and commit durability without immediate data-file flushing, checked 2026-09-11.
- PostgreSQL 18 CHECKPOINT, explicit checkpoint behavior and required privileges, checked 2026-09-10.
- PostgreSQL 18 WAL configuration, checkpoint scheduling, pacing, and WAL size semantics, checked 2026-09-10.
- PostgreSQL 18 cumulative statistics, the dedicated checkpointer statistics view, checked 2026-09-10.