Assertion failure in WAL summarizer

Started by Fujii Masao1 day ago4 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t254307
psql -h localhost -U postgres

Built from patchset v4 (message #4), October 22, 2026 at 03:55 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t254307_4 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t254307_4 && git checkout t254307_4

Patchset v4 (message #4) is on t254307_4

Jump to latest
#1Fujii Masao
masao.fujii@gmail.com

Hi,

While working on [1]/messages/by-id/19774-8a1cbfd1774fcd0c@postgresql.org, I encountered the following assertion failure in
the WAL summarizer:

TRAP: failed Assert("end_of_summary_lsn >= current_lsn"), File:
"walsummarizer.c", Line: 460, PID: 37935

I haven't investigated this yet, but wanted to report it in advance.

The following steps reproduce the failure:

-------------------------------------------------------
initdb -D data
echo "summarize_wal = on" >> data/postgresql.conf
pg_ctl -D data start

psql -c "CREATE TABLE t (i int)"
pg_basebackup -D sby -c fast -R

psql -c "INSERT INTO t VALUES (1)"
psql -c "CHECKPOINT"
psql -c "SELECT redo_lsn FROM pg_control_checkpoint()" # Save this redo_lsn
psql -c "INSERT INTO t VALUES (2)"
psql -c "CHECKPOINT"

cat << EOF >> sby/postgresql.conf
port = 5433
recovery_target_lsn = '0/03001B08' # Use the saved redo_lsn
recovery_target_inclusive = false
recovery_target_action = 'pause'
EOF

pg_ctl -D sby start

# Wait until all three queries return true
psql -c "SELECT pg_get_wal_replay_pause_state() = 'paused'" -p 5433
psql -c "SELECT EXISTS (SELECT FROM pg_available_wal_summaries() WHERE
tli = 1 AND start_lsn = '0/03001B08')" -p 5433 # Use the saved
redo_lsn
psql -c "SELECT EXISTS (SELECT FROM pg_stat_activity WHERE
backend_type = 'walsummarizer' AND wait_event = 'WalSummarizerWal')"
-p 5433

pg_ctl -D sby promote
-------------------------------------------------------

After promotion, I got the following messages:

-------------------------------------------------------
\
2026-10-22 10:01:53.329 JST [37932] LOG: selected new timeline ID: 2
2026-10-22 10:01:53.361 JST [37932] LOG: archive recovery complete
TRAP: failed Assert("end_of_summary_lsn >= current_lsn"), File:
"walsummarizer.c", Line: 460, PID: 37935
2026-10-22 10:01:53.363 JST [37930] LOG: checkpoint starting: force
2026-10-22 10:01:53.364 JST [37927] LOG: database system is ready to
accept connections
0 postgres 0x000000011060e742
ExceptionalCondition + 178
1 postgres 0x00000001102e9762
WalSummarizerMain + 1698
2 postgres 0x00000001102de549
postmaster_child_launch + 377
3 postgres 0x00000001102e2c91
StartChildProcess + 273
4 postgres 0x00000001102e4ec2
LaunchMissingBackgroundProcesses + 674
5 postgres 0x00000001102e30f9 ServerLoop + 489
6 postgres 0x00000001102e1e69 PostmasterMain + 5753
7 postgres 0x0000000110179e43 main + 771
8 dyld 0x00007ff811152530 start + 3056
2026-10-22 10:01:53.371 JST [37927] LOG: WAL summarizer process (PID
37935) was terminated by signal 6: Abort trap: 6
2026-10-22 10:01:53.371 JST [37927] LOG: terminating any other active
server processes
-------------------------------------------------------

Regards,

[1]: /messages/by-id/19774-8a1cbfd1774fcd0c@postgresql.org

--
Fujii Masao

