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][2]: once .history lies about ancestry, consumers that trust it are on thin ice. [1] https://www.postgresql.org/message-id/flat/85386EF6-16B7-4D62-86BE-526A10F93825%40yandex-team.ru [2] https://www.postgresql.org/message-id/flat/CA%2BTgmobr27GpKDZx3_ezW2%2BC5_g18i%2BjSK3sGF_cR-_ESv5N5A%40mail.gmail.com пт, 31 июл. 2026 г. в 13:36, Филиппов Степан <[email protected]>: > > 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] > https://www.postgresql.org/message-id/flat/CA%2BTgmobr27GpKDZx3_ezW2%2BC5_g18i%2BjSK3sGF_cR-_ESv5N5A%40mail.gmail.com > > [1] > https://www.postgresql.org/message-id/flat/85386EF6-16B7-4D62-86BE-526A10F93825%40yandex-team.ru > > > Regards, > Stepan Filippov, > Yandex Cloud. > > -- Regards, Rachitskiy Andrey
