Incremental backup accepts a prior backup that extends past a timeline switch

Started by Raja Sai pranav10 days ago2 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:t254360
psql -h localhost -U postgres

Built from patchset v2 (message #2), November 02, 2026 at 09:35 AM.

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 t254360_2 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 t254360_2 && git checkout t254360_2

Patchset v2 (message #2) is on t254360_2

Jump to latest
#1Raja Sai pranav
rajasaipranav0@gmail.com

Hi,

While testing incremental backup across failovers, I found a case where
pg_basebackup --incremental accepts a prior backup that is not part of the
server's history, and the combined backup restores without errors but is
corrupt.

The scenario is an ordinary failover with asynchronous replication:

1. A full backup is taken on the primary (timeline 1).
2. Before that backup finishes, the standby stops receiving WAL, so the
backup's WAL range ends after the last WAL the standby replayed.
3. The standby is promoted (timeline 2, switch point inside that range).
4. An incremental backup is taken on the new primary, using the full
backup from step 1 as the prior backup.

Step 4 succeeds. In PrepareForIncrementalBackup(), the end LSN of the prior
backup's final WAL range is only compared with the start of the new backup.
When that timeline is a historical one on this server, the end LSN is not
compared with the switch point in the timeline history, so the check that
already exists for non-final timelines never applies.

The result is that blocks changed on the old primary after the switch point
are not in the new primary's WAL summaries, so the incremental backup omits
them and pg_combinebackup takes them from the prior backup. On PG 18.6, in
my
reproduction, the restored cluster started up fine, but an index scan on the
table's primary key failed with:

ERROR: could not read blocks 412..412 in file "base/5/16390": read
only 0 of 8192 bytes

The new primary's t_pkey has 276 blocks and the old primary's has 553, so
the
combined index kept internal pages from the old history that point past the
end of the 276-block file.

The attached patch adds the missing check: if the prior backup's final
timeline has an end in this server's history, the prior backup's WAL range
must not end after it. The error reuses the wording of the existing
non-final-timeline check:

ERROR: manifest requires WAL from final timeline 1 ending at
0/05631BC0,
but this server switched timelines at 0/04001338

It also adds src/bin/pg_combinebackup/t/013_diverged_parent.pl. The test
creates the condition deterministically by promoting a standby with
recovery_target_lsn set to the prior backup's start LSN, so the switch point
falls inside the prior backup's WAL range. It also checks that an
incremental
relative to a backup taken before the switch still works. Without the code
change, the test fails because the incremental backup succeeds.

Testing on master with the patch:
- pg_combinebackup TAP tests: all 13 pass (124 tests)
- pg_basebackup TAP tests: all 6 pass (324 tests)
- incrementals across a promotion that should keep working (full on the old
primary before the switch, full on the standby before promotion) still
combine and restore with data matching the live server

The same code is in REL_17_STABLE and REL_18_STABLE, so I think this should
be
back-patched to 17.

Regards,
Sai Pranav

Attachments:

t254360_1
v1-0001-Reject-incremental-backups-whose-prior-backup-out.patchapplication/octet-stream; name=v1-0001-Reject-incremental-backups-whose-prior-backup-out.patchDownload+111-1
#2Fujii Masao
masao.fujii@gmail.com
In reply to: Raja Sai pranav (#1)
Re: Incremental backup accepts a prior backup that extends past a timeline switch

On Sat, Oct 24, 2026 at 5:39 AM Raja Sai pranav
<rajasaipranav0@gmail.com> wrote:

The attached patch adds the missing check: if the prior backup's final
timeline has an end in this server's history, the prior backup's WAL range
must not end after it.

Thanks for the report and patch!
Attached is a revised version with these changes:

- Check the timeline switch point before the new backup's start LSN,
so the error directly reports the history mismatch.
- Extend 008_promote.pl rather than adding a separate test.
- Document the timeline requirement for incremental backup.

Thought?

Regards,

--
Fujii Masao

Attachments:

t254360_2
v2-0001-Reject-incremental-backups-based-on-a-backup-from.patchapplication/octet-stream; name=v2-0001-Reject-incremental-backups-based-on-a-backup-from.patchDownload+49-1