#2Manu
manuelreyesbravo@gmail.com
In reply to: Fujii Masao (#1)
Re: Assertion failure in WAL summarizer

Hi Fujii,

On Thu, Oct 22, 2026 at 10:38 AM Fujii Masao <masao.fujii@gmail.com> wrote:

I haven't investigated this yet, but wanted to report it in advance.

I can reproduce it with your steps on master, every time once the
three conditions hold, and also on REL_19, REL_18 and REL_17 with
cassert, so it goes back to when the summarizer was added.

Before promotion the summarizer has already gone past the recovery
target. In recovery GetLatestLSN() returns the walreceiver flush
position, and the walreceiver keeps streaming after replay pauses.
With your steps on master, replay pauses at the recovery target
0/03001B08, the walreceiver has flushed to 0/03001D98, and the
summarizer has summarized up to 0/03001CB0 and is waiting for WAL at
0/03001D98.

After promotion TLI 1 ends at 0/03001B08 ("1 0/03001B08 before LSN
0/03001B08" in 00000002.history), behind the summarizer. With gdb
attached to the summarizer:

1. summarizer_read_local_xlog_page() notices TLI 2 while waiting and
sets read_upto = switchpoint: 0/03001D98 -> 0/03001B08. The comment
there expects this ("this will cause read_upto to move backwards,
because we might have been promoted before reaching the end of the
previous timeline").

2. The next read is past read_upto on a timeline that is now historic,
so end_of_wal is set and SummarizeWAL() returns
summary_end_lsn = private_data->read_upto = 0/03001B08, although it
started at 0/03001CB0.

3. WalSummarizerMain(): current_lsn = 0/03001CB0,
end_of_summary_lsn = 0/03001B08 -> the assertion at line 460.

The main loop already handles "we passed the switch point": at the top
of the loop, if current_lsn >= switch_lsn it backs up to the switch
point on the new timeline. But that path only runs when the timeline
change is seen there. When it is seen inside SummarizeWAL(), the
backwards LSN comes back as the return value instead.

Without assertions nothing bad seems to happen. On a non-cassert build
of master with the same steps, the summarizer takes 0/03001B08 as its
position, sees TLI 2 on the next iteration, switches at 0/03001B08 and
goes on. It is the same process, and you end up with the same
summaries and state as with the patch below. So as far as I can tell
this only affects assert-enabled builds, but there the summarizer
crash makes the postmaster restart the whole cluster right after the
promotion.

The attached patch makes SummarizeWAL() report no progress when
read_upto ended up before the start of the summary, so the existing
code in the main loop does the rewind on the next iteration. With it,
your steps no longer trip the assertion, and summarization resumes on
TLI 2 exactly at the target, on master, 19, 18 and 17 (the patch
applies to all four). It also adds
src/bin/pg_walsummary/t/004_promote_past_target.pl, which follows your
steps: it fails with the TRAP on unpatched master and passes with the
fix. make check, pg_walsummary and pg_combinebackup pass.

Another option is to relax the assertion and keep the current
behavior, since that is already what a non-assert build does. I went
with the clamp because then SummarizeWAL() never returns an LSN before
its start, and summarized_lsn doesn't go backwards on the old timeline
for an iteration before the rewind. No strong opinion, though.

Regards,
Manu

Attachments:

t254307_2
v1-0001-Fix-assertion-failure-in-WAL-summarizer-after-pro.patchtext/x-patchDownload+98-1
#3Daria Lepikhova
daria.n.lepikhova@gmail.com
In reply to: Manu (#2)
Re: Assertion failure in WAL summarizer

Hi,

I hit the same assertion independently earlier and can confirm Manu's
analysis.

Before promotion, with Fujii's steps on master:

replay 0/03001B08
receive 0/03001D98
summarized 0/03001CB0

which matches the numbers in Manu's mail. The size of the gap between
replay and receive doesn't matter: with ~20MB of streamed WAL past an
arbitrary recovery_target_lsn (recovery_target_inclusive = true) the
summarizer is ~20MB ahead and the crash is the same.

With cassert disabled, I don't see any adverse effects either: summaries
on TLI 2 start at the switch point, and an incremental backup across the
promotion works.

With v1 applied on master, the same scenarios pass with cassert and the
new test passes; with the walsummarizer.c hunk reverted, the test fails
with the TRAP. pg_walsummary and pg_combinebackup TAP suites pass.

A couple of minor comments on v1:

1. The comment in summarizer_read_local_xlog_page() about read_upto
moving backwards says the next iteration will "likely conclude that
we've reached end of WAL". It might be worth mentioning there that
SummarizeWAL() then reports no progress, so the link between the two
places is visible from both sides.

2. In the test, the summarizer PID check and the log check both detect
the same thing, a restart after the crash. Without the fix neither
is reached anyway: pg_ctl promote fails once the postmaster
reinitializes, and the test dies without a plan. I think one of the
two checks would be sufficient.

Separately, while looking at this I found a related but different
problem: if the switch point falls exactly on a page or segment
boundary, the first summary on the new timeline starts after the page
header, and incremental backups on top of any pre-promotion backup fail
permanently with "WAL summaries are required on timeline 2 from
0/04000000 ...". v1 doesn't change that, and it isn't expected to. I'll
look into it
more closely and then maybe start a separate thread with a reproducer.

--
Daria Lepikhova

#4Manu
manuelreyesbravo@gmail.com
In reply to: Daria Lepikhova (#3)
Re: Assertion failure in WAL summarizer

Hi Daria,

Thanks for confirming the analysis independently, and for the review
and testing.

On Thu, Oct 22, 2026 at 10:30 AM Daria Lepikhova
<daria.n.lepikhova@gmail.com> wrote:

1. The comment in summarizer_read_local_xlog_page() about read_upto
moving backwards says the next iteration will "likely conclude that
we've reached end of WAL". It might be worth mentioning there that
SummarizeWAL() then reports no progress, so the link between the two
places is visible from both sides.

Done in the attached v2. The comment now ends with "If the switch
point precedes the start of the current summary, SummarizeWAL() then
reports no progress." The comment on the clamp in SummarizeWAL()
already points back to summarizer_read_local_xlog_page().

2. In the test, the summarizer PID check and the log check both detect
the same thing, a restart after the crash. Without the fix neither
is reached anyway: pg_ctl promote fails once the postmaster
reinitializes, and the test dies without a plan. I think one of the
two checks would be sufficient.

Agreed. v2 keeps only the log check, since log_contains() doesn't need
a running server.

TAP nodes run with restart_after_crash = off, so the crash shuts the
standby down instead of reinitializing it, 73-105 ms after it reaches
production in my runs. pg_ctl promote polls every 100 ms, so whether
it sees the promotion first is a race. Here promote won in 13 of 13
runs without the fix, and the v1 test then failed "not ok 1" and died
on the PID query against the stopped server. With v2 and the fix
reverted, it now ends with "failed 2 tests of 2" instead (4 of 4).
When promote loses the race, as in your run, the test still bails out
at promote. I left that as it is.

v2 applies to master, 19, 18 and 17 like v1, and passes there.

Separately, while looking at this I found a related but different
problem: if the switch point falls exactly on a page or segment
boundary, [...]

Looking forward to that thread. I'm happy to run your reproducer
against v2 once you post it.

Regards,
Manu

Attachments:

t254307_4
v2-0001-Fix-assertion-failure-in-WAL-summarizer-after-pro.patchtext/x-patchDownload+93-2