Skip to content

Commit 56535dc

Browse files
committed
Prevent references to invalid relation pages after fresh promotion
If a standby crashes after promotion before having completed its first post-recovery checkpoint, then the minimal recovery point which marks the LSN position where the cluster is able to reach consistency may be set to a position older than the first end-of-recovery checkpoint while all the WAL available should be replayed. This leads to the instance thinking that it contains inconsistent pages, causing a PANIC and a hard instance crash even if all the WAL available has not been replayed for certain sets of records replayed. When in crash recovery, minRecoveryPoint is expected to always be set to InvalidXLogRecPtr, which forces the recovery to replay all the WAL available, so this commit makes sure that the local copy of minRecoveryPoint from the control file is initialized properly and stays as it is while crash recovery is performed. Once switching to archive recovery or if crash recovery finishes, then the local copy minRecoveryPoint can be safely updated. Pavan Deolasee has reported and diagnosed the failure in the first place, and the base fix idea to rely on the local copy of minRecoveryPoint comes from Kyotaro Horiguchi, which has been expanded into a full-fledged patch by me. The test included in this commit has been written by Álvaro Herrera and Pavan Deolasee, which I have modified to make it faster and more reliable with sleep phases. Backpatch down to all supported versions where the bug appears, aka 9.3 which is where the end-of-recovery checkpoint is not run by the startup process anymore. The test gets easily supported down to 10, still it has been tested on all branches. Reported-by: Pavan Deolasee Diagnosed-by: Pavan Deolasee Reviewed-by: Pavan Deolasee, Kyotaro Horiguchi Author: Michael Paquier, Kyotaro Horiguchi, Pavan Deolasee, Álvaro Herrera Discussion: https://postgr.es/m/CABOikdPOewjNL=05K5CbNMxnNtXnQjhTx2F--4p4ruorCjukbA@mail.gmail.com
1 parent 7ffe012 commit 56535dc

File tree

1 file changed

+70
-31
lines changed
  • src/backend/access/transam

1 file changed

+70
-31
lines changed

src/backend/access/transam/xlog.c

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,14 @@ static XLogSource XLogReceiptSource = 0; /* XLOG_FROM_* code */
642642
static XLogRecPtr ReadRecPtr; /* start of last record read */
643643
static XLogRecPtr EndRecPtr; /* end+1 of last record read */
644644

645-
static XLogRecPtr minRecoveryPoint; /* local copy of
646-
* ControlFile->minRecoveryPoint */
645+
/*
646+
* Local copies of equivalent fields in the control file. When running
647+
* crash recovery, minRecoveryPoint is set to InvalidXLogRecPtr as we
648+
* expect to replay all the WAL available, and updateMinRecoveryPoint is
649+
* switched to false to prevent any updates while replaying records.
650+
* Those values are kept consistent as long as crash recovery runs.
651+
*/
652+
static XLogRecPtr minRecoveryPoint;
647653
static TimeLineID minRecoveryPointTLI;
648654
static bool updateMinRecoveryPoint = true;
649655

@@ -1849,20 +1855,26 @@ UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force)
18491855
if (!updateMinRecoveryPoint || (!force && lsn <= minRecoveryPoint))
18501856
return;
18511857

1858+
/*
1859+
* An invalid minRecoveryPoint means that we need to recover all the WAL,
1860+
* i.e., we're doing crash recovery. We never modify the control file's
1861+
* value in that case, so we can short-circuit future checks here too. The
1862+
* local values of minRecoveryPoint and minRecoveryPointTLI should not be
1863+
* updated until crash recovery finishes.
1864+
*/
1865+
if (XLogRecPtrIsInvalid(minRecoveryPoint))
1866+
{
1867+
updateMinRecoveryPoint = false;
1868+
return;
1869+
}
1870+
18521871
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
18531872

18541873
/* update local copy */
18551874
minRecoveryPoint = ControlFile->minRecoveryPoint;
18561875
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
18571876

