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:
- Time: every
checkpoint_timeout, default 5 minutes (range 30 seconds to one day). - WAL volume: when
max_wal_size(default 1 GB) is about to be exceeded.
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
- More WAL, not less. With
full_page_writes = on(the default and the safe setting) the first change to each data page after a checkpoint writes the whole page into WAL. The documentation notes that shorter checkpoint intervals increase WAL volume and disk I/O. Hot pages get written as full images again after every checkpoint. - Bursty writes.
checkpoint_completion_target(default 0.9) spreads dirty-page writes over the interval. When the interval shrinks to seconds, there is little room to spread anything, and writes come in bursts that compete with queries. - Heavier replication and archiving. Every extra full page image has to be shipped to standbys and the WAL archive, and stored with your backups.
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.
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
- The default 1 GB is tiny for TB-scale write loads. A busy OLTP database or a nightly ETL can write that in well under a minute, so most checkpoints become size-triggered. Expect to need a value measured in tens of gigabytes, based on your own WAL rate.
- Size pg_wal for the peak, not the average. On large servers pg_wal often lives on its own volume. It must hold
max_wal_size, archiving backlog and slot retention during the worst hour, not a normal one. - Crash recovery gets longer. More WAL between checkpoints means more to replay after a crash or failover to a standby that has to catch up. Measure replay speed on a full-size copy before choosing very large values.
- Standbys follow the primary. Replicas perform restartpoints, which can only happen at checkpoint records from the primary. The documentation warns that
max_wal_sizeis often exceeded during recovery by up to one checkpoint cycle of WAL, so give standby disks the same headroom. - Large shared_buffers means heavier checkpoints. With hundreds of gigabytes of buffers, each checkpoint may flush a lot of dirty data. Keep
checkpoint_completion_targetat 0.9 so it is spread out, and comparewrite_timeandsync_timeto see whether storage keeps up. - Full page images dominate WAL. Compare
wal_fpiwithwal_recordsinpg_stat_wal. If full page images are a big share, longer checkpoint intervals andwal_compression(lz4orzstdsince PostgreSQL 15, if the server was built with them) can shrink WAL a lot, which also helps replicas and archive storage. Compression costs CPU, so test it under real load. - Bulk loads need their own plan. Restores and big
COPYjobs produce warnings even on well-tuned servers. For one-off loads, temporarily raisingmax_wal_sizeis common, as described in pg_dump and restore for large databases.
What not to do
- Do not turn off
full_page_writesto reduce WAL. The documentation warns this can lead to unrecoverable or silent data corruption after a crash. - Do not just set
checkpoint_warning = 0. The checkpoints stay just as frequent. - Do not set
checkpoint_completion_targetto 1.0. The documentation recommends no higher than 0.9.
Key takeaways
- The warning means WAL fills
max_wal_sizefaster thancheckpoint_timeoutexpires. - Frequent checkpoints increase full page images, WAL volume, I/O bursts and replica traffic.
- Check requested vs timed checkpoints in
pg_stat_checkpointer(17+) orpg_stat_bgwriter(16 and older). - Size
max_wal_sizefrom the measured WAL rate at peak; it reloads without a restart. - On large databases plan pg_wal disk space, crash recovery time and standby headroom together with the new value.