Monitoring

PostgreSQL "Checkpoints Are Occurring Too Frequently": What It Means and How to Fix It

The log line looks harmless, but frequent checkpoints mean extra disk writes, a WAL stream bloated with full page images and slower replicas. Here is how to read it and size max_wal_size properly.

· 11 min read · AG Data Team

If your PostgreSQL log says "checkpoints are occurring too frequently", the server is telling you that WAL fills max_wal_size faster than checkpoint_timeout expires. A few such lines during a big batch job are normal. Seeing them every few minutes under ordinary load means the server is doing far more checkpoint work than it needs to, and on a large, busy database that costs real I/O, WAL volume and replica bandwidth.

What the message looks like

LOG:  checkpoints are occurring too frequently (12 seconds apart)
HINT:  Consider increasing the configuration parameter "max_wal_size".

It is a LOG message, not an error, so nothing fails. In the PostgreSQL source the checkpointer writes it when a checkpoint was requested because of WAL volume and it started less than checkpoint_warning seconds (default 30s) after the previous one. Setting checkpoint_warning = 0 disables it, which hides the symptom without changing anything.

Why checkpoints happen

The WAL configuration chapter describes two automatic triggers, whichever comes first:

Checkpoints can also be requested explicitly, for example with the CHECKPOINT command or at the start of a base backup. On a quiet server the timer wins and checkpoints are spread evenly. On a write-heavy one 1 GB of WAL can be produced in seconds, so the size trigger fires again and again and the warning appears.

Why frequent checkpoints hurt

The trade-off is crash recovery: a longer distance between checkpoints means more WAL to replay after a crash. That is why the fix is "large enough", not "as large as possible".

Confirm it with statistics and logs

Check how many checkpoints were requested rather than timed. The columns moved in PostgreSQL 17 from pg_stat_bgwriter to pg_stat_checkpointer:

-- PostgreSQL 17 and newer
SELECT num_timed, num_requested,
       round(100.0 * num_requested / nullif(num_timed + num_requested, 0), 1) AS pct_requested,
       write_time, sync_time, buffers_written, stats_reset
FROM pg_stat_checkpointer;

-- PostgreSQL 16 and older
SELECT checkpoints_timed, checkpoints_req, stats_reset
FROM pg_stat_bgwriter;

If requested checkpoints clearly dominate since stats_reset, max_wal_size is too small for your write rate. Keep in mind that the requested counter also includes manual CHECKPOINT commands and base backups, so a nightly backup job adds a few on its own.

Since PostgreSQL 15 log_checkpoints is on by default. Each checkpoint logs a checkpoint starting: line with its cause (wal, time, immediate, force...) and a checkpoint complete: line with buffers written, write and sync times, and distance and estimate in kB. A run of checkpoint starting: wal lines tells you the same story as the warning, with the numbers attached.

How to size max_wal_size

Do not guess. Measure how much WAL the server writes in one checkpoint_timeout at peak load:

-- 1. note the current WAL position at peak load
SELECT pg_current_wal_lsn();            -- e.g. 3A/1F000000

-- 2. after exactly checkpoint_timeout (5 minutes by default), measure the distance
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '3A/1F000000'));

-- share of full page images in WAL (PostgreSQL 14+)
SELECT wal_records, wal_fpi, pg_size_pretty(wal_bytes) FROM pg_stat_wal;

Then set max_wal_size comfortably above that figure, so that at peak most checkpoints are triggered by time and only unusual spikes hit the size limit. Both max_wal_size and checkpoint_timeout are reloadable settings, so no restart is needed:

ALTER SYSTEM SET max_wal_size = '16GB';
SELECT pg_reload_conf();
SHOW max_wal_size;

The 16GB here is an example, not a recommendation. Watch pg_stat_checkpointer and the checkpoint log lines for a full business cycle after the change. Some teams also raise checkpoint_timeout to 15 or 30 minutes to cut full page images further; that lengthens crash recovery, so decide it together with your recovery time objective.

max_wal_size is a soft limit. The documentation is explicit that WAL can grow beyond it under heavy load, a failing archive_command, a high wal_keep_size, or a lagging replication slot. Before raising it, make sure pg_wal has room for the new value plus generous headroom, or you trade a log warning for the outage described in PostgreSQL disk full: pg_wal.

What changes on large databases

What not to do

Key takeaways

PostgreSQL Checkpoints WAL Performance
Work with us

Checkpoints hurting a busy PostgreSQL server?

We tune checkpoints and WAL on large PostgreSQL databases: measuring the real WAL rate, sizing max_wal_size and pg_wal, and keeping replicas and archiving in step.

Get in touch More from the blog