Refactor some end-of-recovery code out of StartupXLOG().

Create a new function PerformRecoveryXLogAction() and move the
code which either writes an end-of-recovery record or requests a
checkpoint there.

Also create a new function CleanupAfterArchiveRecovery() to
perform a few tasks that we want to do after we've actually exited
archive recovery but before we start accepting new WAL writes.

More refactoring of this file is planned, but this commit is
just straightforward code movement to make StartupXLOG() a
little bit shorter and a little bit easier to understand.

Robert Haas and Amul Sul

Discussion: http://postgr.es/m/CAAJ_b97abMuq=470Wahun=aS1PHTSbStHtrjjPaD-C0YQ1AqVw@mail.gmail.com
This commit is contained in:
Robert Haas 2021-10-13 12:16:38 -04:00
parent 8c7be86883
commit 6df1543abf
1 changed files with 143 additions and 118 deletions

View File

@ -889,6 +889,8 @@ static MemoryContext walDebugCxt = NULL;
static void readRecoverySignalFile(void);
static void validateRecoveryParameters(void);
static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
static void CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI,
XLogRecPtr EndOfLog);
static bool recoveryStopsBefore(XLogReaderState *record);
static bool recoveryStopsAfter(XLogReaderState *record);
static char *getRecoveryStopReason(void);
@ -937,6 +939,7 @@ static void UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force);
static XLogRecord *ReadRecord(XLogReaderState *xlogreader,
int emode, bool fetching_ckpt);
static void CheckRecoveryConsistency(void);
static bool PerformRecoveryXLogAction(void);
static XLogRecord *ReadCheckpointRecord(XLogReaderState *xlogreader,
XLogRecPtr RecPtr, int whichChkpt, bool report);
static bool rescanLatestTimeLine(void);
@ -5731,6 +5734,88 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
(errmsg("archive recovery complete")));
}
/*
* Perform cleanup actions at the conclusion of archive recovery.
*/
static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog)
{
/*
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
ExecuteRecoveryCommand(recoveryEndCommand,
"recovery_end_command",
true);
/*
* We switched to a new timeline. Clean up segments on the old timeline.
*
* If there are any higher-numbered segments on the old timeline, remove
* them. They might contain valid WAL, but they might also be pre-allocated
* files containing garbage. In any case, they are not part of the new
* timeline's history so we don't need them.
*/
RemoveNonParentXlogFiles(EndOfLog, ThisTimeLineID);
/*
* If the switch happened in the middle of a segment, what to do with the
* last, partial segment on the old timeline? If we don't archive it, and
* the server that created the WAL never archives it either (e.g. because it
* was hit by a meteor), it will never make it to the archive. That's OK
* from our point of view, because the new segment that we created with the
* new TLI contains all the WAL from the old timeline up to the switch
* point. But if you later try to do PITR to the "missing" WAL on the old
* timeline, recovery won't find it in the archive. It's physically present
* in the new file with new TLI, but recovery won't look there when it's
* recovering to the older timeline. On the other hand, if we archive the
* partial segment, and the original server on that timeline is still
* running and archives the completed version of the same segment later, it
* will fail. (We used to do that in 9.4 and below, and it caused such
* problems).
*
* As a compromise, we rename the last segment with the .partial suffix, and
* archive it. Archive recovery will never try to read .partial segments, so
* they will normally go unused. But in the odd PITR case, the administrator
* can copy them manually to the pg_wal directory (removing the suffix).
* They can be useful in debugging, too.
*
* If a .done or .ready file already exists for the old timeline, however,
* we had already determined that the segment is complete, so we can let it
* be archived normally. (In particular, if it was restored from the archive
* to begin with, it's expected to have a .done file).
*/
if (XLogSegmentOffset(EndOfLog, wal_segment_size) != 0 &&
XLogArchivingActive())
{
char origfname[MAXFNAMELEN];
XLogSegNo endLogSegNo;
XLByteToPrevSeg(EndOfLog, endLogSegNo, wal_segment_size);
XLogFileName(origfname, EndOfLogTLI, endLogSegNo, wal_segment_size);
if (!XLogArchiveIsReadyOrDone(origfname))
{
char origpath[MAXPGPATH];
char partialfname[MAXFNAMELEN];
char partialpath[MAXPGPATH];
XLogFilePath(origpath, EndOfLogTLI, endLogSegNo, wal_segment_size);
snprintf(partialfname, MAXFNAMELEN, "%s.partial", origfname);
snprintf(partialpath, MAXPGPATH, "%s.partial", origpath);
/*
* Make sure there's no .done or .ready file for the .partial
* file.
*/
XLogArchiveCleanup(partialfname);
durable_rename(origpath, partialpath, ERROR);
XLogArchiveNotify(partialfname);
}
}
}
/*
* Extract timestamp from WAL record.
*
@ -7953,127 +8038,13 @@ StartupXLOG(void)
UpdateFullPageWrites();
LocalXLogInsertAllowed = -1;
/* Emit checkpoint or end-of-recovery record in XLOG, if required. */
if (InRecovery)
{
/*
* Perform a checkpoint to update all our recovery activity to disk.
*
* Note that we write a shutdown checkpoint rather than an on-line
* one. This is not particularly critical, but since we may be
* assigning a new TLI, using a shutdown checkpoint allows us to have
* the rule that TLI only changes in shutdown checkpoints, which
* allows some extra error checking in xlog_redo.
*
* In promotion, only create a lightweight end-of-recovery record
* instead of a full checkpoint. A checkpoint is requested later,
* after we're fully out of recovery mode and already accepting
* queries.
*/
if (ArchiveRecoveryRequested && IsUnderPostmaster &&
LocalPromoteIsTriggered)
{
promoted = true;
/*
* Insert a special WAL record to mark the end of recovery, since
* we aren't doing a checkpoint. That means that the checkpointer
* process may likely be in the middle of a time-smoothed
* restartpoint and could continue to be for minutes after this.
* That sounds strange, but the effect is roughly the same and it
* would be stranger to try to come out of the restartpoint and
* then checkpoint. We request a checkpoint later anyway, just for
* safety.
*/
CreateEndOfRecoveryRecord();
}
else
{
RequestCheckpoint(CHECKPOINT_END_OF_RECOVERY |
CHECKPOINT_IMMEDIATE |
CHECKPOINT_WAIT);
}
}
promoted = PerformRecoveryXLogAction();
/* If this is archive recovery, perform post-recovery cleanup actions. */
if (ArchiveRecoveryRequested)
{
/*
* And finally, execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
ExecuteRecoveryCommand(recoveryEndCommand,
"recovery_end_command",
true);
/*
* We switched to a new timeline. Clean up segments on the old
* timeline.
*
* If there are any higher-numbered segments on the old timeline,
* remove them. They might contain valid WAL, but they might also be
* pre-allocated files containing garbage. In any case, they are not
* part of the new timeline's history so we don't need them.
*/
RemoveNonParentXlogFiles(EndOfLog, ThisTimeLineID);
/*
* If the switch happened in the middle of a segment, what to do with
* the last, partial segment on the old timeline? If we don't archive
* it, and the server that created the WAL never archives it either
* (e.g. because it was hit by a meteor), it will never make it to the
* archive. That's OK from our point of view, because the new segment
* that we created with the new TLI contains all the WAL from the old
* timeline up to the switch point. But if you later try to do PITR to
* the "missing" WAL on the old timeline, recovery won't find it in
* the archive. It's physically present in the new file with new TLI,
* but recovery won't look there when it's recovering to the older
* timeline. On the other hand, if we archive the partial segment, and
* the original server on that timeline is still running and archives
* the completed version of the same segment later, it will fail. (We
* used to do that in 9.4 and below, and it caused such problems).
*
* As a compromise, we rename the last segment with the .partial
* suffix, and archive it. Archive recovery will never try to read
* .partial segments, so they will normally go unused. But in the odd
* PITR case, the administrator can copy them manually to the pg_wal
* directory (removing the suffix). They can be useful in debugging,
* too.
*
* If a .done or .ready file already exists for the old timeline,
* however, we had already determined that the segment is complete, so
* we can let it be archived normally. (In particular, if it was
* restored from the archive to begin with, it's expected to have a
* .done file).
*/
if (XLogSegmentOffset(EndOfLog, wal_segment_size) != 0 &&
XLogArchivingActive())
{
char origfname[MAXFNAMELEN];
XLogSegNo endLogSegNo;
XLByteToPrevSeg(EndOfLog, endLogSegNo, wal_segment_size);
XLogFileName(origfname, EndOfLogTLI, endLogSegNo, wal_segment_size);
if (!XLogArchiveIsReadyOrDone(origfname))
{
char origpath[MAXPGPATH];
char partialfname[MAXFNAMELEN];
char partialpath[MAXPGPATH];
XLogFilePath(origpath, EndOfLogTLI, endLogSegNo, wal_segment_size);
snprintf(partialfname, MAXFNAMELEN, "%s.partial", origfname);
snprintf(partialpath, MAXPGPATH, "%s.partial", origpath);
/*
* Make sure there's no .done or .ready file for the .partial
* file.
*/
XLogArchiveCleanup(partialfname);
durable_rename(origpath, partialpath, ERROR);
XLogArchiveNotify(partialfname);
}
}
}
CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog);
/*
* Preallocate additional log files, if wanted.
@ -8282,6 +8253,60 @@ CheckRecoveryConsistency(void)
}
}
/*
* Perform whatever XLOG actions are necessary at end of REDO.
*
* The goal here is to make sure that we'll be able to recover properly if
* we crash again. If we choose to write a checkpoint, we'll write a shutdown
* checkpoint rather than an on-line one. This is not particularly critical,
* but since we may be assigning a new TLI, using a shutdown checkpoint allows
* us to have the rule that TLI only changes in shutdown checkpoints, which
* allows some extra error checking in xlog_redo.
*/
static bool
PerformRecoveryXLogAction(void)
{
bool promoted = false;
/*
* Perform a checkpoint to update all our recovery activity to disk.
*
* Note that we write a shutdown checkpoint rather than an on-line one. This
* is not particularly critical, but since we may be assigning a new TLI,
* using a shutdown checkpoint allows us to have the rule that TLI only
* changes in shutdown checkpoints, which allows some extra error checking
* in xlog_redo.
*
* In promotion, only create a lightweight end-of-recovery record instead of
* a full checkpoint. A checkpoint is requested later, after we're fully out
* of recovery mode and already accepting queries.
*/
if (ArchiveRecoveryRequested && IsUnderPostmaster &&
LocalPromoteIsTriggered)
{
promoted = true;
/*
* Insert a special WAL record to mark the end of recovery, since we
* aren't doing a checkpoint. That means that the checkpointer process
* may likely be in the middle of a time-smoothed restartpoint and could
* continue to be for minutes after this. That sounds strange, but the
* effect is roughly the same and it would be stranger to try to come
* out of the restartpoint and then checkpoint. We request a checkpoint
* later anyway, just for safety.
*/
CreateEndOfRecoveryRecord();
}
else
{
RequestCheckpoint(CHECKPOINT_END_OF_RECOVERY |
CHECKPOINT_IMMEDIATE |
CHECKPOINT_WAIT);
}
return promoted;
}
/*
* Is the system still in recovery?
*