PostgreSQLXII · WAL, Checkpoints and Crash RecoveryWAL
WAL segments, LSNs and generation rate
What you'll learn
- Measure a cluster's WAL generation rate and project it
- Map an LSN to a segment file and back
- Read pg_stat_wal and act on what it reports
- Identify which workload changes move the WAL rate most
Prerequisites
Practice
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
WAL is a single logical stream written into fixed-size files. Knowing
how fast yours grows is a prerequisite for sizing archives, replication
links, and the filesystem pg_wal lives on.
Segments
The stream is divided into segments of wal_segment_size, 16 MB by
default and fixed at initdb time. A segment is named from three
8-character hexadecimal fields:
0000000100000007000000CF
^^^^^^^^ timeline
^^^^^^^^ high 32 bits of the LSN
^^^^^^^^ segment number within that
The mapping is available directly rather than by hand:
$ psql -U postgres -c "SELECT pg_walfile_name('7/CF446178')" pg_walfile_name
--------------------------
0000000100000007000000CFMeasuring the rate
$ psql -U postgres -At -c "SELECT pg_current_wal_lsn()" # before and after the workloadtps = 2218.150222
start_lsn | end_lsn | wal_in_20s | wal_per_second
-------------+------------+------------+----------------
7/C0F3DB58 | 7/C645F2A0 | 85 MB | 4359 kB4,359 kB per second. Projected:
| Period | WAL |
|---|---|
| Per minute | ~255 MB |
| Per hour | ~15 GB |
| Per day | ~367 GB |
That is the number every downstream capacity question depends on:
pg_walfilesystem size must holdmax_wal_sizeplus whatever slots and archiving retain.- Archive storage grows at this rate, before compression.
- Replication bandwidth must sustain it, or replicas fall behind permanently.
- Recovery time is this rate multiplied by the checkpoint distance.
The measurement takes twenty seconds. Do it on the real workload rather than on pgbench, and do it again after any significant schema change.
-- a reusable form: run twice, a minute apart
SELECT now(), pg_current_wal_lsn();
pg_stat_wal
$ psql -U postgres -x -c "SELECT * FROM pg_stat_wal"wal_records | 82823247
wal_fpi | 3150464
wal_bytes | 33429991279
wal_buffers_full | 2554998
stats_reset | 2026-08-27 18:31:22.55921+00| Column | Reading |
|---|---|
wal_records | Total records written |
wal_fpi | Full page images. Against wal_records, how much of your WAL is torn-page protection |
wal_bytes | Total bytes. With stats_reset, gives the rate |
wal_buffers_full | Times a backend had to wait because the WAL buffer was full |
What moves the rate
In rough order of how much difference each makes:
Non-HOT updates. Lesson VII-02 measured 2.8× more WAL from
fillfactor alone, and 2.6× from adding one index to an updated column.
This is usually the largest available reduction.
Index count. Lesson X-05 measured 3.6× more WAL for seven indexes against one, on identical inserts.
Checkpoint frequency. Every checkpoint restarts the full-page-write cycle. Lesson XII-01 measured 50% more WAL in the round following one.
Freezing. Lesson VIII-05 measured a first freeze generating roughly the table’s own size in WAL.
wal_compression. Compresses full page images specifically. On a
workload where wal_fpi is a large share of bytes, this is
straightforwardly worthwhile.
Batch size. Ten thousand single-row transactions generate substantially more WAL than one transaction inserting ten thousand rows, because each commit writes its own record and each may force a flush.
What to take from this
- Segments are 16 MB by default and fixed at
initdb. The name encodes timeline and LSN. - Measured: 2,218 tps produced 4,359 kB/s, about 367 GB a day. Measure yours; it takes twenty seconds.
wal_buffers_fullgrowing meanswal_buffersis too small. It is cheap shared memory and needs a restart.wal_fpiagainstwal_recordssays how much of your WAL is torn-page protection.- The largest reductions available are usually HOT updates and index count, not WAL settings.
max_wal_sizeis a soft target. Steady-statepg_walis larger, and slots and archiving are not bounded by it at all.
Cross-course references
- Observability for Production Sysadmins — Part XIII (Rates and counters) covers turning an LSN into a generation rate, which is the input to archive sizing and to replication bandwidth.
- Linux for Production Sysadmins — Part XLI (Storage Performance) covers whether the device can absorb that rate alongside everything else on it.
Quiz
Knowledge check · 6 questions
Q1. pg_wal on a cluster with max_wal_size set to 1 GB is consistently around 2.2 GB, with no replication slots and a healthy archiver. Is this a problem?
Q2. pg_stat_wal shows wal_buffers_full climbing into the millions on a write-heavy cluster. What does it mean and what is the action?
Q3. A team wants to reduce WAL volume on a cluster generating 367 GB a day. Which change is likely to give the largest reduction?
Q4. Which of these can make pg_wal grow far beyond max_wal_size? Select all that apply.
Q5. wal_segment_size can be raised on a running cluster with a configuration reload to reduce the number of archive operations.
Q6. How would you measure a production cluster's WAL generation rate, and what does the number let you plan?
Passing score: 75%. Answers are checked in this browser.