[PATCH] Fix timeline history after recovery stops on an ancestor

Started by Филиппов Степан3 days ago2 messageshackers
Jump to latest
#1Филиппов Степан
stepan.filippov@yandex.ru

Attachments:

0001-A-recovery-target-can-be-reached-before-recovery-swi.patchtext/x-diff; name=0001-A-recovery-target-can-be-reached-before-recovery-swi.patchDownload+24-2
#2Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: Филиппов Степан (#1)
Re: [PATCH] Fix timeline history after recovery stops on an ancestor

Hi Stepan,

I looked at this and I think the diagnosis is right.

When archive recovery stops on an ancestor before the switch into
recoveryTargetTLI, the end-of-recovery path already treats
endOfRecoveryInfo->lastRecTLI as the timeline we are leaving
(PrevTimeLineID / the end-of-recovery record). writeTimeLineHistory()
was the odd one out: it still recorded recoveryTargetTLI as the parent,
so the new .history file copied a later switch and then appended an
earlier switchpoint. That is not a usable ancestry for anything that
walks timeline history.

Using lastRecTLI as the parent matches what recovery actually did.
endOfLogTLI would be the wrong substitute here: in the 028 scenario the
last records are still on timeline 1, even though they were read from a
segment whose filename carries timeline 2. Leaving
findNewestTimeLine(recoveryTargetTLI) + 1 alone also looks correct; the
new TLI must be unused in the archive, while the parent is a separate
question.

Heikki, Michael — you've spent a lot of time in this area (028 itself,
the "follow last replayed TLI" idea in walsenders, and the surrounding
recovery/timeline maintenance). If you have a moment, it would be
good to hear whether using lastRecTLI as the history parent is the
right long-term shape, or whether I'm missing a case where
recoveryTargetTLI is still required.

This also seems adjacent to the recent archive-recovery / timeline
threads [1]/messages/by-id/85386EF6-16B7-4D62-86BE-526A10F93825@yandex-team.ru[2]/messages/by-id/CA+Tgmobr27GpKDZx3_ezW2+C5_g18i+jSK3sGF_cR-_ESv5N5A@mail.gmail.com: once .history lies about ancestry, consumers that
trust it are on thin ice.

[1]: /messages/by-id/85386EF6-16B7-4D62-86BE-526A10F93825@yandex-team.ru
/messages/by-id/85386EF6-16B7-4D62-86BE-526A10F93825@yandex-team.ru
[2]: /messages/by-id/CA+Tgmobr27GpKDZx3_ezW2+C5_g18i+jSK3sGF_cR-_ESv5N5A@mail.gmail.com
/messages/by-id/CA+Tgmobr27GpKDZx3_ezW2+C5_g18i+jSK3sGF_cR-_ESv5N5A@mail.gmail.com

пт, 31 июл. 2026 г. в 13:36, Филиппов Степан <stepan.filippov@yandex.ru>:

Hi hackers,

While working on WAL verification, we found a case where a timeline
history file does not describe the sequence of timeline switches that
actually took place.

Suppose timeline 2 forked from timeline 1 at LSN B. We then perform
PITR with recovery_target_timeline = 'latest', but stop at a restore
point A on timeline 1, where A < B. PostgreSQL allocates timeline 3,
but currently records recoveryTargetTLI, i.e. timeline 2, as its
parent. The resulting history looks like this:

cat 000002.history:
1 B ...
2 A ...

The switchpoints run backwards. The actual ancestry is a direct fork
from timeline 1 to timeline 3 at A. A consumer such as a WAL verifier
cannot reconstruct that ancestry from the history file.

The attached patch uses the timeline of the last replayed WAL record
as the parent of the new timeline. It also adjusts 028_pitr_timelines.pl
to check the generated history file.

This came up while looking at the following related discussions [0] & [1]

I think this is a correctness issue and should be backpatched.

[0]
/messages/by-id/CA+Tgmobr27GpKDZx3_ezW2+C5_g18i+jSK3sGF_cR-_ESv5N5A@mail.gmail.com

[1]
/messages/by-id/85386EF6-16B7-4D62-86BE-526A10F93825@yandex-team.ru

Regards,
Stepan Filippov,
Yandex Cloud.

--
Regards,
Rachitskiy Andrey