|
|
|
@ -156,6 +156,14 @@ HotStandbyState standbyState = STANDBY_DISABLED;
|
|
|
|
|
|
|
|
|
|
static XLogRecPtr LastRec;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* During recovery, lastFullPageWrites keeps track of full_page_writes that
|
|
|
|
|
* the replayed WAL records indicate. It's initialized with full_page_writes
|
|
|
|
|
* that the recovery starting checkpoint record indicates, and then updated
|
|
|
|
|
* each time XLOG_FPW_CHANGE record is replayed.
|
|
|
|
|
*/
|
|
|
|
|
static bool lastFullPageWrites;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Local copy of SharedRecoveryInProgress variable. True actually means "not
|
|
|
|
|
* known, need to check the shared state".
|
|
|
|
@ -354,6 +362,15 @@ typedef struct XLogCtlInsert
|
|
|
|
|
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
|
|
|
|
|
bool forcePageWrites; /* forcing full-page writes for PITR? */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* fullPageWrites is the master copy used by all backends to determine
|
|
|
|
|
* whether to write full-page to WAL, instead of using process-local
|
|
|
|
|
* one. This is required because, when full_page_writes is changed
|
|
|
|
|
* by SIGHUP, we must WAL-log it before it actually affects
|
|
|
|
|
* WAL-logging by backends. Checkpointer sets at startup or after SIGHUP.
|
|
|
|
|
*/
|
|
|
|
|
bool fullPageWrites;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* exclusiveBackup is true if a backup started with pg_start_backup() is
|
|
|
|
|
* in progress, and nonExclusiveBackups is a counter indicating the number
|
|
|
|
@ -460,6 +477,12 @@ typedef struct XLogCtlData
|
|
|
|
|
/* Are we requested to pause recovery? */
|
|
|
|
|
bool recoveryPause;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* lastFpwDisableRecPtr points to the start of the last replayed
|
|
|
|
|
* XLOG_FPW_CHANGE record that instructs full_page_writes is disabled.
|
|
|
|
|
*/
|
|
|
|
|
XLogRecPtr lastFpwDisableRecPtr;
|
|
|
|
|
|
|
|
|
|
slock_t info_lck; /* locks shared variables shown above */
|
|
|
|
|
} XLogCtlData;
|
|
|
|
|
|
|
|
|
@ -663,7 +686,7 @@ static void xlog_outrec(StringInfo buf, XLogRecord *record);
|
|
|
|
|
#endif
|
|
|
|
|
static void pg_start_backup_callback(int code, Datum arg);
|
|
|
|
|
static bool read_backup_label(XLogRecPtr *checkPointLoc,
|
|
|
|
|
bool *backupEndRequired);
|
|
|
|
|
bool *backupEndRequired, bool *backupFromStandby);
|
|
|
|
|
static void rm_redo_error_callback(void *arg);
|
|
|
|
|
static int get_sync_bit(int method);
|
|
|
|
|
|
|
|
|
@ -708,7 +731,8 @@ XLogInsert(RmgrId rmid, uint8 info, XLogRecData *rdata)
|
|
|
|
|
unsigned i;
|
|
|
|
|
bool updrqst;
|
|
|
|
|
bool doPageWrites;
|
|
|
|
|
bool isLogSwitch = (rmid == RM_XLOG_ID && info == XLOG_SWITCH);
|
|
|
|
|
bool isLogSwitch = false;
|
|
|
|
|
bool fpwChange = false;
|
|
|
|
|
uint8 info_orig = info;
|
|
|
|
|
|
|
|
|
|
/* cross-check on whether we should be here or not */
|
|
|
|
@ -722,11 +746,30 @@ XLogInsert(RmgrId rmid, uint8 info, XLogRecData *rdata)
|
|
|
|
|
TRACE_POSTGRESQL_XLOG_INSERT(rmid, info);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* In bootstrap mode, we don't actually log anything but XLOG resources;
|
|
|
|
|
* return a phony record pointer.
|
|
|
|
|
* Handle special cases/records.
|
|
|
|
|
*/
|
|
|
|
|
if (IsBootstrapProcessingMode() && rmid != RM_XLOG_ID)
|
|
|
|
|
if (rmid == RM_XLOG_ID)
|
|
|
|
|
{
|
|
|
|
|
switch (info)
|
|
|
|
|
{
|
|
|
|
|
case XLOG_SWITCH:
|
|
|
|
|
isLogSwitch = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case XLOG_FPW_CHANGE:
|
|
|
|
|
fpwChange = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (IsBootstrapProcessingMode())
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* In bootstrap mode, we don't actually log anything but XLOG resources;
|
|
|
|
|
* return a phony record pointer.
|
|
|
|
|
*/
|
|
|
|
|
RecPtr.xlogid = 0;
|
|
|
|
|
RecPtr.xrecoff = SizeOfXLogLongPHD; /* start of 1st chkpt record */
|
|
|
|
|
return RecPtr;
|
|
|
|
@ -756,10 +799,10 @@ begin:;
|
|
|
|
|
/*
|
|
|
|
|
* Decide if we need to do full-page writes in this XLOG record: true if
|
|
|
|
|
* full_page_writes is on or we have a PITR request for it. Since we
|
|
|
|
|
* don't yet have the insert lock, forcePageWrites could change under us,
|
|
|
|
|
* but we'll recheck it once we have the lock.
|
|
|
|
|
* don't yet have the insert lock, fullPageWrites and forcePageWrites
|
|
|
|
|
* could change under us, but we'll recheck them once we have the lock.
|
|
|
|
|
*/
|
|
|
|
|
doPageWrites = fullPageWrites || Insert->forcePageWrites;
|
|
|
|
|
doPageWrites = Insert->fullPageWrites || Insert->forcePageWrites;
|
|
|
|
|
|
|
|
|
|
len = 0;
|
|
|
|
|
for (rdt = rdata;;)
|
|
|
|
@ -939,12 +982,12 @@ begin:;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Also check to see if forcePageWrites was just turned on; if we weren't
|
|
|
|
|
* already doing full-page writes then go back and recompute. (If it was
|
|
|
|
|
* just turned off, we could recompute the record without full pages, but
|
|
|
|
|
* we choose not to bother.)
|
|
|
|
|
* Also check to see if fullPageWrites or forcePageWrites was just turned on;
|
|
|
|
|
* if we weren't already doing full-page writes then go back and recompute.
|
|
|
|
|
* (If it was just turned off, we could recompute the record without full pages,
|
|
|
|
|
* but we choose not to bother.)
|
|
|
|
|
*/
|
|
|
|
|
if (Insert->forcePageWrites && !doPageWrites)
|
|
|
|
|
if ((Insert->fullPageWrites || Insert->forcePageWrites) && !doPageWrites)
|
|
|
|
|
{
|
|
|
|
|
/* Oops, must redo it with full-page data. */
|
|
|
|
|
LWLockRelease(WALInsertLock);
|
|
|
|
@ -1189,6 +1232,15 @@ begin:;
|
|
|
|
|
WriteRqst = XLogCtl->xlblocks[curridx];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the record is an XLOG_FPW_CHANGE, we update full_page_writes
|
|
|
|
|
* in shared memory before releasing WALInsertLock. This ensures that
|
|
|
|
|
* an XLOG_FPW_CHANGE record precedes any WAL record affected
|
|
|
|
|
* by this change of full_page_writes.
|
|
|
|
|
*/
|
|
|
|
|
if (fpwChange)
|
|
|
|
|
Insert->fullPageWrites = fullPageWrites;
|
|
|
|
|
|
|
|
|
|
LWLockRelease(WALInsertLock);
|
|
|
|
|
|
|
|
|
|
if (updrqst)
|
|
|
|
@ -5147,6 +5199,7 @@ BootStrapXLOG(void)
|
|
|
|
|
checkPoint.redo.xlogid = 0;
|
|
|
|
|
checkPoint.redo.xrecoff = XLogSegSize + SizeOfXLogLongPHD;
|
|
|
|
|
checkPoint.ThisTimeLineID = ThisTimeLineID;
|
|
|
|
|
checkPoint.fullPageWrites = fullPageWrites;
|
|
|
|
|
checkPoint.nextXidEpoch = 0;
|
|
|
|
|
checkPoint.nextXid = FirstNormalTransactionId;
|
|
|
|
|
checkPoint.nextOid = FirstBootstrapObjectId;
|
|
|
|
@ -5961,6 +6014,8 @@ StartupXLOG(void)
|
|
|
|
|
uint32 freespace;
|
|
|
|
|
TransactionId oldestActiveXID;
|
|
|
|
|
bool backupEndRequired = false;
|
|
|
|
|
bool backupFromStandby = false;
|
|
|
|
|
DBState dbstate_at_startup;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read control file and check XLOG status looks valid.
|
|
|
|
@ -6094,7 +6149,8 @@ StartupXLOG(void)
|
|
|
|
|
if (StandbyMode)
|
|
|
|
|
OwnLatch(&XLogCtl->recoveryWakeupLatch);
|
|
|
|
|
|
|
|
|
|
if (read_backup_label(&checkPointLoc, &backupEndRequired))
|
|
|
|
|
if (read_backup_label(&checkPointLoc, &backupEndRequired,
|
|
|
|
|
&backupFromStandby))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* When a backup_label file is present, we want to roll forward from
|
|
|
|
@ -6210,6 +6266,8 @@ StartupXLOG(void)
|
|
|
|
|
*/
|
|
|
|
|
ThisTimeLineID = checkPoint.ThisTimeLineID;
|
|
|
|
|
|
|
|
|
|
lastFullPageWrites = checkPoint.fullPageWrites;
|
|
|
|
|
|
|
|
|
|
RedoRecPtr = XLogCtl->Insert.RedoRecPtr = checkPoint.redo;
|
|
|
|
|
|
|
|
|
|
if (XLByteLT(RecPtr, checkPoint.redo))
|
|
|
|
@ -6250,6 +6308,7 @@ StartupXLOG(void)
|
|
|
|
|
* pg_control with any minimum recovery stop point obtained from a
|
|
|
|
|
* backup history file.
|
|
|
|
|
*/
|
|
|
|
|
dbstate_at_startup = ControlFile->state;
|
|
|
|
|
if (InArchiveRecovery)
|
|
|
|
|
ControlFile->state = DB_IN_ARCHIVE_RECOVERY;
|
|
|
|
|
else
|
|
|
|
@ -6270,12 +6329,28 @@ StartupXLOG(void)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* set backupStartPoint if we're starting recovery from a base backup
|
|
|
|
|
* Set backupStartPoint if we're starting recovery from a base backup.
|
|
|
|
|
*
|
|
|
|
|
* Set backupEndPoint and use minRecoveryPoint as the backup end location
|
|
|
|
|
* if we're starting recovery from a base backup which was taken from
|
|
|
|
|
* the standby. In this case, the database system status in pg_control must
|
|
|
|
|
* indicate DB_IN_ARCHIVE_RECOVERY. If not, which means that backup
|
|
|
|
|
* is corrupted, so we cancel recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (haveBackupLabel)
|
|
|
|
|
{
|
|
|
|
|
ControlFile->backupStartPoint = checkPoint.redo;
|
|
|
|
|
ControlFile->backupEndRequired = backupEndRequired;
|
|
|
|
|
|
|
|
|
|
if (backupFromStandby)
|
|
|
|
|
{
|
|
|
|
|
if (dbstate_at_startup != DB_IN_ARCHIVE_RECOVERY)
|
|
|
|
|
ereport(FATAL,
|
|
|
|
|
(errmsg("backup_label contains inconsistent data with control file"),
|
|
|
|
|
errhint("This means that the backup is corrupted and you will "
|
|
|
|
|
"have to use another backup for recovery.")));
|
|
|
|
|
ControlFile->backupEndPoint = ControlFile->minRecoveryPoint;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ControlFile->time = (pg_time_t) time(NULL);
|
|
|
|
|
/* No need to hold ControlFileLock yet, we aren't up far enough */
|
|
|
|
@ -6564,6 +6639,27 @@ StartupXLOG(void)
|
|
|
|
|
/* Pop the error context stack */
|
|
|
|
|
error_context_stack = errcontext.previous;
|
|
|
|
|
|
|
|
|
|
if (!XLogRecPtrIsInvalid(ControlFile->backupStartPoint) &&
|
|
|
|
|
XLByteLE(ControlFile->backupEndPoint, EndRecPtr))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* We have reached the end of base backup, the point where
|
|
|
|
|
* the minimum recovery point in pg_control indicates.
|
|
|
|
|
* The data on disk is now consistent. Reset backupStartPoint
|
|
|
|
|
* and backupEndPoint.
|
|
|
|
|
*/
|
|
|
|
|
elog(DEBUG1, "end of backup reached");
|
|
|
|
|
|
|
|
|
|
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
|
|
|
|
|
|
|
|
|
|
MemSet(&ControlFile->backupStartPoint, 0, sizeof(XLogRecPtr));
|
|
|
|
|
MemSet(&ControlFile->backupEndPoint, 0, sizeof(XLogRecPtr));
|
|
|
|
|
ControlFile->backupEndRequired = false;
|
|
|
|
|
UpdateControlFile();
|
|
|
|
|
|
|
|
|
|
LWLockRelease(ControlFileLock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update shared recoveryLastRecPtr after this record has been
|
|
|
|
|
* replayed.
|
|
|
|
@ -6763,6 +6859,16 @@ StartupXLOG(void)
|
|
|
|
|
/* Pre-scan prepared transactions to find out the range of XIDs present */
|
|
|
|
|
oldestActiveXID = PrescanPreparedTransactions(NULL, NULL);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update full_page_writes in shared memory and write an
|
|
|
|
|
* XLOG_FPW_CHANGE record before resource manager writes cleanup
|
|
|
|
|
* WAL records or checkpoint record is written.
|
|
|
|
|
*/
|
|
|
|
|
Insert->fullPageWrites = lastFullPageWrites;
|
|
|
|
|
LocalSetXLogInsertAllowed();
|
|
|
|
|
UpdateFullPageWrites();
|
|
|
|
|
LocalXLogInsertAllowed = -1;
|
|
|
|
|
|
|
|
|
|
if (InRecovery)
|
|
|
|
|
{
|
|
|
|
|
int rmid;
|
|
|
|
@ -7644,6 +7750,7 @@ CreateCheckPoint(int flags)
|
|
|
|
|
LocalSetXLogInsertAllowed();
|
|
|
|
|
|
|
|
|
|
checkPoint.ThisTimeLineID = ThisTimeLineID;
|
|
|
|
|
checkPoint.fullPageWrites = Insert->fullPageWrites;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Compute new REDO record ptr = location of next XLOG record.
|
|
|
|
@ -8358,6 +8465,48 @@ XLogReportParameters(void)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update full_page_writes in shared memory, and write an
|
|
|
|
|
* XLOG_FPW_CHANGE record if necessary.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
UpdateFullPageWrites(void)
|
|
|
|
|
{
|
|
|
|
|
XLogCtlInsert *Insert = &XLogCtl->Insert;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Do nothing if full_page_writes has not been changed.
|
|
|
|
|
*
|
|
|
|
|
* It's safe to check the shared full_page_writes without the lock,
|
|
|
|
|
* because we can guarantee that there is no concurrently running
|
|
|
|
|
* process which can update it.
|
|
|
|
|
*/
|
|
|
|
|
if (fullPageWrites == Insert->fullPageWrites)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Write an XLOG_FPW_CHANGE record. This allows us to keep
|
|
|
|
|
* track of full_page_writes during archive recovery, if required.
|
|
|
|
|
*/
|
|
|
|
|
if (XLogStandbyInfoActive() && !RecoveryInProgress())
|
|
|
|
|
{
|
|
|
|
|
XLogRecData rdata;
|
|
|
|
|
|
|
|
|
|
rdata.data = (char *) (&fullPageWrites);
|
|
|
|
|
rdata.len = sizeof(bool);
|
|
|
|
|
rdata.buffer = InvalidBuffer;
|
|
|
|
|
rdata.next = NULL;
|
|
|
|
|
|
|
|
|
|
XLogInsert(RM_XLOG_ID, XLOG_FPW_CHANGE, &rdata);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LWLockAcquire(WALInsertLock, LW_EXCLUSIVE);
|
|
|
|
|
Insert->fullPageWrites = fullPageWrites;
|
|
|
|
|
LWLockRelease(WALInsertLock);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* XLOG resource manager's routines
|
|
|
|
|
*
|
|
|
|
@ -8402,7 +8551,8 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
|
|
|
|
|
* never arrive.
|
|
|
|
|
*/
|
|
|
|
|
if (InArchiveRecovery &&
|
|
|
|
|
!XLogRecPtrIsInvalid(ControlFile->backupStartPoint))
|
|
|
|
|
!XLogRecPtrIsInvalid(ControlFile->backupStartPoint) &&
|
|
|
|
|
XLogRecPtrIsInvalid(ControlFile->backupEndPoint))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errmsg("online backup was canceled, recovery cannot continue")));
|
|
|
|
|
|
|
|
|
@ -8571,6 +8721,30 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
|
|
|
|
|
/* Check to see if any changes to max_connections give problems */
|
|
|
|
|
CheckRequiredParameterValues();
|
|
|
|
|
}
|
|
|
|
|
else if (info == XLOG_FPW_CHANGE)
|
|
|
|
|
{
|
|
|
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
|
|
|
volatile XLogCtlData *xlogctl = XLogCtl;
|
|
|
|
|
bool fpw;
|
|
|
|
|
|
|
|
|
|
memcpy(&fpw, XLogRecGetData(record), sizeof(bool));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Update the LSN of the last replayed XLOG_FPW_CHANGE record
|
|
|
|
|
* so that do_pg_start_backup() and do_pg_stop_backup() can check
|
|
|
|
|
* whether full_page_writes has been disabled during online backup.
|
|
|
|
|
*/
|
|
|
|
|
if (!fpw)
|
|
|
|
|
{
|
|
|
|
|
SpinLockAcquire(&xlogctl->info_lck);
|
|
|
|
|
if (XLByteLT(xlogctl->lastFpwDisableRecPtr, ReadRecPtr))
|
|
|
|
|
xlogctl->lastFpwDisableRecPtr = ReadRecPtr;
|
|
|
|
|
SpinLockRelease(&xlogctl->info_lck);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Keep track of full_page_writes */
|
|
|
|
|
lastFullPageWrites = fpw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
@ -8584,10 +8758,11 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
|
|
|
|
|
CheckPoint *checkpoint = (CheckPoint *) rec;
|
|
|
|
|
|
|
|
|
|
appendStringInfo(buf, "checkpoint: redo %X/%X; "
|
|
|
|
|
"tli %u; xid %u/%u; oid %u; multi %u; offset %u; "
|
|
|
|
|
"tli %u; fpw %s; xid %u/%u; oid %u; multi %u; offset %u; "
|
|
|
|
|
"oldest xid %u in DB %u; oldest running xid %u; %s",
|
|
|
|
|
checkpoint->redo.xlogid, checkpoint->redo.xrecoff,
|
|
|
|
|
checkpoint->ThisTimeLineID,
|
|
|
|
|
checkpoint->fullPageWrites ? "true" : "false",
|
|
|
|
|
checkpoint->nextXidEpoch, checkpoint->nextXid,
|
|
|
|
|
checkpoint->nextOid,
|
|
|
|
|
checkpoint->nextMulti,
|
|
|
|
@ -8652,6 +8827,13 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
|
|
|
|
|
xlrec.max_locks_per_xact,
|
|
|
|
|
wal_level_str);
|
|
|
|
|
}
|
|
|
|
|
else if (info == XLOG_FPW_CHANGE)
|
|
|
|
|
{
|
|
|
|
|
bool fpw;
|
|
|
|
|
|
|
|
|
|
memcpy(&fpw, rec, sizeof(bool));
|
|
|
|
|
appendStringInfo(buf, "full_page_writes: %s", fpw ? "true" : "false");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
appendStringInfo(buf, "UNKNOWN");
|
|
|
|
|
}
|
|
|
|
@ -8837,6 +9019,7 @@ XLogRecPtr
|
|
|
|
|
do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
{
|
|
|
|
|
bool exclusive = (labelfile == NULL);
|
|
|
|
|
bool backup_started_in_recovery = false;
|
|
|
|
|
XLogRecPtr checkpointloc;
|
|
|
|
|
XLogRecPtr startpoint;
|
|
|
|
|
pg_time_t stamp_time;
|
|
|
|
@ -8848,18 +9031,27 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
FILE *fp;
|
|
|
|
|
StringInfoData labelfbuf;
|
|
|
|
|
|
|
|
|
|
backup_started_in_recovery = RecoveryInProgress();
|
|
|
|
|
|
|
|
|
|
if (!superuser() && !is_authenticated_user_replication_role())
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
|
|
|
errmsg("must be superuser or replication role to run a backup")));
|
|
|
|
|
|
|
|
|
|
if (RecoveryInProgress())
|
|
|
|
|
/*
|
|
|
|
|
* Currently only non-exclusive backup can be taken during recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (backup_started_in_recovery && exclusive)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("recovery is in progress"),
|
|
|
|
|
errhint("WAL control functions cannot be executed during recovery.")));
|
|
|
|
|
|
|
|
|
|
if (!XLogIsNeeded())
|
|
|
|
|
/*
|
|
|
|
|
* During recovery, we don't need to check WAL level. Because, if WAL level
|
|
|
|
|
* is not sufficient, it's impossible to get here during recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (!backup_started_in_recovery && !XLogIsNeeded())
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("WAL level not sufficient for making an online backup"),
|
|
|
|
@ -8885,6 +9077,9 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
* since we expect that any pages not modified during the backup interval
|
|
|
|
|
* must have been correctly captured by the backup.)
|
|
|
|
|
*
|
|
|
|
|
* Note that forcePageWrites has no effect during an online backup from
|
|
|
|
|
* the standby.
|
|
|
|
|
*
|
|
|
|
|
* We must hold WALInsertLock to change the value of forcePageWrites, to
|
|
|
|
|
* ensure adequate interlocking against XLogInsert().
|
|
|
|
|
*/
|
|
|
|
@ -8927,17 +9122,32 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
* Therefore, if a WAL archiver (such as pglesslog) is trying to
|
|
|
|
|
* compress out removable backup blocks, it won't remove any that
|
|
|
|
|
* occur after this point.
|
|
|
|
|
*
|
|
|
|
|
* During recovery, we skip forcing XLOG file switch, which means that
|
|
|
|
|
* the backup taken during recovery is not available for the special
|
|
|
|
|
* recovery case described above.
|
|
|
|
|
*/
|
|
|
|
|
RequestXLogSwitch();
|
|
|
|
|
if (!backup_started_in_recovery)
|
|
|
|
|
RequestXLogSwitch();
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
bool checkpointfpw;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Force a CHECKPOINT. Aside from being necessary to prevent torn
|
|
|
|
|
* Force a CHECKPOINT. Aside from being necessary to prevent torn
|
|
|
|
|
* page problems, this guarantees that two successive backup runs
|
|
|
|
|
* will have different checkpoint positions and hence different
|
|
|
|
|
* history file names, even if nothing happened in between.
|
|
|
|
|
*
|
|
|
|
|
* During recovery, establish a restartpoint if possible. We use the last
|
|
|
|
|
* restartpoint as the backup starting checkpoint. This means that two
|
|
|
|
|
* successive backup runs can have same checkpoint positions.
|
|
|
|
|
*
|
|
|
|
|
* Since the fact that we are executing do_pg_start_backup() during
|
|
|
|
|
* recovery means that checkpointer is running, we can use
|
|
|
|
|
* RequestCheckpoint() to establish a restartpoint.
|
|
|
|
|
*
|
|
|
|
|
* We use CHECKPOINT_IMMEDIATE only if requested by user (via
|
|
|
|
|
* passing fast = true). Otherwise this can take awhile.
|
|
|
|
|
*/
|
|
|
|
@ -8953,8 +9163,44 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
|
|
|
|
checkpointloc = ControlFile->checkPoint;
|
|
|
|
|
startpoint = ControlFile->checkPointCopy.redo;
|
|
|
|
|
checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
|
|
|
|
|
LWLockRelease(ControlFileLock);
|
|
|
|
|
|
|
|
|
|
if (backup_started_in_recovery)
|
|
|
|
|
{
|
|
|
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
|
|
|
volatile XLogCtlData *xlogctl = XLogCtl;
|
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check to see if all WAL replayed during online backup (i.e.,
|
|
|
|
|
* since last restartpoint used as backup starting checkpoint)
|
|
|
|
|
* contain full-page writes.
|
|
|
|
|
*/
|
|
|
|
|
SpinLockAcquire(&xlogctl->info_lck);
|
|
|
|
|
recptr = xlogctl->lastFpwDisableRecPtr;
|
|
|
|
|
SpinLockRelease(&xlogctl->info_lck);
|
|
|
|
|
|
|
|
|
|
if (!checkpointfpw || XLByteLE(startpoint, recptr))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("WAL generated with full_page_writes=off was replayed "
|
|
|
|
|
"since last restartpoint"),
|
|
|
|
|
errhint("This means that the backup being taken on standby "
|
|
|
|
|
"is corrupt and should not be used. "
|
|
|
|
|
"Enable full_page_writes and run CHECKPOINT on the master, "
|
|
|
|
|
"and then try an online backup again.")));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* During recovery, since we don't use the end-of-backup WAL
|
|
|
|
|
* record and don't write the backup history file, the starting WAL
|
|
|
|
|
* location doesn't need to be unique. This means that two base
|
|
|
|
|
* backups started at the same time might use the same checkpoint
|
|
|
|
|
* as starting locations.
|
|
|
|
|
*/
|
|
|
|
|
gotUniqueStartpoint = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If two base backups are started at the same time (in WAL sender
|
|
|
|
|
* processes), we need to make sure that they use different
|
|
|
|
@ -8994,6 +9240,8 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
|
|
|
|
|
checkpointloc.xlogid, checkpointloc.xrecoff);
|
|
|
|
|
appendStringInfo(&labelfbuf, "BACKUP METHOD: %s\n",
|
|
|
|
|
exclusive ? "pg_start_backup" : "streamed");
|
|
|
|
|
appendStringInfo(&labelfbuf, "BACKUP FROM: %s\n",
|
|
|
|
|
backup_started_in_recovery ? "standby" : "master");
|
|
|
|
|
appendStringInfo(&labelfbuf, "START TIME: %s\n", strfbuf);
|
|
|
|
|
appendStringInfo(&labelfbuf, "LABEL: %s\n", backupidstr);
|
|
|
|
|
|
|
|
|
@ -9088,6 +9336,7 @@ XLogRecPtr
|
|
|
|
|
do_pg_stop_backup(char *labelfile, bool waitforarchive)
|
|
|
|
|
{
|
|
|
|
|
bool exclusive = (labelfile == NULL);
|
|
|
|
|
bool backup_started_in_recovery = false;
|
|
|
|
|
XLogRecPtr startpoint;
|
|
|
|
|
XLogRecPtr stoppoint;
|
|
|
|
|
XLogRecData rdata;
|
|
|
|
@ -9098,6 +9347,7 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
|
|
|
|
|
char stopxlogfilename[MAXFNAMELEN];
|
|
|
|
|
char lastxlogfilename[MAXFNAMELEN];
|
|
|
|
|
char histfilename[MAXFNAMELEN];
|
|
|
|
|
char backupfrom[20];
|
|
|
|
|
uint32 _logId;
|
|
|
|
|
uint32 _logSeg;
|
|
|
|
|
FILE *lfp;
|
|
|
|
@ -9107,19 +9357,29 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
|
|
|
|
|
int waits = 0;
|
|
|
|
|
bool reported_waiting = false;
|
|
|
|
|
char *remaining;
|
|
|
|
|
char *ptr;
|
|
|
|
|
|
|
|
|
|
backup_started_in_recovery = RecoveryInProgress();
|
|
|
|
|
|
|
|
|
|
if (!superuser() && !is_authenticated_user_replication_role())
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
|
|
|
(errmsg("must be superuser or replication role to run a backup"))));
|
|
|
|
|
|
|
|
|
|
if (RecoveryInProgress())
|
|
|
|
|
/*
|
|
|
|
|
* Currently only non-exclusive backup can be taken during recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (backup_started_in_recovery && exclusive)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("recovery is in progress"),
|
|
|
|
|
errhint("WAL control functions cannot be executed during recovery.")));
|
|
|
|
|
|
|
|
|
|
if (!XLogIsNeeded())
|
|
|
|
|
/*
|
|
|
|
|
* During recovery, we don't need to check WAL level. Because, if WAL level
|
|
|
|
|
* is not sufficient, it's impossible to get here during recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (!backup_started_in_recovery && !XLogIsNeeded())
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("WAL level not sufficient for making an online backup"),
|
|
|
|
@ -9209,6 +9469,82 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
|
|
|
|
|
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
|
|
|
|
|
remaining = strchr(labelfile, '\n') + 1; /* %n is not portable enough */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse the BACKUP FROM line. If we are taking an online backup from
|
|
|
|
|
* the standby, we confirm that the standby has not been promoted
|
|
|
|
|
* during the backup.
|
|
|
|
|
*/
|
|
|
|
|
ptr = strstr(remaining, "BACKUP FROM:");
|
|
|
|
|
if (sscanf(ptr, "BACKUP FROM: %19s\n", backupfrom) != 1)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
|
|
|
|
|
if (strcmp(backupfrom, "standby") == 0 && !backup_started_in_recovery)
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("the standby was promoted during online backup"),
|
|
|
|
|
errhint("This means that the backup being taken is corrupt "
|
|
|
|
|
"and should not be used. "
|
|
|
|
|
"Try taking another online backup.")));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* During recovery, we don't write an end-of-backup record. We assume
|
|
|
|
|
* that pg_control was backed up last and its minimum recovery
|
|
|
|
|
* point can be available as the backup end location. Since we don't
|
|
|
|
|
* have an end-of-backup record, we use the pg_control value to check
|
|
|
|
|
* whether we've reached the end of backup when starting recovery from
|
|
|
|
|
* this backup. We have no way of checking if pg_control wasn't backed
|
|
|
|
|
* up last however.
|
|
|
|
|
*
|
|
|
|
|
* We don't force a switch to new WAL file and wait for all the required
|
|
|
|
|
* files to be archived. This is okay if we use the backup to start
|
|
|
|
|
* the standby. But, if it's for an archive recovery, to ensure all the
|
|
|
|
|
* required files are available, a user should wait for them to be archived,
|
|
|
|
|
* or include them into the backup.
|
|
|
|
|
*
|
|
|
|
|
* We return the current minimum recovery point as the backup end
|
|
|
|
|
* location. Note that it's would be bigger than the exact backup end
|
|
|
|
|
* location if the minimum recovery point is updated since the backup
|
|
|
|
|
* of pg_control. This is harmless for current uses.
|
|
|
|
|
*
|
|
|
|
|
* XXX currently a backup history file is for informational and debug
|
|
|
|
|
* purposes only. It's not essential for an online backup. Furthermore,
|
|
|
|
|
* even if it's created, it will not be archived during recovery because
|
|
|
|
|
* an archiver is not invoked. So it doesn't seem worthwhile to write
|
|
|
|
|
* a backup history file during recovery.
|
|
|
|
|
*/
|
|
|
|
|
if (backup_started_in_recovery)
|
|
|
|
|
{
|
|
|
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
|
|
|
volatile XLogCtlData *xlogctl = XLogCtl;
|
|
|
|
|
XLogRecPtr recptr;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Check to see if all WAL replayed during online backup contain
|
|
|
|
|
* full-page writes.
|
|
|
|
|
*/
|
|
|
|
|
SpinLockAcquire(&xlogctl->info_lck);
|
|
|
|
|
recptr = xlogctl->lastFpwDisableRecPtr;
|
|
|
|
|
SpinLockRelease(&xlogctl->info_lck);
|
|
|
|
|
|
|
|
|
|
if (XLByteLE(startpoint, recptr))
|
|
|
|
|
ereport(ERROR,
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("WAL generated with full_page_writes=off was replayed "
|
|
|
|
|
"during online backup"),
|
|
|
|
|
errhint("This means that the backup being taken on standby "
|
|
|
|
|
"is corrupt and should not be used. "
|
|
|
|
|
"Enable full_page_writes and run CHECKPOINT on the master, "
|
|
|
|
|
"and then try an online backup again.")));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
|
|
|
|
stoppoint = ControlFile->minRecoveryPoint;
|
|
|
|
|
LWLockRelease(ControlFileLock);
|
|
|
|
|
|
|
|
|
|
return stoppoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Write the backup-end xlog record
|
|
|
|
|
*/
|
|
|
|
@ -9454,18 +9790,22 @@ GetXLogWriteRecPtr(void)
|
|
|
|
|
* Returns TRUE if a backup_label was found (and fills the checkpoint
|
|
|
|
|
* location and its REDO location into *checkPointLoc and RedoStartLSN,
|
|
|
|
|
* respectively); returns FALSE if not. If this backup_label came from a
|
|
|
|
|
* streamed backup, *backupEndRequired is set to TRUE.
|
|
|
|
|
* streamed backup, *backupEndRequired is set to TRUE. If this backup_label
|
|
|
|
|
* was created during recovery, *backupFromStandby is set to TRUE.
|
|
|
|
|
*/
|
|
|
|
|
static bool
|
|
|
|
|
read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired)
|
|
|
|
|
read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired,
|
|
|
|
|
bool *backupFromStandby)
|
|
|
|
|
{
|
|
|
|
|
char startxlogfilename[MAXFNAMELEN];
|
|
|
|
|
TimeLineID tli;
|
|
|
|
|
FILE *lfp;
|
|
|
|
|
char ch;
|
|
|
|
|
char backuptype[20];
|
|
|
|
|
char backupfrom[20];
|
|
|
|
|
|
|
|
|
|
*backupEndRequired = false;
|
|
|
|
|
*backupFromStandby = false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* See if label file is present
|
|
|
|
@ -9499,16 +9839,22 @@ read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired)
|
|
|
|
|
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
|
|
|
|
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
|
|
|
|
|
/*
|
|
|
|
|
* BACKUP METHOD line is new in 9.1. We can't restore from an older backup
|
|
|
|
|
* anyway, but since the information on it is not strictly required, don't
|
|
|
|
|
* error out if it's missing for some reason.
|
|
|
|
|
* BACKUP METHOD and BACKUP FROM lines are new in 9.2. We can't
|
|
|
|
|
* restore from an older backup anyway, but since the information on it
|
|
|
|
|
* is not strictly required, don't error out if it's missing for some reason.
|
|
|
|
|
*/
|
|
|
|
|
if (fscanf(lfp, "BACKUP METHOD: %19s", backuptype) == 1)
|
|
|
|
|
if (fscanf(lfp, "BACKUP METHOD: %19s\n", backuptype) == 1)
|
|
|
|
|
{
|
|
|
|
|
if (strcmp(backuptype, "streamed") == 0)
|
|
|
|
|
*backupEndRequired = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fscanf(lfp, "BACKUP FROM: %19s\n", backupfrom) == 1)
|
|
|
|
|
{
|
|
|
|
|
if (strcmp(backupfrom, "standby") == 0)
|
|
|
|
|
*backupFromStandby = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ferror(lfp) || FreeFile(lfp))
|
|
|
|
|
ereport(FATAL,
|
|
|
|
|
(errcode_for_file_access(),
|
|
|
|
|