PlatformPostgreSQL 17+Viewpg_stat_checkpointerAccessPublic, no privileges requiredRelatedpg_stat_bgwriter, checkpoint_completion_target
PostgreSQL flushes dirty pages to disk on a schedule. Every five minutes by default, the checkpointer process writes everything that accumulated in shared_buffers since the last checkpoint. This is not optional. WAL replay depends on it. Recovery time depends on it. If you have never looked at checkpoint behavior, you are flying blind through one of the most important maintenance cycles in your database.
In PostgreSQL 17, the checkpoint-related columns were split out of pg_stat_bgwriter into their own view: pg_stat_checkpointer. If you are on PG16 or earlier, the same data lives in pg_stat_bgwriter under slightly different column names. This post covers the PG17 view, with notes on where the columns lived before.
The View
SELECT * FROM pg_stat_checkpointer;
The key columns:
- num_timed — checkpoints triggered by
checkpoint_timeout(the five-minute clock). These are expected and healthy. - num_requested — checkpoints triggered by WAL volume hitting
max_wal_size, or by explicitCHECKPOINTcommands, or by certain recovery events. These are expensive and mean your WAL generation rate is too high for your current config. - write_time — milliseconds spent writing dirty buffers to disk. This is the I/O phase.
- sync_time — milliseconds spent in
fsync()calls. This is the durability phase. High sync_time usually means your storage is slow, your OS filesystem sync is slow, or you have too many dirty buffers accumulating. - buffers_written — total pages written by the checkpointer across all checkpoints.
In PG16 and earlier: num_timed was checkpoints_timed, num_requested was checkpoints_req, write_time was checkpoint_write_time, sync_time was checkpoint_sync_time, and buffers_written was buffers_checkpoint. Same data, renamed.
Scheduled vs Requested: The First Diagnostic Signal
The ratio of timed to requested checkpoints tells you whether WAL generation is within bounds.
SELECT
num_timed,
num_requested,
round(100.0 * num_requested / nullif(num_timed + num_requested, 0), 1) AS pct_requested
FROM pg_stat_checkpointer;
A healthy cluster has almost all timed checkpoints. If more than ten percent are requested, your WAL volume is exceeding max_wal_size faster than checkpoints can keep up. The fix is to either increase max_wal_size (allowing more WAL to accumulate before forcing a checkpoint) or tune checkpoint_completion_target to spread checkpoint I/O more evenly.
Requested checkpoints are expensive because they are not spread over time — they happen as fast as the disk allows, which creates I/O spikes visible as latency bursts in your application.
Write Time vs Sync Time
These two numbers together diagnose where checkpoint I/O time is going.
High write_time, normal sync_time: You have a lot of dirty pages to write. This is usually a volume problem — too many writes in your workload, too large a shared_buffers, or too aggressive a checkpoint_completion_target. The checkpointer is spending most of its time writing pages, which is the expected phase.
Normal write_time, high sync_time: Your storage is slow to acknowledge durability. This shows up with underpowered storage, overly aggressive write barriers in the filesystem, or ext4 with barriers enabled on spinning disk. On SSDs, high sync_time usually means the drive's internal write cache is not battery-backed and it is being cautious.
Both high: Volume and durability are both bottlenecked. This is the worst case and usually means the checkpoint_completion_target is too low, forcing all I/O into a short window, combined with slow storage.
SELECT
round(write_time / nullif(num_timed + num_requested, 0)) AS avg_write_ms_per_checkpoint,
round(sync_time / nullif(num_timed + num_requested, 0)) AS avg_sync_ms_per_checkpoint
FROM pg_stat_checkpointer;
The Background Writer Connection
pg_stat_bgwriter still exists in PG17 and tracks the background writer process separately from the checkpointer. The background writer pre-cleans pages between checkpoints so the checkpointer has less work to do at checkpoint time.
The key column to watch there is maxwritten_clean — the number of times the bgwriter stopped cleaning because it hit bgwriter_lru_maxpages. If this is nonzero and rising, the bgwriter is hitting its per-round page limit before it can clean enough dirty buffers. The fix is to increase bgwriter_lru_maxpages (default 100).
SELECT
buffers_clean,
maxwritten_clean,
buffers_backend,
buffers_backend_fsync
FROM pg_stat_bgwriter;
buffers_backend is more alarming: these are pages that backends flushed directly because no cleaner process got there first. When this is high, your cleaning processes (checkpointer and bgwriter) cannot keep up with write volume and your query processes are doing their own I/O on the side. This adds latency to ordinary queries.
Interpreting buffers_written
The buffers_written column in pg_stat_checkpointer counts total pages written across all checkpoints since the stats were last reset. Divide by elapsed checkpoints to get pages per checkpoint. Multiply by your block size (usually 8192 bytes) to get data volume per checkpoint.
SELECT
buffers_written,
num_timed + num_requested AS total_checkpoints,
round(buffers_written::numeric / nullif(num_timed + num_requested, 0), 0) AS avg_pages_per_checkpoint,
round(buffers_written * 8192.0 / nullif(num_timed + num_requested, 0) / (1024*1024), 1) AS avg_mb_per_checkpoint
FROM pg_stat_checkpointer;
If avg_mb_per_checkpoint is climbing toward your shared_buffers size, your working set is almost entirely dirty at checkpoint time. This is a strong signal to tune checkpoint_completion_target upward (toward 0.9) and spread the writes over the full checkpoint interval rather than bursting them at the end.
Stats Reset Awareness
All numbers in pg_stat_checkpointer accumulate since the last stats reset. The reset timestamp is available in pg_stat_checkpointer.stats_reset. A freshly restarted database or a manual pg_stat_reset() call will show misleadingly small numbers.
SELECT stats_reset FROM pg_stat_checkpointer;
When diagnosing a problem, always check how long the stats have been accumulating before interpreting ratios.
What the View Does Not Show
pg_stat_checkpointer does not show you which tables or indexes are being written. It does not correlate checkpoint I/O to specific query workloads. It does not show you whether checkpoints are finishing on time relative to the checkpoint interval (you need to look at PostgreSQL logs with log_checkpoints = on for that). And it does not show you WAL archiving lag, which is a separate problem tracked in pg_stat_archiver.
Enable checkpoint logging to get timing data that pg_stat_checkpointer cannot give you:
-- In postgresql.conf
log_checkpoints = on
With this enabled, every checkpoint emits a log line showing duration, buffers written, and whether it was timed or requested. These log lines, combined with the aggregate stats in the view, give you the full checkpoint picture.
Building something in public? builds.anethoth.com is where you can post your build dossier — shipping milestones, known limitations, and what you're looking for.