Skip to content

Commit ee99427

Browse files
committed
Delay reading timeline history file until it's fetched from master.
Streaming replication can fetch any missing timeline history files from the master, but recovery would read the timeline history file for the target timeline before reading the checkpoint record, and before walreceiver has had a chance to fetch it from the master. Delay reading it, and the sanity checks involving timeline history, until after reading the checkpoint record. There is at least one scenario where this makes a difference: if you take a base backup from a standby server right after a timeline switch, the WAL segment containing the initial checkpoint record will begin with an older timeline ID. Without the timeline history file, recovering that file will fail as the older timeline ID is not recognized to be an ancestor of the target timeline. If you try to recover from such a backup, using only streaming replication to fetch the WAL, this patch is required for that to work.
1 parent bcbe992 commit ee99427

File tree

2 files changed

+90
-55
lines changed

2 files changed

+90
-55
lines changed

src/backend/access/transam/xlog.c

+87-53
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,7 @@ XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source)
26752675
char path[MAXPGPATH];
26762676
ListCell *cell;
26772677
int fd;
2678+
List *tles;
26782679

26792680
/*
26802681
* Loop looking for a suitable timeline ID: we might need to read any of
@@ -2685,8 +2686,21 @@ XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source)
26852686
* to go backwards; this prevents us from picking up the wrong file when a
26862687
* parent timeline extends to higher segment numbers than the child we
26872688
* want to read.
2688-
*/
2689-
foreach(cell, expectedTLEs)
2689+
*
2690+
* If we haven't read the timeline history file yet, read it now, so that
2691+
* we know which TLIs to scan. We don't save the list in expectedTLEs,
2692+
* however, unless we actually find a valid segment. That way if there is
2693+
* neither a timeline history file nor a WAL segment in the archive, and
2694+
* streaming replication is set up, we'll read the timeline history file
2695+
* streamed from the master when we start streaming, instead of recovering
2696+
* with a dummy history generated here.
2697+
*/
2698+
if (expectedTLEs)
2699+
tles = expectedTLEs;
2700+
else
2701+
tles = readTimeLineHistory(recoveryTargetTLI);
2702+
2703+
foreach(cell, tles)
26902704
{
26912705
TimeLineID tli = ((TimeLineHistoryEntry *) lfirst(cell))->tli;
26922706

@@ -2699,6 +2713,8 @@ XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source)
26992713
if (fd != -1)
27002714
{
27012715
elog(DEBUG1, "got WAL segment from archive");
2716+
if (!expectedTLEs)
2717+
expectedTLEs = tles;
27022718
return fd;
27032719
}
27042720
}
@@ -2707,7 +2723,11 @@ XLogFileReadAnyTLI(XLogSegNo segno, int emode, int source)
27072723
{
27082724
fd = XLogFileRead(segno, emode, tli, XLOG_FROM_PG_XLOG, true);
27092725
if (fd != -1)
2726+
{
2727+
if (!expectedTLEs)
2728+
expectedTLEs = tles;
27102729
return fd;
2730+
}
27112731
}
27122732
}
27132733

@@ -5279,49 +5299,6 @@ StartupXLOG(void)
52795299
*/
52805300
readRecoveryCommandFile();
52815301

5282-
/* Now we can determine the list of expected TLIs */
5283-
expectedTLEs = readTimeLineHistory(recoveryTargetTLI);
5284-
5285-
/*
5286-
* If the location of the checkpoint record is not on the expected
5287-
* timeline in the history of the requested timeline, we cannot proceed:
5288-
* the backup is not part of the history of the requested timeline.
5289-
*/
5290-
if (tliOfPointInHistory(ControlFile->checkPoint, expectedTLEs) !=
5291-
ControlFile->checkPointCopy.ThisTimeLineID)
5292-
{
5293-
XLogRecPtr switchpoint;
5294-
5295-
/*
5296-
* tliSwitchPoint will throw an error if the checkpoint's timeline
5297-
* is not in expectedTLEs at all.
5298-
*/
5299-
switchpoint = tliSwitchPoint(ControlFile->checkPointCopy.ThisTimeLineID, expectedTLEs);
5300-
ereport(FATAL,
5301-
(errmsg("requested timeline %u is not a child of this server's history",
5302-
recoveryTargetTLI),
5303-
errdetail("Latest checkpoint is at %X/%X on timeline %u, but in the history of the requested timeline, the server forked off from that timeline at %X/%X",
5304-
(uint32) (ControlFile->checkPoint >> 32),
5305-
(uint32) ControlFile->checkPoint,
5306-
ControlFile->checkPointCopy.ThisTimeLineID,
5307-
(uint32) (switchpoint >> 32),
5308-
(uint32) switchpoint)));
5309-
}
5310-
5311-
/*
5312-
* The min recovery point should be part of the requested timeline's
5313-
* history, too.
5314-
*/
5315-
if (!XLogRecPtrIsInvalid(ControlFile->minRecoveryPoint) &&
5316-
tliOfPointInHistory(ControlFile->minRecoveryPoint - 1, expectedTLEs) !=
5317-
ControlFile->minRecoveryPointTLI)
5318-
ereport(FATAL,
5319-
(errmsg("requested timeline %u does not contain minimum recovery point %X/%X on timeline %u",
5320-
recoveryTargetTLI,
5321-
(uint32) (ControlFile->minRecoveryPoint >> 32),
5322-
(uint32) ControlFile->minRecoveryPoint,
5323-
ControlFile->minRecoveryPointTLI)));
5324-
53255302
/*
53265303
* Save archive_cleanup_command in shared memory so that other processes
53275304
* can see it.
@@ -5443,6 +5420,47 @@ StartupXLOG(void)
54435420
wasShutdown = (record->xl_info == XLOG_CHECKPOINT_SHUTDOWN);
54445421
}
54455422

5423+
/*
5424+
* If the location of the checkpoint record is not on the expected
5425+
* timeline in the history of the requested timeline, we cannot proceed:
5426+
* the backup is not part of the history of the requested timeline.
5427+
*/
5428+
Assert(expectedTLEs); /* was initialized by reading checkpoint record */
5429+
if (tliOfPointInHistory(checkPointLoc, expectedTLEs) !=
5430+
checkPoint.ThisTimeLineID)
5431+
{
5432+
XLogRecPtr switchpoint;
5433+
5434+
/*
5435+
* tliSwitchPoint will throw an error if the checkpoint's timeline
5436+
* is not in expectedTLEs at all.
5437+
*/
5438+
switchpoint = tliSwitchPoint(ControlFile->checkPointCopy.ThisTimeLineID, expectedTLEs);
5439+
ereport(FATAL,
5440+
(errmsg("requested timeline %u is not a child of this server's history",
5441+
recoveryTargetTLI),
5442+
errdetail("Latest checkpoint is at %X/%X on timeline %u, but in the history of the requested timeline, the server forked off from that timeline at %X/%X",
5443+
(uint32) (ControlFile->checkPoint >> 32),
5444+
(uint32) ControlFile->checkPoint,
5445+
ControlFile->checkPointCopy.ThisTimeLineID,
5446+
(uint32) (switchpoint >> 32),
5447+
(uint32) switchpoint)));
5448+
}
5449+
5450+
/*
5451+
* The min recovery point should be part of the requested timeline's
5452+
* history, too.
5453+
*/
5454+
if (!XLogRecPtrIsInvalid(ControlFile->minRecoveryPoint) &&
5455+
tliOfPointInHistory(ControlFile->minRecoveryPoint - 1, expectedTLEs) !=
5456+
ControlFile->minRecoveryPointTLI)
5457+
ereport(FATAL,
5458+
(errmsg("requested timeline %u does not contain minimum recovery point %X/%X on timeline %u",
5459+
recoveryTargetTLI,
5460+
(uint32) (ControlFile->minRecoveryPoint >> 32),
5461+
(uint32) ControlFile->minRecoveryPoint,
5462+
ControlFile->minRecoveryPointTLI)));
5463+
54465464
LastRec = RecPtr = checkPointLoc;
54475465

