Background processes
WAL summarizer
Since PostgreSQL 17 an optional process reads the WAL and writes small summary files listing which blocks of which relations were modified. pg_basebackup --incremental uses them to copy only changed blocks.
In plain words
A full backup copies every file, even if only a few rows changed since yesterday. Incremental backups (new in PostgreSQL 17) copy only the 8 kB blocks that changed. To know which ones, PostgreSQL needs a record of changed blocks: that is the job of the WAL summarizer.
It is off by default. With summarize_wal = on the process reads the WAL as it is written and notes, for each range of WAL, "these blocks of these tables were modified", without copying any data. The notes are small files in pg_wal/summaries/.
When you take an incremental backup, the server looks up the summaries between the previous backup and now, and sends only those blocks. pg_combinebackup later stitches the full and incremental backups into a normal data directory you can start. Step through the scene to follow one cycle.
For DBAs and operators
The process
Started by the postmaster when summarize_wal = on (reload is enough, no restart), also on a standby. ps: postgres: walsummarizer; backend_type = 'walsummarizer'. Wait events: WalSummarizerWal (waiting for more WAL), WalSummaryRead/WalSummaryWrite (file I/O), WalSummarizerError (sleeping after an error before retrying). It requires wal_level replica or logical.
What it writes
It decodes only the block references of WAL records (relation, fork, block number) into an in-memory block reference table, plus "limit blocks" for truncations. A summary file ends at a checkpoint's redo point (or at a timeline switch) and is named TTTTTTTTSSSSSSSSSSSSSSSSEEEEEEEEEEEEEEEE.summary: timeline, start LSN, end LSN in hex.
SELECT * FROM pg_get_wal_summarizer_state();
-- summarized_tli | summarized_lsn | pending_lsn | summarizer_pid
SELECT * FROM pg_available_wal_summaries() ORDER BY end_lsn DESC LIMIT 5;
SELECT * FROM pg_wal_summary_contents(1, '0/175ECB0', '0/A000028')
LIMIT 10; -- relfilenode, reltablespace, reldatabase, relforknumber, relblocknumber, is_limit_blockThe summarizer itself removes summaries older than wal_summary_keep_time (default 10 days, 0 = never), once the WAL they cover is gone too. Keep it comfortably longer than the time between a backup and the next incremental backup based on it.
Incremental backup cycle
pg_basebackup -D /bk/full -c fast # writes backup_manifest
pg_basebackup -D /bk/incr1 --incremental=/bk/full/backup_manifest
pg_basebackup -D /bk/incr2 --incremental=/bk/incr1/backup_manifest
pg_combinebackup /bk/full /bk/incr1 /bk/incr2 -o /restore/dataThe server reads the prior manifest's start LSN and timeline, waits until the summarizer has covered WAL up to the new backup's start, and then sends changed blocks as INCREMENTAL.<relfilenode> files (block list + blocks). Files that are new since the prior backup, empty, not relation files, free space map forks, or have more than 90 % of their blocks changed are sent whole. pg_combinebackup must get the chain in order, oldest first. The result is a normal full backup; recovery from it then needs WAL from its start as usual (archive or pg_wal included with -X).
Support notes
- Incremental backup fails if any summary in the needed LSN range is missing, for example because
summarize_walwas off for a while, the summarizer lagged too far, or the summaries were already removed. Take a new full backup. - If the summarizer cannot keep up,
summarized_lsnlags behind and an incremental backup waits for it (with warnings in the server log). - While
summarize_walis on, checkpoints do not remove WAL that has not been summarized yet. A summarizer that is stuck (look forWalSummarizerErrorand errors in the log) therefore makespg_walgrow, like a stale replication slot. - Tools like pgBackRest and Barman have their own incremental methods; the WAL summarizer serves the built-in
pg_basebackupone. - An ordinary ERROR does not end the process: it logs it, sleeps 10 s (
WalSummarizerError) and retries. Any abnormal exit of the process (FATAL, SIGKILL) makes the postmaster perform a crash restart.
For developers: inside the source
Where it lives
walsummarizer.c: the process (WalSummarizerMain()).blkreftable.c/blkreftable.h: the block reference table and its on-disk format (shared with frontend tools).walsummary.c: listing, reading and removing files inpg_wal/summaries;walsummaryfuncs.c: the SQL functions.basebackup_incremental.c: the consumer, used byBASE_BACKUP ... INCREMENTAL;src/bin/pg_combinebackupandsrc/bin/pg_walsummaryare the frontend tools.
Key data structures
WalSummarizerData in shared memory (protected by WALSummarizerLock) holds summarized_tli/summarized_lsn (end of the last summary file written), pending_lsn (how far it has read), summarizer_pgprocno and the condition variable summary_file_cv that waiters sleep on. This is what pg_get_wal_summarizer_state() returns. A BlockRefTable is a hash of BlockRefTableEntry, one per relation fork, each storing a limit_block and the set of modified block numbers (as arrays or bitmaps per chunk of blocks).
Call flow
- The main loop calls
SummarizeWAL(), which reads records with anXLogReader(summarizer_read_local_xlog_page(), waiting insummarizer_wait_for_wal()with wait eventWalSummarizerWal). - For each record,
XLogRecGetBlockTagExtended()yields block references that go toBlockRefTableMarkBlockModified()(FSM fork skipped).SummarizeSmgrRecord(),SummarizeDbaseRecord()andSummarizeXactRecord()turn truncations, created/dropped databases and dropped relations intoBlockRefTableSetLimitBlock()calls. SummarizeXlogRecord()recognises redo-point, shutdown-checkpoint, parameter-change and end-of-recovery records: one of these after the start ends the current file (it also trackswal_levelchanges, so WAL written withminimalis skipped, never summarized).WriteBlockRefTable()writes a temp file that isdurable_rename()d into place.MaybeRemoveOldWalSummaries()applieswal_summary_keep_time.
Interaction with the rest of the server: the checkpointer's WAL-removal code in xlog.c calls GetOldestUnsummarizedLSN() to keep unsummarized segments; PrepareForIncrementalBackup() calls WaitForWalSummarization() and GetWalSummaries(), then GetFileBackupMethod() decides per file between full copy and an INCREMENTAL. file.
Experimenting
pg_walsummary pg_wal/summaries/*.summarydumps a file's block references.- TAP:
src/bin/pg_combinebackup/t(e.g.002_compare_backups.pl,011_ib_truncation.pl) andsrc/bin/pg_walsummary/t. - The summarizer starts at
DEBUG1with DEBUG: WAL summarizer started and logs each file written atDEBUG1too.
Parameters that shape this
Related topics
Write-ahead log (WAL) Archiver Archiving and point-in-time recovery Checkpoints Checkpointer WAL sender Physical layout on disk