DB Internals AtlasPostgreSQL 18.6 Home About

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_block

The 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/data

The 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_wal was 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_lsn lags behind and an incremental backup waits for it (with warnings in the server log).
  • While summarize_wal is on, checkpoints do not remove WAL that has not been summarized yet. A summarizer that is stuck (look for WalSummarizerError and errors in the log) therefore makes pg_wal grow, like a stale replication slot.
  • Tools like pgBackRest and Barman have their own incremental methods; the WAL summarizer serves the built-in pg_basebackup one.
  • 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

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 an XLogReader (summarizer_read_local_xlog_page(), waiting in summarizer_wait_for_wal() with wait event WalSummarizerWal).
  • For each record, XLogRecGetBlockTagExtended() yields block references that go to BlockRefTableMarkBlockModified() (FSM fork skipped). SummarizeSmgrRecord(), SummarizeDbaseRecord() and SummarizeXactRecord() turn truncations, created/dropped databases and dropped relations into BlockRefTableSetLimitBlock() 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 tracks wal_level changes, so WAL written with minimal is skipped, never summarized). WriteBlockRefTable() writes a temp file that is durable_rename()d into place.
  • MaybeRemoveOldWalSummaries() applies wal_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/*.summary dumps a file's block references.
  • TAP: src/bin/pg_combinebackup/t (e.g. 002_compare_backups.pl, 011_ib_truncation.pl) and src/bin/pg_walsummary/t.
  • The summarizer starts at DEBUG1 with DEBUG: WAL summarizer started and logs each file written at DEBUG1 too.

Parameters that shape this

summarize_wal wal_summary_keep_time wal_level

Related topics

Write-ahead log (WAL) Archiver Archiving and point-in-time recovery Checkpoints Checkpointer WAL sender Physical layout on disk