Write-ahead logging
Lab: what a commit costs, and what a checkpoint adds
The idea in 30 seconds
Time thousands of tiny commits and measure the log behind a large update. A durable commit is one log flush, and how long that takes depends on how honestly your storage reports "written": on our laptop, from 31 microseconds to 4.5 milliseconds. And the first change to each page after a checkpoint writes the whole page to the log: 23 times the WAL of the same change a moment later.
Helps to know first: How a table is stored on disk
On this page
Before you start
Use a throwaway database you are allowed to reconfigure. Step 2 changes a server setting and puts it back. Our numbers come from PostgreSQL 18.3 on an Apple Silicon laptop with its built-in SSD; commit timings on your machine will differ, possibly by orders of magnitude.
1. A thousand tiny commits
A procedure can commit inside a loop, so it can produce thousands of separate transactions quickly:
CREATE TABLE events (id int, at timestamptz DEFAULT clock_timestamp());
CREATE PROCEDURE many_commits(n int) LANGUAGE plpgsql AS $$
BEGIN
FOR i IN 1..n LOOP
INSERT INTO events (id) VALUES (i);
COMMIT;
END LOOP;
END $$;
\timing on
SET synchronous_commit = on; CALL many_commits(5000);
SET synchronous_commit = off; CALL many_commits(5000);
synchronous_commit | 5,000 commits (3 runs) | Per commit | Yours |
|---|---|---|---|
on | 154, 169, 157 ms | 31 µs | |
off | 12, 11, 11 ms | 2.2 µs |
With on, each commit waits for its log record to be flushed. With off, it returns at once and the log is flushed shortly after.
2. How durable was that, really?
31 microseconds is suspiciously fast for a write that survives a power cut. Check how PostgreSQL forces the flush:
SHOW wal_sync_method; -- open_datasync on our Mac
On macOS, the system’s flush calls hand data to the drive, but the drive may keep it in its own cache, and the manual page warns that a power loss can then lose it. PostgreSQL’s fsync_writethrough method also forces the drive’s cache. Measure it:
ALTER SYSTEM SET wal_sync_method = 'fsync_writethrough';
SELECT pg_reload_conf();
SET synchronous_commit = on;
CALL many_commits(500);
ALTER SYSTEM RESET wal_sync_method;
SELECT pg_reload_conf();
| Flush method | 500 commits (3 runs) | Per commit | Yours |
|---|---|---|---|
open_datasync (default here) | not run at 500; 5,000 took 157 ms | 31 µs | |
fsync_writethrough | 2,262, 2,267, 2,253 ms | 4.5 ms |
145 times slower. That is the real cost of making each commit reach permanent storage on this machine. Which number your production server pays depends on its operating system, its storage, and whether the storage has protected write caches. Servers usually run Linux, where the defaults are different; measure there, not on a laptop.
3. The checkpoint tax
Make a table of about 5,300 pages, force a checkpoint, then change one row on each of 5,000 different pages, twice:
CREATE TABLE wide (id int PRIMARY KEY, n int, pad text);
INSERT INTO wide SELECT g, 0, repeat('x', 200) FROM generate_series(1, 175000) g;
VACUUM ANALYZE wide;
CHECKPOINT;
SELECT pg_current_wal_insert_lsn() AS a0 \gset
UPDATE wide SET n = n + 1 WHERE id % 35 = 1; -- one row on each of 5,000 pages
SELECT pg_current_wal_insert_lsn() AS a1 \gset
UPDATE wide SET n = n + 1 WHERE id % 35 = 2; -- the same pages again
SELECT pg_current_wal_insert_lsn() AS a2 \gset
SELECT pg_size_pretty(pg_wal_lsn_diff(:'a1', :'a0')) AS first_update,
pg_size_pretty(pg_wal_lsn_diff(:'a2', :'a1')) AS second_update;
| WAL written (ours) | Yours | |
|---|---|---|
| First update after the checkpoint | 44 MB | |
| Same pages, second update | 1,880 kB |
The same change to the same 5,000 rows, 23 times the log. pg_waldump --stats over the first range shows why: full page images were 95.6% of it, about 42 MB, because each of the 5,000 pages was copied into the log the first time it changed after the checkpoint. The second update found those pages already logged.
What you measured
- Synchronous commit costs one log flush per transaction; turning it off removed nearly all of that.
- How long a “durable” flush takes depends on what the storage promises. On this laptop the honest version cost 145 times the default.
- The first change to each page after a checkpoint writes the whole page to the log. Frequent checkpoints, or changes spread across many pages, multiply write volume.
Clean up
DROP TABLE events, wide;
DROP PROCEDURE many_commits(int);
Check yourself
Why did 5,000 commits take 13 times longer with
synchronous_commit = on?Show answer
Each commit waited for its WAL record to be flushed to storage before returning. With
off, commits return immediately and the log is flushed shortly afterwards.Why was
fsync_writethroughso much slower than the default on macOS?Show answer
It also forces the drive to empty its write cache to permanent storage. The default flush only hands data to the drive, which may keep it in a volatile cache.
Why did the first update after a checkpoint write 23 times the WAL of the second?
Show answer
With
full_page_writes, the first change to each page after a checkpoint copies the whole 8 kB page into the log. The second update changed pages that had already been copied, so it logged only the small change records.
Finished reading?
Saved in this browser only.
Sources
Where each claim on this page comes from.
- 1.PostgreSQL documentation: 19.5 Write Ahead LogPrimary· §19.5 Write Ahead Log: synchronous_commit, wal_sync_method, full_page_writes
With synchronous_commit on, commit waits for the WAL flush; wal_sync_method chooses how the flush is forced, and fsync_writethrough also forces write-through of the disk's write cache; full page images are written on the first change to a page after a checkpoint.
- 2.fsync(2) manual page, macOSPrimary· DESCRIPTION·
man 2 fsyncOn macOS, fsync() moves data to the drive but the drive may keep it in its cache; F_FULLFSYNC asks the drive to flush to permanent storage.
- 3.PostgreSQL documentation: pg_waldumpPrimary· Options: --stats
pg_waldump --stats summarises record and full-page-image sizes.
Go further
What to read next, and why it is worth the time.
- 1.PostgreSQL documentation: 28.1 ReliabilityPrimary· §28.1 Reliability
Why "the disk said it wrote it" is not always true, and what PostgreSQL relies on.
Applies to PostgreSQL 18. Draft: not yet fact-checked end to end.