PostgreSQL: WAL Configuration (docs ch. 28.5)
The chapter that explains the dials, read for the four things write-ahead-logging listed as
undefined in this corpus: checkpoints, full-page writes, the durability levels between "committed"
and "safely elsewhere", and WAL write amplification. First-party engineering documentation for the
engine's own mechanism, so T1 by the artifact-facts branch of the tier rule. Graded 30d: this
is a living reference edited in place, versioned with the engine.
Checkpoints, and why they are a tuning problem at all
A checkpoint is the moment the engine stops owing the log a debt:
"A checkpoint is begun every
checkpoint_timeoutseconds, or ifmax_wal_sizeis about to be exceeded, whichever comes first."
Defaults are 5 minutes and 1 GB. A CHECKPOINT command forces one, and if no WAL has been
written since the last one, the next is skipped rather than performed on an idle system.
The trade-off is stated plainly, and it is the reason this is not a set-and-forget parameter:
"Reducing
checkpoint_timeoutand/ormax_wal_sizecauses checkpoints to occur more often. This allows faster after-crash recovery, since less work will need to be redone."
Against which: checkpoints "are fairly expensive, first because they require writing out all currently dirty buffers, and second because they result in extra subsequent WAL traffic." So the dial trades recovery time against steady-state I/O, and both ends are real costs.
max_wal_size is a target and not a bound — "max_wal_size is never a hard limit anyway, so you
should always leave plenty of headroom to avoid running out of disk space." Segment files that are no
longer needed are removed or recycled, renamed into place as future segments in the sequence.
Spreading the write. checkpoint_completion_target (default 0.9) is a fraction of the
checkpoint interval over which the dirty-buffer writes are paced: "the I/O rate is adjusted so that
the checkpoint finishes when the given fraction of checkpoint_timeout seconds have elapsed, or
before max_wal_size is exceeded, whichever is sooner." The docs warn against 1.0 — checkpoints do
other work besides writing buffers, so a target of 1.0 "is quite likely to result in checkpoints not
being completed on time."
Full-page writes are the write amplification
This is the mechanism, and it is a direct answer to the amplification question the spoke had open:
"If
full_page_writesis set (as is the default), there is another factor to consider. To ensure data page consistency, the first modification of a data page after each checkpoint results in logging the entire page content."
The whole 8 kB page goes into the log the first time it is touched in a checkpoint cycle, however small the change. Which means checkpoint frequency and log volume are coupled in the direction nobody expects:
"In that case, a smaller checkpoint interval increases the volume of output to the WAL, partially negating the goal of using a smaller interval, and in any case causing more disk I/O."
Checkpointing more often to shorten recovery makes the log bigger, because every page pays its full-image toll again on the other side of each checkpoint. The two obvious tuning goals — quick recovery, small log — are in direct opposition, and the coupling runs through full-page writes. Why the full images are needed at all is the torn-page problem, described in postgres-wal-reliability.
The durability levels
synchronous_commit "specifies how much WAL processing must complete before the database server
returns a 'success' indication to the client," with values remote_apply, on (the default),
remote_write, local and off. The docs' own recommendation is the interesting part:
"In many situations, turning off
synchronous_commitfor noncritical transactions can provide much of the potential performance benefit of turning offfsync, without the attendant risks of data corruption."
The distinction matters and is easy to lose: fsync=off risks a corrupt database, while
synchronous_commit=off risks losing recent transactions that were reported committed. One breaks
the file; the other breaks the promise. And because the setting is per-transaction, durability is not
a property of the server — a single database can honour it strictly for payments and loosely for
telemetry.
Group commit, and the number to start from
The amortisation write-ahead-logging already describes — one fsync committing many transactions
— has a dial. commit_delay "defines for how many microseconds a group commit leader process will
sleep after acquiring a lock within XLogFlush, while group commit followers queue up behind the
leader." No sleep happens if fsync is off or if fewer than commit_siblings sessions are in active
transactions.
Two details that make this usable rather than decorative:
- A starting value is given, derived from measurement rather than taste. "A value of half of the
average time the program reports it takes to flush after a single 8kB write operation is often the
most effective setting for
commit_delay." - Platform resolution can swallow the setting entirely — "on some platforms, the resolution of a
sleep request is ten milliseconds, so that any nonzero
commit_delaysetting between 1 and 10000 microseconds would have the same effect."
At the default of zero, group commit still happens opportunistically: a group forms from whatever sessions need to flush during the window in which the previous flush is already running.
wal_buffers sits underneath all of it. XLogInsertRecord runs "on every database low level
modification… at a time when an exclusive lock is held on affected data pages," so if it has to
evict filled buffers itself, that cost is paid under a lock. The docs specifically recommend raising
wal_buffers when full_page_writes is on and the system is busy, "to help smooth response times
during the period immediately following each checkpoint" — which is exactly when the full images
arrive in a burst.
Related
write-ahead-logging · checkpoint · postgres-wal-reliability · postgresql · online-transaction-processing · patroni · pgbackrest