Skip to content

Commit c030db3

Browse files
committed
Rework order of end-of-recovery actions to delay timeline history write
A critical failure in some of the end-of-recovery actions before the end-of-recovery record is written can cause PostgreSQL to react inconsistently with the rest of the cluster in the event of a crash before the final record is written. Two such failures are for example an error while processing a two-phase state files or when operating on recovery.conf. With this commit, the failures are still considered FATAL, but the write of the timeline history file is delayed as much as possible so as the window between the moment the file is written and the end-of-recovery record is generated gets minimized. This way, in the event of a crash or a failure, the new timeline decided at promotion will not seem taken by other nodes in the cluster. It is not really possible to reduce to zero this window, hence one could still see failures if a crash happens between the history file write and the end-of-recovery record, so any future code should be careful when adding new end-of-recovery actions. The original report from Magnus Hagander mentioned a renamed recovery.conf as original end-of-recovery failure which caused a timeline to be seen as taken but the subsequent processing on the now-missing recovery.conf cause the startup process to issue stop on FATAL, which at follow-up startup made the system inconsistent because of on-disk changes which already happened. Processing of two-phase state files still needs some work as corrupted entries are simply ignored now. This is left as a future item and this commit fixes the original complain. Reported-by: Magnus Hagander Author: Heikki Linnakangas Reviewed-by: Alexander Korotkov, Michael Paquier, David Steele Discussion: https://postgr.es/m/CABUevEz09XY2EevA2dLjPCY-C5UO4Hq=XxmXLmF6ipNFecbShQ@mail.gmail.com
1 parent 4230397 commit c030db3

File tree

1 file changed

+25
-12
lines changed
  • src/backend/access/transam

1 file changed

+25
-12
lines changed

src/backend/access/transam/xlog.c

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7439,6 +7439,13 @@ StartupXLOG(void)
74397439
}
74407440
}
74417441

7442+
/*
7443+
* Pre-scan prepared transactions to find out the range of XIDs present.
7444+
* This information is not quite needed yet, but it is positioned here so
7445+
* as potential problems are detected before any on-disk change is done.
7446+
*/
7447+
oldestActiveXID = PrescanPreparedTransactions(NULL, NULL);
7448+
74427449
/*
74437450
* Consider whether we need to assign a new timeline ID.
74447451
*
@@ -7493,6 +7500,24 @@ StartupXLOG(void)
74937500
else
74947501
snprintf(reason, sizeof(reason), "no recovery target specified");
74957502

7503+
/*
7504+
* We are now done reading the old WAL. Turn off archive fetching if
7505+
* it was active, and make a writable copy of the last WAL segment.
7506+
* (Note that we also have a copy of the last block of the old WAL in
7507+
* readBuf; we will use that below.)
7508+
*/
7509+
exitArchiveRecovery(EndOfLogTLI, EndOfLog);
7510+
7511+
/*
7512+
* Write the timeline history file, and have it archived. After this
7513+
* point (or rather, as soon as the file is archived), the timeline
7514+
* will appear as "taken" in the WAL archive and to any standby
7515+
* servers. If we crash before actually switching to the new
7516+
* timeline, standby servers will nevertheless think that we switched
7517+
* to the new timeline, and will try to connect to the new timeline.
7518+
* To minimize the window for that, try to do as little as possible
7519+
* between here and writing the end-of-recovery record.
7520+
*/
74967521
writeTimeLineHistory(ThisTimeLineID, recoveryTargetTLI,
74977522
EndRecPtr, reason);
74987523
}
@@ -7501,15 +7526,6 @@ StartupXLOG(void)
75017526
XLogCtl->ThisTimeLineID = ThisTimeLineID;
75027527
XLogCtl->PrevTimeLineID = PrevTimeLineID;
75037528

7504-
/*
7505-
* We are now done reading the old WAL. Turn off archive fetching if it
7506-
* was active, and make a writable copy of the last WAL segment. (Note
7507-
* that we also have a copy of the last block of the old WAL in readBuf;
7508-
* we will use that below.)
7509-
*/
7510-
if (ArchiveRecoveryRequested)
7511-
exitArchiveRecovery(EndOfLogTLI, EndOfLog);
7512-
75137529
/*
75147530
* Prepare to write WAL starting at EndOfLog location, and init xlog
75157531
* buffer cache using the block containing the last record from the
@@ -7562,9 +7578,6 @@ StartupXLOG(void)
75627578
XLogCtl->LogwrtRqst.Write = EndOfLog;
75637579
XLogCtl->LogwrtRqst.Flush = EndOfLog;
75647580

7565-
/* Pre-scan prepared transactions to find out the range of XIDs present */
7566-
oldestActiveXID = PrescanPreparedTransactions(NULL, NULL);
7567-
75687581
/*
75697582
* Update full_page_writes in shared memory and write an XLOG_FPW_CHANGE
75707583
* record before resource manager writes cleanup WAL records or checkpoint

0 commit comments

Comments
 (0)