54485466
ereport(DEBUG1,
@@ -9569,13 +9587,24 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
95699587
*/
95709588
if (PrimaryConnInfo)
95719589
{
9572-
XLogRecPtr ptr = fetching_ckpt ? RedoStartLSN : RecPtr;
9573-
TimeLineID tli = tliOfPointInHistory(ptr, expectedTLEs);
9590+
XLogRecPtr ptr;
9591+
TimeLineID tli;
95749592

9575-
if (curFileTLI > 0 && tli < curFileTLI)
9576-
elog(ERROR, "according to history file, WAL location %X/%X belongs to timeline %u, but previous recovered WAL file came from timeline %u",
9577-
(uint32) (ptr >> 32), (uint32) ptr,
9578-
tli, curFileTLI);
9593+
if (fetching_ckpt)
9594+
{
9595+
ptr = RedoStartLSN;
9596+
tli = ControlFile->checkPointCopy.ThisTimeLineID;
9597+
}
9598+
else
9599+
{
9600+
ptr = RecPtr;
9601+
tli = tliOfPointInHistory(ptr, expectedTLEs);
9602+
9603+
if (curFileTLI > 0 && tli < curFileTLI)
9604+
elog(ERROR, "according to history file, WAL location %X/%X belongs to timeline %u, but previous recovered WAL file came from timeline %u",
9605+
(uint32) (ptr >> 32), (uint32) ptr,
9606+
tli, curFileTLI);
9607+
}
95799608
curFileTLI = tli;
95809609
RequestXLogStreaming(curFileTLI, ptr, PrimaryConnInfo);
95819610
}
@@ -9739,11 +9768,16 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
97399768
{
97409769
/*
97419770
* Great, streamed far enough. Open the file if it's not
9742-
* open already. Use XLOG_FROM_STREAM so that source info
9743-
* is set correctly and XLogReceiptTime isn't changed.
9771+
* open already. Also read the timeline history file if
9772+
* we haven't initialized timeline history yet; it should
9773+
* be streamed over and present in pg_xlog by now. Use
9774+
* XLOG_FROM_STREAM so that source info is set correctly
9775+
* and XLogReceiptTime isn't changed.
97449776
*/
97459777
if (readFile < 0)
97469778
{
9779+
if (!expectedTLEs)
9780+
expectedTLEs = readTimeLineHistory(receiveTLI);
97479781
readFile = XLogFileRead(readSegNo, PANIC,
97489782
receiveTLI,
97499783
XLOG_FROM_STREAM, false);

src/backend/replication/walreceiver.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ WalReceiverMain(void)
338338
* ensure that a unique timeline id is chosen in every case, but let's
339339
* avoid the confusion of timeline id collisions where we can.
340340
*/
341-
WalRcvFetchTimeLineHistoryFiles(startpointTLI + 1, primaryTLI);
341+
WalRcvFetchTimeLineHistoryFiles(startpointTLI, primaryTLI);
342342

343343
/*
344344
* Start streaming.
@@ -627,7 +627,8 @@ WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
627627

628628
for (tli = first; tli <= last; tli++)
629629
{
630-
if (!existsTimeLineHistory(tli))
630+
/* there's no history file for timeline 1 */
631+
if (tli != 1 && !existsTimeLineHistory(tli))
631632
{
632633
char *fname;
633634
char *content;

0 commit comments

Comments
 (0)