PITR promote bug: Checkpointer writes to older timeline
Soumyadeep Chakraborty <soumyadeep2007@gmail.com>
From: Soumyadeep Chakraborty <soumyadeep2007@gmail.com>
To: pgsql-hackers <pgsql-hackers@postgresql.org>
Cc: kevin.yeap@vmware.com, Michael Paquier <michael@paquier.xyz>,
jyih@vmware.com
Date: 2021-03-03T01:56:03Z
Lists: pgsql-hackers
Attachments
- repro.txt (text/plain)
- 0001-Prevent-checkpointer-from-writing-to-older-timeline.patch (text/x-patch) patch 0001
- waldiff.diff (text/x-patch) patch
Hello hackers, We came across an issue where the checkpointer writes to the older timeline while a promotion is ongoing after reaching the recovery point in a PITR, when there are prepared transactions before the recovery point. We came across this issue first in REL_12_STABLE and saw that it also exists in devel. Setup: PFA a minimal reproduction script repro.txt. After running the script, we notice that the checkpointer has written the end-of-recovery shutdown checkpoint in the previous timeline (tli = 1), i.e. it wrote into the WAL segment 000000010000000000000003 instead of writing to the WAL segment 000000020000000000000003, causing it to overwrite WAL records past the recovery point (please see attached diff output file waldiff.diff) in 000000010000000000000003. Also, performing a subsequent shutdown on the recovered server may cause the next shutdown checkpoint record to be written, again, to the previous timeline, i.e. to 000000010000000000000003. A subsequent server start will fail as the starup process will be unable to find the checkpoint in the latest timeline (000000020000000000000003) and we will get: ... LOG: invalid record length at 0/3016FB8: wante d 24, got 0 LOG: invalid primary checkpoint record PANIC: could not locate a valid checkpoint record ... RCA: When there are prepared transactions in an older timeline, in the checkpointer, a call to CheckPointTwoPhase() and subsequently to XlogReadTwoPhaseData() and subsequently to read_local_xlog_page() leads to the following line: read_upto = GetXLogReplayRecPtr(&ThisTimeLineID); GetXLogReplayRecPtr() will change ThisTimeLineID to 1, in order to read the two phase WAL records in the older timeline. This variable will remain unchanged and the checkpointer ends up writing the checkpoint record into the older WAL segment (when XLogBeginInsert() is called within CreateCheckPoint(), the value is still 1). The value is not synchronized as even if RecoveryInProgress() is called, xlogctl->SharedRecoveryState is not RECOVERY_STATE_DONE (SharedRecoveryInProgress = true in older versions) as the startup process waits for the checkpointer inside RequestCheckpoint() (since recovery_target_action='promote' involves a non-fast promotion). Thus, InitXLOGAccess() is not called and the value of ThisTimeLineID is not updated before the checkpoint record write. Since 1148e22a82e, GetXLogReplayRecPtr() is called with ThisTimeLineID instead of a local variable, within read_local_xlog_page(). PFA a small patch that fixes the problem by explicitly calling InitXLOGAccess() in CheckPointTwoPhase(), after the two phase state data is read, in order to update ThisTimeLineID to the latest timeline. It is okay to call InitXLOGAccess() as it is lightweight and would mostly be a no-op. Regards, Soumyadeep, Kevin and Jimmy VMWare
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Fix timeline assignment in checkpoints with 2PC transactions
- 1ec7162a8d34 10.17 landed
- f1d550f188e2 11.12 landed
- 4b1dd9b1ea75 12.7 landed
- 6e5ce888ad1e 13.3 landed
- 595b9cba2ab0 14.0 landed