1858-
/*
1859-
* An invalid minRecoveryPoint means that we need to recover all the WAL,
1860-
* i.e., we're doing crash recovery. We never modify the control file's
1861-
* value in that case, so we can short-circuit future checks here too.
1862-
*/
1863-
if (minRecoveryPoint == 0)
1864-
updateMinRecoveryPoint = false;
1865-
else if (force || minRecoveryPoint < lsn)
1877+
if (force || minRecoveryPoint < lsn)
18661878
{
18671879
/* use volatile pointer to prevent code rearrangement */
18681880
volatile XLogCtlData *xlogctl = XLogCtl;
@@ -2202,7 +2214,16 @@ XLogNeedsFlush(XLogRecPtr record)
22022214
*/
22032215
if (RecoveryInProgress())
22042216
{
2205-
/* Quick exit if already known updated */
2217+
/*
2218+
* An invalid minRecoveryPoint means that we need to recover all the
2219+
* WAL, i.e., we're doing crash recovery. We never modify the control
2220+
* file's value in that case, so we can short-circuit future checks
2221+
* here too.
2222+
*/
2223+
if (XLogRecPtrIsInvalid(minRecoveryPoint))
2224+
updateMinRecoveryPoint = false;
2225+
2226+
/* Quick exit if already known to be updated or cannot be updated */
22062227
if (record <= minRecoveryPoint || !updateMinRecoveryPoint)
22072228
return false;
22082229

@@ -2216,20 +2237,8 @@ XLogNeedsFlush(XLogRecPtr record)
22162237
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
22172238
LWLockRelease(ControlFileLock);
22182239

2219-
/*
2220-
* An invalid minRecoveryPoint means that we need to recover all the
2221-
* WAL, i.e., we're doing crash recovery. We never modify the control
2222-
* file's value in that case, so we can short-circuit future checks
2223-
* here too.
2224-
*/
2225-
if (minRecoveryPoint == 0)
2226-
updateMinRecoveryPoint = false;
2227-
22282240
/* check again */
2229-
if (record <= minRecoveryPoint || !updateMinRecoveryPoint)
2230-
return false;
2231-
else
2232-
return true;
2241+
return record > minRecoveryPoint;
22332242
}
22342243

22352244
/* Quick exit if already known flushed */
@@ -3371,6 +3380,12 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
33713380
minRecoveryPoint = ControlFile->minRecoveryPoint;
33723381
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
33733382

3383+
/*
3384+
* The startup process can update its local copy of
3385+
* minRecoveryPoint from this point.
3386+
*/
3387+
updateMinRecoveryPoint = true;
3388+
33743389
UpdateControlFile();
33753390
LWLockRelease(ControlFileLock);
33763391

@@ -5492,9 +5507,26 @@ StartupXLOG(void)
54925507
/* No need to hold ControlFileLock yet, we aren't up far enough */
54935508
UpdateControlFile();
54945509

5495-
/* initialize our local copy of minRecoveryPoint */
5496-
minRecoveryPoint = ControlFile->minRecoveryPoint;
5497-
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
5510+
/*
5511+
* Initialize our local copy of minRecoveryPoint. When doing crash
5512+
* recovery we want to replay up to the end of WAL. Particularly, in
5513+
* the case of a promoted standby minRecoveryPoint value in the
5514+
* control file is only updated after the first checkpoint. However,
5515+
* if the instance crashes before the first post-recovery checkpoint
5516+
* is completed then recovery will use a stale location causing the
5517+
* startup process to think that there are still invalid page
5518+
* references when checking for data consistency.
5519+
*/
5520+
if (InArchiveRecovery)
5521+
{
5522+
minRecoveryPoint = ControlFile->minRecoveryPoint;
5523+
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
5524+
}
5525+
else
5526+
{
5527+
minRecoveryPoint = InvalidXLogRecPtr;
5528+
minRecoveryPointTLI = 0;
5529+
}
54985530

54995531
/*
55005532
* Reset pgstat data, because it may be invalid after recovery.
@@ -6315,6 +6347,8 @@ CheckRecoveryConsistency(void)
63156347
if (XLogRecPtrIsInvalid(minRecoveryPoint))
63166348
return;
63176349

6350+
Assert(InArchiveRecovery);
6351+
63186352
/*
63196353
* assume that we are called in the startup process, and hence don't need
63206354
* a lock to read lastReplayedEndRecPtr
@@ -8448,11 +8482,16 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
84488482
* This is particularly important if wal_level was set to 'archive'
84498483
* before, and is now 'hot_standby', to ensure you don't run queries
84508484
* against the WAL preceding the wal_level change. Same applies to
8451-
* decreasing max_* settings.
8485+
* decreasing max_* settings. The local copies cannot be updated as
8486+
* long as crash recovery is happening and we expect all the WAL to
8487+
* be replayed.
84528488
*/
8453-
minRecoveryPoint = ControlFile->minRecoveryPoint;
8454-
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
8455-
if (minRecoveryPoint != 0 && minRecoveryPoint < lsn)
8489+
if (InArchiveRecovery)
8490+
{
8491+
minRecoveryPoint = ControlFile->minRecoveryPoint;
8492+
minRecoveryPointTLI = ControlFile->minRecoveryPointTLI;
8493+
}
8494+
if (minRecoveryPoint != InvalidXLogRecPtr && minRecoveryPoint < lsn)
84568495
{
84578496
ControlFile->minRecoveryPoint = lsn;
84588497
ControlFile->minRecoveryPointTLI = ThisTimeLineID;

0 commit comments

Comments
 (0)