From: Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>
To: Simon Riggs <simon@2ndQuadrant.com>
Cc: Alvaro Herrera <alvherre@commandprompt.com>
Cc: Pavan Deolasee <pavan.deolasee@gmail.com>
Cc: Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>
Cc: pgsql-hackers <pgsql-hackers@postgresql.org>
Subject: Re: [PATCHES] Infrastructure changes for recovery (v8)
Date: Mon, 22 Dec 2008 22:18:57 +0200
Message-ID: <494FF631.90908@enterprisedb.com> (raw)
In-Reply-To: <1229568649.4793.296.camel@ebony.2ndQuadrant>
References: <48EC9CC5.6060803@enterprisedb.com>
<1223468660.4747.294.camel@ebony.2ndQuadrant>
<1223472898.4747.310.camel@ebony.2ndQuadrant>
<492176E2.1000006@enterprisedb.com>
<1226935876.3790.10.camel@ebony.2ndQuadrant>
<2e78013d0811192136o5b722ddyb1562658508dcc5a@mail.gmail.com>
<1227174122.7015.5.camel@ebony.2ndQuadrant>
<2e78013d0811200149t29173598m5eb70f62c1dc02d4@mail.gmail.com>
<1227175805.7015.22.camel@ebony.2ndQuadrant>
<1229563881.4793.264.camel@ebony.2ndQuadrant>
<20081218023212.GO4453@alvh.no-ip.org>
<1229568649.4793.296.camel@ebony.2ndQuadrant>
Simon Riggs wrote:
> On Wed, 2008-12-17 at 23:32 -0300, Alvaro Herrera wrote:
>> Simon Riggs escribió:
>>
>>> Please let me know how I can make the reviewer's job easier. Diagrams,
>>> writeups, whatever. Thanks,
>> A link perhaps?
>
> There is much confusion on this point for which I'm very sorry.
>
> I originally wrote "infra" patch to allow it to be committed separately
> in the Sept commitfest, to reduce size of the forthcoming hotstandby
> patch. That didn't happen (no moans there) so the eventual "hotstandby"
> patch includes all of what was the infra patch, plus the new code.
>
> So currently there is no separate "infra" patch. The two line items on
> the CommitFest page are really just one large project. I would be in
> favour of removing the "infra" lines from the CommitFest page.
I think it's useful to review the "infra" part of the patch separately,
so I split it out of the big patch again. I haven't looked at this in
detail yet, but it compiles and passes regression tests.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.comAttachments:recovery-infra-separated-again-1.patch (text/x-diff, 86.9K) download
*** src/backend/access/transam/clog.c
--- src/backend/access/transam/clog.c
***************
*** 475,480 **** ZeroCLOGPage(int pageno, bool writeXlog)
--- 475,483 ----
/*
* This must be called ONCE during postmaster or standalone-backend startup,
* after StartupXLOG has initialized ShmemVariableCache->nextXid.
+ *+ * We access just a single clog page, so this action is atomic and safe+ * for use if other processes are active during recovery.
*/
void
StartupCLOG(void)
*** src/backend/access/transam/multixact.c
--- src/backend/access/transam/multixact.c
***************
*** 1413,1420 **** ZeroMultiXactMemberPage(int pageno, bool writeXlog)
* MultiXactSetNextMXact and/or MultiXactAdvanceNextMXact. Note that we
* may already have replayed WAL data into the SLRU files.
*
! * We don't need any locks here, really; the SLRU locks are taken
! * only because slru.c expects to be called with locks held.
*/
void
StartupMultiXact(void)
--- 1413,1423 ----
* MultiXactSetNextMXact and/or MultiXactAdvanceNextMXact. Note that we
* may already have replayed WAL data into the SLRU files.
*
! * We want this operation to be atomic to ensure that other processes can
! * use MultiXact while we complete recovery. We access one page only from the
! * offset and members buffers, so once locks are acquired they will not be
! * dropped and re-acquired by SLRU code. So we take both locks at start, then
! * hold them all the way to the end.
*/
void
StartupMultiXact(void)
***************
*** 1426,1431 **** StartupMultiXact(void)
--- 1429,1435 ----
/* Clean up offsets state */
LWLockAcquire(MultiXactOffsetControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(MultiXactMemberControlLock, LW_EXCLUSIVE);
/*
* Initialize our idea of the latest page number.
***************
*** 1452,1461 **** StartupMultiXact(void)
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(MultiXactOffsetControlLock);-
/* And the same for members */
- LWLockAcquire(MultiXactMemberControlLock, LW_EXCLUSIVE);
/*
* Initialize our idea of the latest page number.
--- 1456,1462 ----
***************
*** 1483,1488 **** StartupMultiXact(void)
--- 1484,1490 ----
}
LWLockRelease(MultiXactMemberControlLock);
+ LWLockRelease(MultiXactOffsetControlLock);
/*
* Initialize lastTruncationPoint to invalid, ensuring that the first
***************
*** 1542,1549 **** CheckPointMultiXact(void)
* isn't valid (because StartupMultiXact hasn't been called yet) and so
* SimpleLruTruncate would get confused. It seems best not to risk
* removing any data during recovery anyway, so don't truncate.
*/
! if (!InRecovery)
TruncateMultiXact();
TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE(true);
--- 1544,1552 ----
* isn't valid (because StartupMultiXact hasn't been called yet) and so
* SimpleLruTruncate would get confused. It seems best not to risk
* removing any data during recovery anyway, so don't truncate.
+ * We are executing in the bgwriter, so we must access shared status.
*/
! if (!IsRecoveryProcessingMode())
TruncateMultiXact();
TRACE_POSTGRESQL_MULTIXACT_CHECKPOINT_DONE(true);
*** src/backend/access/transam/slru.c
--- src/backend/access/transam/slru.c
***************
*** 598,604 **** SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
* commands to set the commit status of transactions whose bits are in
* already-truncated segments of the commit log (see notes in
* SlruPhysicalWritePage). Hence, if we are InRecovery, allow the case
! * where the file doesn't exist, and return zeroes instead.
*/
fd = BasicOpenFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
if (fd < 0)
--- 598,605 ----
* commands to set the commit status of transactions whose bits are in
* already-truncated segments of the commit log (see notes in
* SlruPhysicalWritePage). Hence, if we are InRecovery, allow the case
! * where the file doesn't exist, and return zeroes instead. We also
! * return a zeroed page when seek and read fails.
*/
fd = BasicOpenFile(path, O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR);
if (fd < 0)
***************
*** 619,624 **** SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
--- 620,633 ----
if (lseek(fd, (off_t) offset, SEEK_SET) < 0)
{
+ if (InRecovery)+ {+ ereport(LOG,+ (errmsg("file \"%s\" doesn't exist, reading as zeroes",+ path)));+ MemSet(shared->page_buffer[slotno], 0, BLCKSZ);+ return true;+ }
slru_errcause = SLRU_SEEK_FAILED;
slru_errno = errno;
close(fd);
***************
*** 628,633 **** SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
--- 637,650 ----
errno = 0;
if (read(fd, shared->page_buffer[slotno], BLCKSZ) != BLCKSZ)
{
+ if (InRecovery)+ {+ ereport(LOG,+ (errmsg("file \"%s\" doesn't exist, reading as zeroes",+ path)));+ MemSet(shared->page_buffer[slotno], 0, BLCKSZ);+ return true;+ }
slru_errcause = SLRU_READ_FAILED;
slru_errno = errno;
close(fd);
*** src/backend/access/transam/subtrans.c
--- src/backend/access/transam/subtrans.c
***************
*** 223,255 **** ZeroSUBTRANSPage(int pageno)
/*
* This must be called ONCE during postmaster or standalone-backend startup,
* after StartupXLOG has initialized ShmemVariableCache->nextXid.
- *- * oldestActiveXID is the oldest XID of any prepared transaction, or nextXid- * if there are none.
*/
void
StartupSUBTRANS(TransactionId oldestActiveXID)
{
! int startPage;
! int endPage;
- /*- * Since we don't expect pg_subtrans to be valid across crashes, we- * initialize the currently-active page(s) to zeroes during startup.- * Whenever we advance into a new page, ExtendSUBTRANS will likewise zero- * the new page without regard to whatever was previously on disk.- */
LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE);
! startPage = TransactionIdToPage(oldestActiveXID);
! endPage = TransactionIdToPage(ShmemVariableCache->nextXid);
!
! while (startPage != endPage)
! {
! (void) ZeroSUBTRANSPage(startPage);
! startPage++;
! }
! (void) ZeroSUBTRANSPage(startPage);
LWLockRelease(SubtransControlLock);
}
--- 223,241 ----
/*
* This must be called ONCE during postmaster or standalone-backend startup,
* after StartupXLOG has initialized ShmemVariableCache->nextXid.
*/
void
StartupSUBTRANS(TransactionId oldestActiveXID)
{
! TransactionId xid = ShmemVariableCache->nextXid;
! int pageno = TransactionIdToPage(xid);
LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE);
! /*
! * Initialize our idea of the latest page number.
! */
! SubTransCtl->shared->latest_page_number = pageno;
LWLockRelease(SubtransControlLock);
}
*** src/backend/access/transam/xact.c
--- src/backend/access/transam/xact.c
***************
*** 40,45 ****
--- 40,46 ----
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
+ #include "storage/sinval.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "utils/combocid.h"
*** src/backend/access/transam/xlog.c
--- src/backend/access/transam/xlog.c
***************
*** 114,120 **** CheckpointStatsData CheckpointStats;
/*
* ThisTimeLineID will be same in all backends --- it identifies current
! * WAL timeline for the database system.
*/
TimeLineID ThisTimeLineID = 0;
--- 114,121 ----
/*
* ThisTimeLineID will be same in all backends --- it identifies current
! * WAL timeline for the database system. Zero is always a bug, so we
! * start with that to allow us to spot any errors.
*/
TimeLineID ThisTimeLineID = 0;
***************
*** 122,128 **** TimeLineID ThisTimeLineID = 0;
bool InRecovery = false;
/* Are we recovering using offline XLOG archives? */
! static bool InArchiveRecovery = false;
/* Was the last xlog file restored from archive, or local? */
static bool restoredFromArchive = false;
--- 123,136 ----
bool InRecovery = false;
/* Are we recovering using offline XLOG archives? */
! bool InArchiveRecovery = false;
!
! /* Local copy of shared RecoveryProcessingMode state */
! static bool LocalRecoveryProcessingMode = true;
! static bool knownProcessingMode = false;
!
! /* is the database proven consistent yet? */
! bool reachedSafeStartPoint = false;
/* Was the last xlog file restored from archive, or local? */
static bool restoredFromArchive = false;
***************
*** 241,250 **** static XLogRecPtr RedoRecPtr;
* ControlFileLock: must be held to read/update control file or create
* new log file.
*
! * CheckpointLock: must be held to do a checkpoint (ensures only one
! * checkpointer at a time; currently, with all checkpoints done by the
! * bgwriter, this is just pro forma).
*
*----------
*/
--- 249,278 ----
* ControlFileLock: must be held to read/update control file or create
* new log file.
*
! * CheckpointLock: must be held to do a checkpoint or restartpoint, ensuring
! * we get just one of those at any time. In 8.4+ recovery, both startup and
! * bgwriter processes may take restartpoints, so this locking must be strict
! * to ensure there are no mistakes.
*
+ * In 8.4 we progress through a number of states at startup. Initially, the+ * postmaster is in PM_STARTUP state and spawns the Startup process. We then+ * progress until the database is in a consistent state, then if we are in+ * InArchiveRecovery we go into PM_RECOVERY state. The bgwriter then starts+ * up and takes over responsibility for performing restartpoints. We then+ * progress until the end of recovery when we enter PM_RUN state upon+ * termination of the Startup process. In summary:+ * + * PM_STARTUP state: Startup process performs restartpoints+ * PM_RECOVERY state: bgwriter process performs restartpoints+ * PM_RUN state: bgwriter process performs checkpoints+ *+ * These transitions are fairly delicate, with many things that need to+ * happen at the same time in order to change state successfully throughout+ * the system. Changing PM_STARTUP to PM_RECOVERY only occurs when we can+ * prove the databases are in a consistent state. Changing from PM_RECOVERY+ * to PM_RUN happens whenever recovery ends, which could be forced upon us+ * externally or it can occur because of damage or termination of the WAL+ * sequence.
*----------
*/
***************
*** 312,317 **** typedef struct XLogCtlData
--- 340,372 ----
int XLogCacheBlck; /* highest allocated xlog buffer index */
TimeLineID ThisTimeLineID;
+ /*+ * IsRecoveryProcessingMode shows whether the postmaster is in a+ * postmaster state earlier than PM_RUN, or not. This is a globally+ * accessible state to allow EXEC_BACKEND case.+ *+ * We also retain a local state variable InRecovery. InRecovery=true+ * means the code is being executed by Startup process and therefore+ * always during Recovery Processing Mode. This allows us to identify+ * code executed *during* Recovery Processing Mode but not necessarily+ * by Startup process itself.+ *+ * This is only written to by the startup process, so no need for locking.+ */+ bool SharedRecoveryProcessingMode;+ + /*+ * recovery target control information+ *+ * Protected by info_lck+ */+ TransactionId recoveryTargetXid;+ TimestampTz recoveryTargetTime;+ int recoveryTargetAdvance;+ + TimestampTz recoveryLastXTime;+ TransactionId recoveryLastXid;+
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
***************
*** 398,405 **** static void XLogArchiveCleanup(const char *xlog);
--- 453,462 ----
static void readRecoveryCommandFile(void);
static void exitArchiveRecovery(TimeLineID endTLI,
uint32 endLogId, uint32 endLogSeg);
+ static void exitRecovery(void);
static bool recoveryStopsHere(XLogRecord *record, bool *includeThis);
static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags);
+ static XLogRecPtr GetRedoLocationForCheckpoint(void);
static bool XLogCheckBuffer(XLogRecData *rdata, bool doPageWrites,
XLogRecPtr *lsn, BkpBlock *bkpb);
***************
*** 482,487 **** XLogInsert(RmgrId rmid, uint8 info, XLogRecData *rdata)
--- 539,552 ----
bool updrqst;
bool doPageWrites;
bool isLogSwitch = (rmid == RM_XLOG_ID && info == XLOG_SWITCH);
+ bool isRecoveryEnd = (rmid == RM_XLOG_ID && + (info == XLOG_RECOVERY_END ||+ info == XLOG_CHECKPOINT_ONLINE));+ + /* cross-check on whether we should be here or not */+ if (IsRecoveryProcessingMode() && !isRecoveryEnd)+ elog(FATAL, "cannot make new WAL entries during recovery "+ "(RMgrId = %d info = %d)", rmid, info);
/* info's high bits are reserved for use by me */
if (info & XLR_INFO_MASK)
***************
*** 1728,1735 **** XLogFlush(XLogRecPtr record)
XLogRecPtr WriteRqstPtr;
XLogwrtRqst WriteRqst;
! /* Disabled during REDO */
! if (InRedo)
return;
/* Quick exit if already known flushed */
--- 1793,1799 ----
XLogRecPtr WriteRqstPtr;
XLogwrtRqst WriteRqst;
! if (IsRecoveryProcessingMode())
return;
/* Quick exit if already known flushed */
***************
*** 1817,1826 **** XLogFlush(XLogRecPtr record)
* the bad page is encountered again during recovery then we would be
* unable to restart the database at all! (This scenario has actually
* happened in the field several times with 7.1 releases. Note that we
! * cannot get here while InRedo is true, but if the bad page is brought in
! * and marked dirty during recovery then CreateCheckPoint will try to
! * flush it at the end of recovery.)
*
* The current approach is to ERROR under normal conditions, but only
* WARNING during recovery, so that the system can be brought up even if
* there's a corrupt LSN. Note that for calls from xact.c, the ERROR will
--- 1881,1891 ----
* the bad page is encountered again during recovery then we would be
* unable to restart the database at all! (This scenario has actually
* happened in the field several times with 7.1 releases. Note that we
! * cannot get here while IsRecoveryProcessingMode(), but if the bad page is
! * brought in and marked dirty during recovery, the next checkpoint after
! * recovery will try to flush it.
*
+ * XXX obsolete comment
* The current approach is to ERROR under normal conditions, but only
* WARNING during recovery, so that the system can be brought up even if
* there's a corrupt LSN. Note that for calls from xact.c, the ERROR will
***************
*** 1829,1835 **** XLogFlush(XLogRecPtr record)
* and so we will not force a restart for a bad LSN on a data page.
*/
if (XLByteLT(LogwrtResult.Flush, record))
! elog(InRecovery ? WARNING : ERROR,
"xlog flush request %X/%X is not satisfied --- flushed only to %X/%X",
record.xlogid, record.xrecoff,
LogwrtResult.Flush.xlogid, LogwrtResult.Flush.xrecoff);
--- 1894,1900 ----
* and so we will not force a restart for a bad LSN on a data page.
*/
if (XLByteLT(LogwrtResult.Flush, record))
! elog(ERROR,
"xlog flush request %X/%X is not satisfied --- flushed only to %X/%X",
record.xlogid, record.xrecoff,
LogwrtResult.Flush.xlogid, LogwrtResult.Flush.xrecoff);
***************
*** 2102,2108 **** XLogFileInit(uint32 log, uint32 seg,
unlink(tmppath);
}
! elog(DEBUG2, "done creating and filling new WAL file");
/* Set flag to tell caller there was no existent file */
*use_existent = false;
--- 2167,2174 ----
unlink(tmppath);
}
! XLogFileName(tmppath, ThisTimeLineID, log, seg);
! elog(DEBUG2, "done creating and filling new WAL file %s", tmppath);
/* Set flag to tell caller there was no existent file */
*use_existent = false;
***************
*** 2408,2413 **** XLogFileRead(uint32 log, uint32 seg, int emode)
--- 2474,2501 ----
xlogfname);
set_ps_display(activitymsg, false);
+ /* + * Calculate and write out a new safeStartPoint. This defines+ * the latest LSN that might appear on-disk while we apply+ * the WAL records in this file. If we crash during recovery+ * we must reach this point again before we can prove+ * database consistency. Not a restartpoint! Restart points+ * define where we should start recovery from, if we crash.+ */+ if (InArchiveRecovery)+ {+ uint32 nextLog = log;+ uint32 nextSeg = seg;+ + NextLogSeg(nextLog, nextSeg);+ + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);+ ControlFile->minSafeStartPoint.xlogid = nextLog;+ ControlFile->minSafeStartPoint.xrecoff = nextSeg * XLogSegSize;+ UpdateControlFile();+ LWLockRelease(ControlFileLock);+ }+
return fd;
}
if (errno != ENOENT) /* unexpected failure? */
***************
*** 4733,4754 **** exitArchiveRecovery(TimeLineID endTLI, uint32 endLogId, uint32 endLogSeg)
unlink(recoveryPath); /* ignore any error */
/*
! * Rename the config file out of the way, so that we don't accidentally
! * re-enter archive recovery mode in a subsequent crash.
*/
- unlink(RECOVERY_COMMAND_DONE);- if (rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE) != 0)- ereport(FATAL,- (errcode_for_file_access(),- errmsg("could not rename file \"%s\" to \"%s\": %m",- RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE)));
ereport(LOG,
(errmsg("archive recovery complete")));
}
/*
! * For point-in-time recovery, this function decides whether we want to
* stop applying the XLOG at or after the current record.
*
* Returns TRUE if we are stopping, FALSE otherwise. On TRUE return,
--- 4821,4840 ----
unlink(recoveryPath); /* ignore any error */
/*
! * As of 8.4 we no longer rename the recovery.conf file out of the
! * way until after we have performed a full checkpoint. This ensures
! * that any crash between now and the end of the checkpoint does not
! * attempt to restart from a WAL file that is no longer available to us.
! * As soon as we remove recovery.conf we lose our recovery_command and
! * cannot reaccess WAL files from the archive.
*/
ereport(LOG,
(errmsg("archive recovery complete")));
}
/*
! * For archive recovery, this function decides whether we want to
* stop applying the XLOG at or after the current record.
*
* Returns TRUE if we are stopping, FALSE otherwise. On TRUE return,
***************
*** 4876,4881 **** StartupXLOG(void)
--- 4962,4968 ----
CheckPoint checkPoint;
bool wasShutdown;
bool reachedStopPoint = false;
+ bool performedRecovery = false;
bool haveBackupLabel = false;
XLogRecPtr RecPtr,
LastRec,
***************
*** 4888,4893 **** StartupXLOG(void)
--- 4975,4982 ----
uint32 freespace;
TransactionId oldestActiveXID;
+ XLogCtl->SharedRecoveryProcessingMode = true;+
/*
* Read control file and check XLOG status looks valid.
*
***************
*** 5108,5116 **** StartupXLOG(void)
--- 5197,5211 ----
if (minRecoveryLoc.xlogid != 0 || minRecoveryLoc.xrecoff != 0)
ControlFile->minRecoveryPoint = minRecoveryLoc;
ControlFile->time = (pg_time_t) time(NULL);
+ /* No need to hold ControlFileLock yet, we aren't up far enough */
UpdateControlFile();
/*
+ * Reset pgstat data, because it may be invalid after recovery.+ */+ pgstat_reset_all();+ + /*
* If there was a backup label file, it's done its job and the info
* has now been propagated into pg_control. We must get rid of the
* label file so that if we crash during recovery, we'll pick up at
***************
*** 5220,5225 **** StartupXLOG(void)
--- 5315,5348 ----
LastRec = ReadRecPtr;
+ /*+ * Can we signal Postmaster to enter consistent recovery mode?+ *+ * There are two points in the log that we must pass. The first+ * is minRecoveryPoint, which is the LSN at the time the+ * base backup was taken that we are about to rollforward from.+ * If recovery has ever crashed or was stopped there is also+ * another point also: minSafeStartPoint, which we know the+ * latest LSN that recovery could have reached prior to crash.+ *+ * We must also have assembled sufficient information about+ * transaction state to allow valid snapshots to be taken.+ */+ if (!reachedSafeStartPoint &&+ XLByteLE(ControlFile->minSafeStartPoint, EndRecPtr) && + XLByteLE(ControlFile->minRecoveryPoint, EndRecPtr))+ {+ reachedSafeStartPoint = true;+ if (InArchiveRecovery)+ {+ ereport(LOG,+ (errmsg("database has now reached consistent state at %X/%X",+ EndRecPtr.xlogid, EndRecPtr.xrecoff)));+ if (IsUnderPostmaster)+ SendPostmasterSignal(PMSIGNAL_RECOVERY_START);+ }+ }+
record = ReadRecord(NULL, LOG);
} while (record != NULL && recoveryContinue);
***************
*** 5241,5246 **** StartupXLOG(void)
--- 5364,5370 ----
/* there are no WAL records following the checkpoint */
ereport(LOG,
(errmsg("redo is not required")));
+ reachedSafeStartPoint = true;
}
}
***************
*** 5254,5269 **** StartupXLOG(void)
/*
* Complain if we did not roll forward far enough to render the backup
! * dump consistent.
*/
! if (XLByteLT(EndOfLog, ControlFile->minRecoveryPoint))
{
if (reachedStopPoint) /* stopped because of stop request */
ereport(FATAL,
(errmsg("requested recovery stop point is before end time of backup dump")));
else /* ran off end of WAL */
ereport(FATAL,
! (errmsg("WAL ends before end time of backup dump")));
}
/*
--- 5378,5393 ----
/*
* Complain if we did not roll forward far enough to render the backup
! * dump consistent and start safely.
*/
! if (InArchiveRecovery && !reachedSafeStartPoint)
{
if (reachedStopPoint) /* stopped because of stop request */
ereport(FATAL,
(errmsg("requested recovery stop point is before end time of backup dump")));
else /* ran off end of WAL */
ereport(FATAL,
! (errmsg("end of WAL reached before end time of backup dump")));
}
/*
***************
*** 5378,5416 **** StartupXLOG(void)
XLogCheckInvalidPages();
/*
! * Reset pgstat data, because it may be invalid after recovery.
*/
! pgstat_reset_all();
! /*
! * 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.
! */
! CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
}
- /*- * Preallocate additional log files, if wanted.- */- PreallocXlogFiles(EndOfLog);- - /*- * Okay, we're officially UP.- */- InRecovery = false;- - ControlFile->state = DB_IN_PRODUCTION;- ControlFile->time = (pg_time_t) time(NULL);- UpdateControlFile();- - /* start the archive_timeout timer running */- XLogCtl->Write.lastSegSwitchTime = ControlFile->time;-
/* initialize shared-memory copy of latest checkpoint XID/epoch */
XLogCtl->ckptXidEpoch = ControlFile->checkPointCopy.nextXidEpoch;
XLogCtl->ckptXid = ControlFile->checkPointCopy.nextXid;
--- 5502,5515 ----
XLogCheckInvalidPages();
/*
! * Finally exit recovery and mark that in WAL. Pre-8.4 we wrote
! * a shutdown checkpoint here, but we ask bgwriter to do that now.
*/
! exitRecovery();
! performedRecovery = true;
}
/* initialize shared-memory copy of latest checkpoint XID/epoch */
XLogCtl->ckptXidEpoch = ControlFile->checkPointCopy.nextXidEpoch;
XLogCtl->ckptXid = ControlFile->checkPointCopy.nextXid;
***************
*** 5444,5449 **** StartupXLOG(void)
--- 5543,5641 ----
readRecordBuf = NULL;
readRecordBufSize = 0;
}
+ + /*+ * Prior to 8.4 we wrote a Shutdown Checkpoint at the end of recovery.+ * This could add minutes to the startup time, so we want bgwriter+ * to perform it. This then frees the Startup process to complete so we can+ * allow transactions and WAL inserts. We still write a checkpoint, but+ * it will be an online checkpoint. Online checkpoints have a redo+ * location that can be prior to the actual checkpoint record. So we want+ * to derive that redo location *before* we let anybody else write WAL,+ * otherwise we might miss some WAL records if we crash.+ */+ if (performedRecovery)+ {+ XLogRecPtr redo;+ + /* + * We must grab the pointer before anybody writes WAL + */+ redo = GetRedoLocationForCheckpoint();+ + /* + * Set up information for the bgwriter, but if it is not active+ * for whatever reason, perform the checkpoint ourselves.+ */+ if (SetRedoLocationForArchiveCheckpoint(redo))+ {+ /*+ * Okay, we can come up now. Allow others to write WAL.+ */+ XLogCtl->SharedRecoveryProcessingMode = false;+ + /*+ * Now request checkpoint from bgwriter.+ */+ RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_IMMEDIATE);+ }+ else+ {+ /*+ * Startup process performs the checkpoint, but defers+ * the change in processing mode until afterwards.+ */+ CreateCheckPoint(CHECKPOINT_FORCE | CHECKPOINT_IMMEDIATE);+ }+ }+ else+ {+ /*+ * No recovery, so lets just get on with it. + */+ LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);+ ControlFile->state = DB_IN_PRODUCTION;+ ControlFile->time = (pg_time_t) time(NULL);+ UpdateControlFile();+ LWLockRelease(ControlFileLock);+ }+ + /*+ * Okay, we can come up now. Allow others to write WAL.+ */+ XLogCtl->SharedRecoveryProcessingMode = false;+ + /* start the archive_timeout timer running */+ XLogCtl->Write.lastSegSwitchTime = (pg_time_t) time(NULL);+ }+ + /*+ * IsRecoveryProcessingMode()+ *+ * Fast test for whether we're still in recovery or not. We test the shared+ * state each time only until we leave recovery mode. After that we never+ * look again, relying upon the settings of our local state variables. This+ * is designed to avoid the need for a separate initialisation step.+ */+ bool+ IsRecoveryProcessingMode(void)+ {+ if (knownProcessingMode && !LocalRecoveryProcessingMode)+ return false;+ + {+ /* use volatile pointer to prevent code rearrangement */+ volatile XLogCtlData *xlogctl = XLogCtl;+ + if (xlogctl == NULL)+ return false;+ + LocalRecoveryProcessingMode = XLogCtl->SharedRecoveryProcessingMode;+ }+ + knownProcessingMode = true;+ + return LocalRecoveryProcessingMode;
}
/*
***************
*** 5701,5720 **** ShutdownXLOG(int code, Datum arg)
static void
LogCheckpointStart(int flags)
{
! elog(LOG, "checkpoint starting:%s%s%s%s%s%s",
! (flags & CHECKPOINT_IS_SHUTDOWN) ? " shutdown" : "",
! (flags & CHECKPOINT_IMMEDIATE) ? " immediate" : "",
! (flags & CHECKPOINT_FORCE) ? " force" : "",
! (flags & CHECKPOINT_WAIT) ? " wait" : "",
! (flags & CHECKPOINT_CAUSE_XLOG) ? " xlog" : "",
! (flags & CHECKPOINT_CAUSE_TIME) ? " time" : "");
}
/*
* Log end of a checkpoint.
*/
static void
! LogCheckpointEnd(void)
{
long write_secs,
sync_secs,
--- 5893,5916 ----
static void
LogCheckpointStart(int flags)
{
! if (flags & CHECKPOINT_RESTARTPOINT)
! elog(LOG, "restartpoint starting:%s",
! (flags & CHECKPOINT_IMMEDIATE) ? " immediate" : "");
! else
! elog(LOG, "checkpoint starting:%s%s%s%s%s%s",
! (flags & CHECKPOINT_IS_SHUTDOWN) ? " shutdown" : "",
! (flags & CHECKPOINT_IMMEDIATE) ? " immediate" : "",
! (flags & CHECKPOINT_FORCE) ? " force" : "",
! (flags & CHECKPOINT_WAIT) ? " wait" : "",
! (flags & CHECKPOINT_CAUSE_XLOG) ? " xlog" : "",
! (flags & CHECKPOINT_CAUSE_TIME) ? " time" : "");
}
/*
* Log end of a checkpoint.
*/
static void
! LogCheckpointEnd(int flags)
{
long write_secs,
sync_secs,
***************
*** 5737,5753 **** LogCheckpointEnd(void)
CheckpointStats.ckpt_sync_end_t,
&sync_secs, &sync_usecs);
! elog(LOG, "checkpoint complete: wrote %d buffers (%.1f%%); "
! "%d transaction log file(s) added, %d removed, %d recycled; "
! "write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s",
! CheckpointStats.ckpt_bufs_written,
! (double) CheckpointStats.ckpt_bufs_written * 100 / NBuffers,
! CheckpointStats.ckpt_segs_added,
! CheckpointStats.ckpt_segs_removed,
! CheckpointStats.ckpt_segs_recycled,
! write_secs, write_usecs / 1000,
! sync_secs, sync_usecs / 1000,
! total_secs, total_usecs / 1000);
}
/*
--- 5933,5958 ----
CheckpointStats.ckpt_sync_end_t,
&sync_secs, &sync_usecs);
! if (flags & CHECKPOINT_RESTARTPOINT)
! elog(LOG, "restartpoint complete: wrote %d buffers (%.1f%%); "
! "write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s",
! CheckpointStats.ckpt_bufs_written,
! (double) CheckpointStats.ckpt_bufs_written * 100 / NBuffers,
! write_secs, write_usecs / 1000,
! sync_secs, sync_usecs / 1000,
! total_secs, total_usecs / 1000);
! else
! elog(LOG, "checkpoint complete: wrote %d buffers (%.1f%%); "
! "%d transaction log file(s) added, %d removed, %d recycled; "
! "write=%ld.%03d s, sync=%ld.%03d s, total=%ld.%03d s",
! CheckpointStats.ckpt_bufs_written,
! (double) CheckpointStats.ckpt_bufs_written * 100 / NBuffers,
! CheckpointStats.ckpt_segs_added,
! CheckpointStats.ckpt_segs_removed,
! CheckpointStats.ckpt_segs_recycled,
! write_secs, write_usecs / 1000,
! sync_secs, sync_usecs / 1000,
! total_secs, total_usecs / 1000);
}
/*
***************
*** 5772,5788 **** CreateCheckPoint(int flags)
XLogRecPtr recptr;
XLogCtlInsert *Insert = &XLogCtl->Insert;
XLogRecData rdata;
- uint32 freespace;
uint32 _logId;
uint32 _logSeg;
TransactionId *inCommitXids;
int nInCommit;
/*
* Acquire CheckpointLock to ensure only one checkpoint happens at a time.
! * (This is just pro forma, since in the present system structure there is
! * only one process that is allowed to issue checkpoints at any given
! * time.)
*/
LWLockAcquire(CheckpointLock, LW_EXCLUSIVE);
--- 5977,5992 ----
XLogRecPtr recptr;
XLogCtlInsert *Insert = &XLogCtl->Insert;
XLogRecData rdata;
uint32 _logId;
uint32 _logSeg;
TransactionId *inCommitXids;
int nInCommit;
+ bool leavingArchiveRecovery = false;
/*
* Acquire CheckpointLock to ensure only one checkpoint happens at a time.
! * That shouldn't be happening, but checkpoints are an important aspect
! * of our resilience, so we take no chances.
*/
LWLockAcquire(CheckpointLock, LW_EXCLUSIVE);
***************
*** 5797,5811 **** CreateCheckPoint(int flags)
--- 6001,6024 ----
CheckpointStats.ckpt_start_t = GetCurrentTimestamp();
/*
+ * Find out if this is the first checkpoint after archive recovery.+ */+ LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);+ leavingArchiveRecovery = (ControlFile->state == DB_IN_ARCHIVE_RECOVERY);+ LWLockRelease(ControlFileLock);+ + /*
* Use a critical section to force system panic if we have trouble.
*/
START_CRIT_SECTION();
if (shutdown)
{
+ LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
ControlFile->state = DB_SHUTDOWNING;
ControlFile->time = (pg_time_t) time(NULL);
UpdateControlFile();
+ LWLockRelease(ControlFileLock);
}
/*
***************
*** 5861,5901 **** CreateCheckPoint(int flags)
}
}
! /*
! * Compute new REDO record ptr = location of next XLOG record.
! *
! * NB: this is NOT necessarily where the checkpoint record itself will be,
! * since other backends may insert more XLOG records while we're off doing
! * the buffer flush work. Those XLOG records are logically after the
! * checkpoint, even though physically before it. Got that?
! */
! freespace = INSERT_FREESPACE(Insert);
! if (freespace < SizeOfXLogRecord)
! {
! (void) AdvanceXLInsertBuffer(false);
! /* OK to ignore update return flag, since we will do flush anyway */
! freespace = INSERT_FREESPACE(Insert);
! }
! INSERT_RECPTR(checkPoint.redo, Insert, Insert->curridx);
!
! /*
! * Here we update the shared RedoRecPtr for future XLogInsert calls; this
! * must be done while holding the insert lock AND the info_lck.
! *
! * Note: if we fail to complete the checkpoint, RedoRecPtr will be left
! * pointing past where it really needs to point. This is okay; the only
! * consequence is that XLogInsert might back up whole buffers that it
! * didn't really need to. We can't postpone advancing RedoRecPtr because
! * XLogInserts that happen while we are dumping buffers must assume that
! * their buffer changes are not included in the checkpoint.
! */
{
! /* use volatile pointer to prevent code rearrangement */
! volatile XLogCtlData *xlogctl = XLogCtl;
!
! SpinLockAcquire(&xlogctl->info_lck);
! RedoRecPtr = xlogctl->Insert.RedoRecPtr = checkPoint.redo;
! SpinLockRelease(&xlogctl->info_lck);
}
/*
--- 6074,6092 ----
}
}
! if (leavingArchiveRecovery)
! checkPoint.redo = GetRedoLocationForArchiveCheckpoint();
! else
{
! /*
! * Compute new REDO record ptr = location of next XLOG record.
! *
! * NB: this is NOT necessarily where the checkpoint record itself will be,
! * since other backends may insert more XLOG records while we're off doing
! * the buffer flush work. Those XLOG records are logically after the
! * checkpoint, even though physically before it. Got that?
! */
! checkPoint.redo = GetRedoLocationForCheckpoint();
}
/*
***************
*** 6013,6023 **** CreateCheckPoint(int flags)
XLByteToSeg(ControlFile->checkPointCopy.redo, _logId, _logSeg);
/*
! * Update the control file.
*/
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
if (shutdown)
ControlFile->state = DB_SHUTDOWNED;
ControlFile->prevCheckPoint = ControlFile->checkPoint;
ControlFile->checkPoint = ProcLastRecPtr;
ControlFile->checkPointCopy = checkPoint;
--- 6204,6221 ----
XLByteToSeg(ControlFile->checkPointCopy.redo, _logId, _logSeg);
/*
! * Update the control file. In 8.4, this routine becomes the primary
! * point for recording changes of state in the control file at the
! * end of recovery. Postmaster state already shows us being in
! * normal running mode, but it is only after this point that we
! * are completely free of reperforming a recovery if we crash. Note
! * that this is executed by bgwriter after the death of Startup process.
*/
LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
if (shutdown)
ControlFile->state = DB_SHUTDOWNED;
+ else+ ControlFile->state = DB_IN_PRODUCTION;
ControlFile->prevCheckPoint = ControlFile->checkPoint;
ControlFile->checkPoint = ProcLastRecPtr;
ControlFile->checkPointCopy = checkPoint;
***************
*** 6025,6030 **** CreateCheckPoint(int flags)
--- 6223,6243 ----
UpdateControlFile();
LWLockRelease(ControlFileLock);
+ if (leavingArchiveRecovery)+ {+ /*+ * Rename the config file out of the way, so that we don't accidentally+ * re-enter archive recovery mode in a subsequent crash. Prior to+ * 8.4 this step was performed at end of exitArchiveRecovery().+ */+ unlink(RECOVERY_COMMAND_DONE);+ if (rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE) != 0)+ ereport(ERROR,+ (errcode_for_file_access(),+ errmsg("could not rename file \"%s\" to \"%s\": %m",+ RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE)));+ }+
/* Update shared-memory copy of checkpoint XID/epoch */
{
/* use volatile pointer to prevent code rearrangement */
***************
*** 6068,6082 **** CreateCheckPoint(int flags)
* Truncate pg_subtrans if possible. We can throw away all data before
* the oldest XMIN of any running transaction. No future transaction will
* attempt to reference any pg_subtrans entry older than that (see Asserts
! * in subtrans.c). During recovery, though, we mustn't do this because
! * StartupSUBTRANS hasn't been called yet.
*/
! if (!InRecovery)
TruncateSUBTRANS(GetOldestXmin(true, false));
/* All real work is done, but log before releasing lock. */
if (log_checkpoints)
! LogCheckpointEnd();
TRACE_POSTGRESQL_CHECKPOINT_DONE(CheckpointStats.ckpt_bufs_written,
NBuffers, CheckpointStats.ckpt_segs_added,
--- 6281,6294 ----
* Truncate pg_subtrans if possible. We can throw away all data before
* the oldest XMIN of any running transaction. No future transaction will
* attempt to reference any pg_subtrans entry older than that (see Asserts
! * in subtrans.c).
*/
! if (!shutdown)
TruncateSUBTRANS(GetOldestXmin(true, false));
/* All real work is done, but log before releasing lock. */
if (log_checkpoints)
! LogCheckpointEnd(flags);
TRACE_POSTGRESQL_CHECKPOINT_DONE(CheckpointStats.ckpt_bufs_written,
NBuffers, CheckpointStats.ckpt_segs_added,
***************
*** 6085,6090 **** CreateCheckPoint(int flags)
--- 6297,6347 ----
LWLockRelease(CheckpointLock);
}
+ + /* + * GetRedoLocationForCheckpoint()+ *+ * When !IsRecoveryProcessingMode() this must be called while holding + * WALInsertLock().+ */+ static XLogRecPtr+ GetRedoLocationForCheckpoint()+ {+ XLogCtlInsert *Insert = &XLogCtl->Insert;+ uint32 freespace;+ XLogRecPtr redo;+ + freespace = INSERT_FREESPACE(Insert);+ if (freespace < SizeOfXLogRecord)+ {+ (void) AdvanceXLInsertBuffer(false);+ /* OK to ignore update return flag, since we will do flush anyway */+ freespace = INSERT_FREESPACE(Insert);+ }+ INSERT_RECPTR(redo, Insert, Insert->curridx);+ + /*+ * Here we update the shared RedoRecPtr for future XLogInsert calls; this+ * must be done while holding the insert lock AND the info_lck.+ *+ * Note: if we fail to complete the checkpoint, RedoRecPtr will be left+ * pointing past where it really needs to point. This is okay; the only+ * consequence is that XLogInsert might back up whole buffers that it+ * didn't really need to. We can't postpone advancing RedoRecPtr because+ * XLogInserts that happen while we are dumping buffers must assume that+ * their buffer changes are not included in the checkpoint.+ */+ {+ /* use volatile pointer to prevent code rearrangement */+ volatile XLogCtlData *xlogctl = XLogCtl;+ + SpinLockAcquire(&xlogctl->info_lck);+ RedoRecPtr = xlogctl->Insert.RedoRecPtr = redo;+ SpinLockRelease(&xlogctl->info_lck);+ }+ + return redo;+ }
/*
* Flush all data in shared memory to disk, and fsync
***************
*** 6150,6180 **** RecoveryRestartPoint(const CheckPoint *checkPoint)
}
}
/*
! * OK, force data out to disk
*/
! CheckPointGuts(checkPoint->redo, CHECKPOINT_IMMEDIATE);
/*
! * Update pg_control so that any subsequent crash will restart from this
! * checkpoint. Note: ReadRecPtr gives the XLOG address of the checkpoint
! * record itself.
*/
- ControlFile->prevCheckPoint = ControlFile->checkPoint;- ControlFile->checkPoint = ReadRecPtr;- ControlFile->checkPointCopy = *checkPoint;- ControlFile->time = (pg_time_t) time(NULL);- UpdateControlFile();
ereport((recoveryLogRestartpoints ? LOG : DEBUG2),
! (errmsg("recovery restart point at %X/%X",
! checkPoint->redo.xlogid, checkPoint->redo.xrecoff)));
! if (recoveryLastXTime)
! ereport((recoveryLogRestartpoints ? LOG : DEBUG2),
! (errmsg("last completed transaction was at log time %s",
! timestamptz_to_str(recoveryLastXTime))));
! }
/*
* Write a NEXTOID log record
*/
--- 6407,6477 ----
}
}
+ RequestRestartPoint(ReadRecPtr, checkPoint, reachedSafeStartPoint);+ }+ + /*+ * As of 8.4, RestartPoints are always created by the bgwriter+ * once we have reachedSafeStartPoint. We use bgwriter's shared memory+ * area wherever we call it from, to keep better code structure.+ */+ void+ CreateRestartPoint(const XLogRecPtr ReadPtr, const CheckPoint *restartPoint, int flags)+ {+ if (recoveryLogRestartpoints || log_checkpoints)+ {+ /*+ * Prepare to accumulate statistics.+ */+ + MemSet(&CheckpointStats, 0, sizeof(CheckpointStats));+ CheckpointStats.ckpt_start_t = GetCurrentTimestamp();+ + LogCheckpointStart(CHECKPOINT_RESTARTPOINT | flags);+ }+ + /*+ * Acquire CheckpointLock to ensure only one restartpoint happens at a time.+ * We rely on this lock to ensure that the startup process doesn't exit+ * Recovery while we are half way through a restartpoint.+ */+ LWLockAcquire(CheckpointLock, LW_EXCLUSIVE);+ + CheckPointGuts(restartPoint->redo, CHECKPOINT_RESTARTPOINT | flags);+
/*
! * Update pg_control, using current time
*/
! LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
! ControlFile->prevCheckPoint = ControlFile->checkPoint;
! ControlFile->checkPoint = ReadPtr;
! ControlFile->checkPointCopy = *restartPoint;
! ControlFile->time = (pg_time_t) time(NULL);
! UpdateControlFile();
! LWLockRelease(ControlFileLock);
/*
! * Currently, there is no need to truncate pg_subtrans during recovery.
! * If we did do that, we will need to have called StartupSUBTRANS()
! * already and then TruncateSUBTRANS() would go here.
*/
+ /* All real work is done, but log before releasing lock. */+ if (recoveryLogRestartpoints || log_checkpoints)+ LogCheckpointEnd(CHECKPOINT_RESTARTPOINT);+
ereport((recoveryLogRestartpoints ? LOG : DEBUG2),
! (errmsg("recovery restart point at %X/%X",
! restartPoint->redo.xlogid, restartPoint->redo.xrecoff)));
+ if (recoveryLastXTime)+ ereport((recoveryLogRestartpoints ? LOG : DEBUG2),+ (errmsg("last completed transaction was at log time %s",+ timestamptz_to_str(recoveryLastXTime))));+ + LWLockRelease(CheckpointLock);+ }+
/*
* Write a NEXTOID log record
*/
***************
*** 6237,6243 **** RequestXLogSwitch(void)
}
/*
! * XLOG resource manager's routines
*/
void
xlog_redo(XLogRecPtr lsn, XLogRecord *record)
--- 6534,6596 ----
}
/*
! * exitRecovery()
! *
! * Exit recovery state and write a XLOG_RECOVERY_END record. This is the
! * only record type that can record a change of timelineID. We assume
! * caller has already set ThisTimeLineID, if appropriate.
! */
! static void
! exitRecovery(void)
! {
! XLogRecData rdata;
!
! rdata.buffer = InvalidBuffer;
! rdata.data = (char *) (&ThisTimeLineID);
! rdata.len = sizeof(TimeLineID);
! rdata.next = NULL;
!
! /*
! * If a restartpoint is in progress, we will not be able to successfully
! * acquire CheckpointLock. If bgwriter is still in progress then send
! * a second signal to nudge bgwriter to go faster so we can avoid delay.
! * Then wait for lock, so we know the restartpoint has completed. We do
! * this because we don't want to interrupt the restartpoint half way
! * through, which might leave us in a mess and we want to be robust. We're
! * going to checkpoint soon anyway, so not it's not wasted effort.
! */
! if (LWLockConditionalAcquire(CheckpointLock, LW_EXCLUSIVE))
! LWLockRelease(CheckpointLock);
! else
! {
! RequestRestartPointCompletion();
! ereport(DEBUG1,
! (errmsg("startup process waiting for restartpoint to complete")));
! LWLockAcquire(CheckpointLock, LW_EXCLUSIVE);
! LWLockRelease(CheckpointLock);
! }
!
! /*
! * This is the only type of WAL message that can be inserted during
! * recovery. This ensures that we don't allow others to get access
! * until after we have changed state.
! */
! (void) XLogInsert(RM_XLOG_ID, XLOG_RECOVERY_END, &rdata);
!
! /*
! * We don't XLogFlush() here otherwise we'll end up zeroing the WAL
! * file ourselves. So just let bgwriter's forthcoming checkpoint do
! * that for us.
! */
!
! InRecovery = false;
! }
!
! /*
! * XLOG resource manager's routines.
! *
! * Definitions of message info are in include/catalog/pg_control.h,
! * though not all messages relate to control file processing.
*/
void
xlog_redo(XLogRecPtr lsn, XLogRecord *record)
***************
*** 6271,6293 **** xlog_redo(XLogRecPtr lsn, XLogRecord *record)
ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
! /*
! * TLI may change in a shutdown checkpoint, but it shouldn't decrease
*/
- if (checkPoint.ThisTimeLineID != ThisTimeLineID)- {- if (checkPoint.ThisTimeLineID < ThisTimeLineID ||- !list_member_int(expectedTLIs,- (int) checkPoint.ThisTimeLineID))- ereport(PANIC,- (errmsg("unexpected timeline ID %u (after %u) in checkpoint record",- checkPoint.ThisTimeLineID, ThisTimeLineID)));- /* Following WAL records should be run with new TLI */- ThisTimeLineID = checkPoint.ThisTimeLineID;- }
RecoveryRestartPoint(&checkPoint);
}
else if (info == XLOG_CHECKPOINT_ONLINE)
{
CheckPoint checkPoint;
--- 6624,6663 ----
ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
! /*
! * TLI no longer changes at shutdown checkpoint, since as of 8.4,
! * shutdown checkpoints only occur at shutdown. Much less confusing.
*/
RecoveryRestartPoint(&checkPoint);
}
+ else if (info == XLOG_RECOVERY_END)+ {+ TimeLineID tli;+ + memcpy(&tli, XLogRecGetData(record), sizeof(TimeLineID));+ + /*+ * TLI may change when recovery ends, but it shouldn't decrease.+ *+ * This is the only WAL record that can tell us to change timelineID+ * while we process WAL records. + *+ * We can *choose* to stop recovery at any point, generating a+ * new timelineID which is recorded using this record type.+ */+ if (tli != ThisTimeLineID)+ {+ if (tli < ThisTimeLineID ||+ !list_member_int(expectedTLIs,+ (int) tli))+ ereport(PANIC,+ (errmsg("unexpected timeline ID %u (after %u) at recovery end record",+ tli, ThisTimeLineID)));+ /* Following WAL records should be run with new TLI */+ ThisTimeLineID = tli;+ }+ }
else if (info == XLOG_CHECKPOINT_ONLINE)
{
CheckPoint checkPoint;
***************
*** 6309,6315 **** xlog_redo(XLogRecPtr lsn, XLogRecord *record)
ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
! /* TLI should not change in an on-line checkpoint */
if (checkPoint.ThisTimeLineID != ThisTimeLineID)
ereport(PANIC,
(errmsg("unexpected timeline ID %u (should be %u) in checkpoint record",
--- 6679,6685 ----
ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
! /* TLI must not change at a checkpoint */
if (checkPoint.ThisTimeLineID != ThisTimeLineID)
ereport(PANIC,
(errmsg("unexpected timeline ID %u (should be %u) in checkpoint record",
***************
*** 6545,6550 **** pg_start_backup(PG_FUNCTION_ARGS)
--- 6915,6926 ----
errhint("archive_command must be defined before "
"online backups can be made safely.")));
+ if (IsRecoveryProcessingMode())+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("recovery is in progress"),+ errhint("WAL control functions cannot be executed during recovery.")));+
backupidstr = text_to_cstring(backupid);
/*
***************
*** 6710,6715 **** pg_stop_backup(PG_FUNCTION_ARGS)
--- 7086,7097 ----
errmsg("WAL archiving is not active"),
errhint("archive_mode must be enabled at server start.")));
+ if (IsRecoveryProcessingMode())+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("recovery is in progress"),+ errhint("WAL control functions cannot be executed during recovery.")));+
/*
* OK to clear forcePageWrites
*/
***************
*** 6865,6870 **** pg_switch_xlog(PG_FUNCTION_ARGS)
--- 7247,7258 ----
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be superuser to switch transaction log files"))));
+ if (IsRecoveryProcessingMode())+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("recovery is in progress"),+ errhint("WAL control functions cannot be executed during recovery.")));+
switchpoint = RequestXLogSwitch();
/*
***************
*** 6887,6892 **** pg_current_xlog_location(PG_FUNCTION_ARGS)
--- 7275,7286 ----
{
char location[MAXFNAMELEN];
+ if (IsRecoveryProcessingMode())+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("recovery is in progress"),+ errhint("WAL control functions cannot be executed during recovery.")));+
/* Make sure we have an up-to-date local LogwrtResult */
{
/* use volatile pointer to prevent code rearrangement */
***************
*** 6914,6919 **** pg_current_xlog_insert_location(PG_FUNCTION_ARGS)
--- 7308,7319 ----
XLogRecPtr current_recptr;
char location[MAXFNAMELEN];
+ if (IsRecoveryProcessingMode())+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("recovery is in progress"),+ errhint("WAL control functions cannot be executed during recovery.")));+
/*
* Get the current end-of-WAL position ... shared lock is sufficient
*/
*** src/backend/commands/dbcommands.c
--- src/backend/commands/dbcommands.c
***************
*** 1976,1981 **** dbase_redo(XLogRecPtr lsn, XLogRecord *record)
--- 1976,1986 ----
* We don't need to copy subdirectories
*/
copydir(src_path, dst_path, false);
+ + /*+ * Flat files are updated immediately following transaction commit.+ * Nothing to do here.+ */
}
else if (info == XLOG_DBASE_DROP)
{
***************
*** 1998,2003 **** dbase_redo(XLogRecPtr lsn, XLogRecord *record)
--- 2003,2012 ----
ereport(WARNING,
(errmsg("some useless files may be left behind in old database directory \"%s\"",
dst_path)));
+ /*+ * Flat files are updated immediately following transaction commit.+ * Nothing to do here.+ */
}
else
elog(PANIC, "dbase_redo: unknown op code %u", info);
*** src/backend/postmaster/bgwriter.c
--- src/backend/postmaster/bgwriter.c
***************
*** 49,54 ****
--- 49,55 ----
#include <unistd.h>
#include "access/xlog_internal.h"
+ #include "catalog/pg_control.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
***************
*** 129,134 **** typedef struct
--- 130,142 ----
int ckpt_flags; /* checkpoint flags, as defined in xlog.h */
+ /* + * When the Startup process wants bgwriter to perform a restartpoint, it + * sets these fields so that we can update the control file afterwards.+ */+ XLogRecPtr ReadPtr; /* Requested log pointer */+ CheckPoint restartPoint; /* restartPoint data for ControlFile */+
uint32 num_backend_writes; /* counts non-bgwriter buffer writes */
int num_requests; /* current # of requests */
***************
*** 165,171 **** static bool ckpt_active = false;
/* these values are valid when ckpt_active is true: */
static pg_time_t ckpt_start_time;
! static XLogRecPtr ckpt_start_recptr;
static double ckpt_cached_elapsed;
static pg_time_t last_checkpoint_time;
--- 173,179 ----
/* these values are valid when ckpt_active is true: */
static pg_time_t ckpt_start_time;
! static XLogRecPtr ckpt_start_recptr; /* not used if IsRecoveryProcessingMode */
static double ckpt_cached_elapsed;
static pg_time_t last_checkpoint_time;
***************
*** 197,202 **** BackgroundWriterMain(void)
--- 205,211 ----
{
sigjmp_buf local_sigjmp_buf;
MemoryContext bgwriter_context;
+ bool BgWriterRecoveryMode;
BgWriterShmem->bgwriter_pid = MyProcPid;
am_bg_writer = true;
***************
*** 355,370 **** BackgroundWriterMain(void)
*/
PG_SETMASK(&UnBlockSig);
/*
* Loop forever
*/
for (;;)
{
- bool do_checkpoint = false;- int flags = 0;- pg_time_t now;- int elapsed_secs;-
/*
* Emergency bailout if postmaster has died. This is to avoid the
* necessity for manual cleanup of all postmaster children.
--- 364,380 ----
*/
PG_SETMASK(&UnBlockSig);
+ BgWriterRecoveryMode = IsRecoveryProcessingMode();+ + if (BgWriterRecoveryMode)+ elog(DEBUG1, "bgwriter starting during recovery, pid = %u", + BgWriterShmem->bgwriter_pid);+
/*
* Loop forever
*/
for (;;)
{
/*
* Emergency bailout if postmaster has died. This is to avoid the
* necessity for manual cleanup of all postmaster children.
***************
*** 372,499 **** BackgroundWriterMain(void)
if (!PostmasterIsAlive(true))
exit(1);
- /*- * Process any requests or signals received recently.- */- AbsorbFsyncRequests();-
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
- if (checkpoint_requested)- {- checkpoint_requested = false;- do_checkpoint = true;- BgWriterStats.m_requested_checkpoints++;- }- if (shutdown_requested)- {- /*- * From here on, elog(ERROR) should end with exit(1), not send- * control back to the sigsetjmp block above- */- ExitOnAnyError = true;- /* Close down the database */- ShutdownXLOG(0, 0);- /* Normal exit from the bgwriter is here */- proc_exit(0); /* done */- }
! /*
! * Force a checkpoint if too much time has elapsed since the last one.
! * Note that we count a timed checkpoint in stats only when this
! * occurs without an external request, but we set the CAUSE_TIME flag
! * bit even if there is also an external request.
! */
! now = (pg_time_t) time(NULL);
! elapsed_secs = now - last_checkpoint_time;
! if (elapsed_secs >= CheckPointTimeout)
{
! if (!do_checkpoint)
! BgWriterStats.m_timed_checkpoints++;
! do_checkpoint = true;
! flags |= CHECKPOINT_CAUSE_TIME;
! }
! /*
! * Do a checkpoint if requested, otherwise do one cycle of
! * dirty-buffer writing.
! */
! if (do_checkpoint)
! {
! /* use volatile pointer to prevent code rearrangement */
! volatile BgWriterShmemStruct *bgs = BgWriterShmem;
! /*
! * Atomically fetch the request flags to figure out what kind of a
! * checkpoint we should perform, and increase the started-counter
! * to acknowledge that we've started a new checkpoint.
! */
! SpinLockAcquire(&bgs->ckpt_lck);
! flags |= bgs->ckpt_flags;
! bgs->ckpt_flags = 0;
! bgs->ckpt_started++;
! SpinLockRelease(&bgs->ckpt_lck);
! /*
! * We will warn if (a) too soon since last checkpoint (whatever
! * caused it) and (b) somebody set the CHECKPOINT_CAUSE_XLOG flag
! * since the last checkpoint start. Note in particular that this
! * implementation will not generate warnings caused by
! * CheckPointTimeout < CheckPointWarning.
! */
! if ((flags & CHECKPOINT_CAUSE_XLOG) &&
! elapsed_secs < CheckPointWarning)
! ereport(LOG,
! (errmsg("checkpoints are occurring too frequently (%d seconds apart)",
! elapsed_secs),
! errhint("Consider increasing the configuration parameter \"checkpoint_segments\".")));
/*
! * Initialize bgwriter-private variables used during checkpoint.
*/
! ckpt_active = true;
! ckpt_start_recptr = GetInsertRecPtr();
! ckpt_start_time = now;
! ckpt_cached_elapsed = 0;
! /*
! * Do the checkpoint.
! */
! CreateCheckPoint(flags);
/*
! * After any checkpoint, close all smgr files. This is so we
! * won't hang onto smgr references to deleted files indefinitely.
*/
! smgrcloseall();
/*
! * Indicate checkpoint completion to any waiting backends.
*/
! SpinLockAcquire(&bgs->ckpt_lck);
! bgs->ckpt_done = bgs->ckpt_started;
! SpinLockRelease(&bgs->ckpt_lck);
! ckpt_active = false;
! /*
! * Note we record the checkpoint start time not end time as
! * last_checkpoint_time. This is so that time-driven checkpoints
! * happen at a predictable spacing.
! */
! last_checkpoint_time = now;
}
- else- BgBufferSync();- - /* Check for archive_timeout and switch xlog files if necessary. */- CheckArchiveTimeout();- - /* Nap for the configured time. */- BgWriterNap();
}
}
--- 382,595 ----
if (!PostmasterIsAlive(true))
exit(1);
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
! if (BgWriterRecoveryMode)
{
! if (shutdown_requested)
! {
! /*
! * From here on, elog(ERROR) should end with exit(1), not send
! * control back to the sigsetjmp block above
! */
! ExitOnAnyError = true;
! /* Normal exit from the bgwriter is here */
! proc_exit(0); /* done */
! }
! if (!IsRecoveryProcessingMode())
! {
! elog(DEBUG2, "bgwriter changing from recovery to normal mode");
!
! InitXLOGAccess();
! BgWriterRecoveryMode = false;
!
! /*
! * Start time-driven events from now
! */
! last_checkpoint_time = last_xlog_switch_time = (pg_time_t) time(NULL);
!
! /*
! * Notice that we do *not* act on a checkpoint_requested
! * state at this point. We have changed mode, so we wish to
! * perform a checkpoint not a restartpoint.
! */
! continue;
! }
! if (checkpoint_requested)
! {
! XLogRecPtr ReadPtr;
! CheckPoint restartPoint;
!
! checkpoint_requested = false;
!
! /*
! * Initialize bgwriter-private variables used during checkpoint.
! */
! ckpt_active = true;
! ckpt_start_time = (pg_time_t) time(NULL);
! ckpt_cached_elapsed = 0;
!
! /*
! * Get the requested values from shared memory that the
! * Startup process has put there for us.
! */
! SpinLockAcquire(&BgWriterShmem->ckpt_lck);
! ReadPtr = BgWriterShmem->ReadPtr;
! memcpy(&restartPoint, &BgWriterShmem->restartPoint, sizeof(CheckPoint));
! SpinLockRelease(&BgWriterShmem->ckpt_lck);
!
! /* Use smoothed writes, until interrupted if ever */
! CreateRestartPoint(ReadPtr, &restartPoint, 0);
!
! /*
! * After any checkpoint, close all smgr files. This is so we
! * won't hang onto smgr references to deleted files indefinitely.
! */
! smgrcloseall();
!
! ckpt_active = false;
! checkpoint_requested = false;
! }
! else
! {
! /* Clean buffers dirtied by recovery */
! BgBufferSync();
! /* Nap for the configured time. */
! BgWriterNap();
! }
! }
! else /* Normal processing */
! {
! bool do_checkpoint = false;
! int flags = 0;
! pg_time_t now;
! int elapsed_secs;
/*
! * Process any requests or signals received recently.
*/
! AbsorbFsyncRequests();
! if (checkpoint_requested)
! {
! checkpoint_requested = false;
! do_checkpoint = true;
! BgWriterStats.m_requested_checkpoints++;
! }
! if (shutdown_requested)
! {
! /*
! * From here on, elog(ERROR) should end with exit(1), not send
! * control back to the sigsetjmp block above
! */
! ExitOnAnyError = true;
! /* Close down the database */
! ShutdownXLOG(0, 0);
! /* Normal exit from the bgwriter is here */
! proc_exit(0); /* done */
! }
/*
! * Force a checkpoint if too much time has elapsed since the last one.
! * Note that we count a timed checkpoint in stats only when this
! * occurs without an external request, but we set the CAUSE_TIME flag
! * bit even if there is also an external request.
*/
! now = (pg_time_t) time(NULL);
! elapsed_secs = now - last_checkpoint_time;
! if (elapsed_secs >= CheckPointTimeout)
! {
! if (!do_checkpoint)
! BgWriterStats.m_timed_checkpoints++;
! do_checkpoint = true;
! flags |= CHECKPOINT_CAUSE_TIME;
! }
/*
! * Do a checkpoint if requested, otherwise do one cycle of
! * dirty-buffer writing.
*/
! if (do_checkpoint)
! {
! /* use volatile pointer to prevent code rearrangement */
! volatile BgWriterShmemStruct *bgs = BgWriterShmem;
!
! /*
! * Atomically fetch the request flags to figure out what kind of a
! * checkpoint we should perform, and increase the started-counter
! * to acknowledge that we've started a new checkpoint.
! */
! SpinLockAcquire(&bgs->ckpt_lck);
! flags |= bgs->ckpt_flags;
! bgs->ckpt_flags = 0;
! bgs->ckpt_started++;
! SpinLockRelease(&bgs->ckpt_lck);
!
! /*
! * We will warn if (a) too soon since last checkpoint (whatever
! * caused it) and (b) somebody set the CHECKPOINT_CAUSE_XLOG flag
! * since the last checkpoint start. Note in particular that this
! * implementation will not generate warnings caused by
! * CheckPointTimeout < CheckPointWarning.
! */
! if ((flags & CHECKPOINT_CAUSE_XLOG) &&
! elapsed_secs < CheckPointWarning)
! ereport(LOG,
! (errmsg("checkpoints are occurring too frequently (%d seconds apart)",
! elapsed_secs),
! errhint("Consider increasing the configuration parameter \"checkpoint_segments\".")));
!
! /*
! * Initialize bgwriter-private variables used during checkpoint.
! */
! ckpt_active = true;
! ckpt_start_recptr = GetInsertRecPtr();
! ckpt_start_time = now;
! ckpt_cached_elapsed = 0;
!
! /*
! * Do the checkpoint.
! */
! CreateCheckPoint(flags);
!
! /*
! * After any checkpoint, close all smgr files. This is so we
! * won't hang onto smgr references to deleted files indefinitely.
! */
! smgrcloseall();
!
! /*
! * Indicate checkpoint completion to any waiting backends.
! */
! SpinLockAcquire(&bgs->ckpt_lck);
! bgs->ckpt_done = bgs->ckpt_started;
! SpinLockRelease(&bgs->ckpt_lck);
!
! ckpt_active = false;
!
! /*
! * Note we record the checkpoint start time not end time as
! * last_checkpoint_time. This is so that time-driven checkpoints
! * happen at a predictable spacing.
! */
! last_checkpoint_time = now;
! }
! else
! BgBufferSync();
! /* Check for archive_timeout and switch xlog files if necessary. */
! CheckArchiveTimeout();
! /* Nap for the configured time. */
! BgWriterNap();
}
}
}
***************
*** 586,592 **** BgWriterNap(void)
(ckpt_active ? ImmediateCheckpointRequested() : checkpoint_requested))
break;
pg_usleep(1000000L);
! AbsorbFsyncRequests();
udelay -= 1000000L;
}
--- 682,689 ----
(ckpt_active ? ImmediateCheckpointRequested() : checkpoint_requested))
break;
pg_usleep(1000000L);
! if (!IsRecoveryProcessingMode())
! AbsorbFsyncRequests();
udelay -= 1000000L;
}
***************
*** 640,645 **** CheckpointWriteDelay(int flags, double progress)
--- 737,755 ----
if (!am_bg_writer)
return;
+ /* Perform minimal duties during recovery and skip wait if requested */+ if (IsRecoveryProcessingMode())+ {+ BgBufferSync();+ + if (!shutdown_requested &&+ !checkpoint_requested &&+ IsCheckpointOnSchedule(progress))+ BgWriterNap();+ + return;+ }+
/*
* Perform the usual bgwriter duties and take a nap, unless we're behind
* schedule, in which case we just try to catch up as quickly as possible.
***************
*** 714,729 **** IsCheckpointOnSchedule(double progress)
* However, it's good enough for our purposes, we're only calculating an
* estimate anyway.
*/
! recptr = GetInsertRecPtr();
! elapsed_xlogs =
! (((double) (int32) (recptr.xlogid - ckpt_start_recptr.xlogid)) * XLogSegsPerFile +
! ((double) recptr.xrecoff - (double) ckpt_start_recptr.xrecoff) / XLogSegSize) /
! CheckPointSegments;
!
! if (progress < elapsed_xlogs)
{
! ckpt_cached_elapsed = elapsed_xlogs;
! return false;
}
/*
--- 824,842 ----
* However, it's good enough for our purposes, we're only calculating an
* estimate anyway.
*/
! if (!IsRecoveryProcessingMode())
{
! recptr = GetInsertRecPtr();
! elapsed_xlogs =
! (((double) (int32) (recptr.xlogid - ckpt_start_recptr.xlogid)) * XLogSegsPerFile +
! ((double) recptr.xrecoff - (double) ckpt_start_recptr.xrecoff) / XLogSegSize) /
! CheckPointSegments;
!
! if (progress < elapsed_xlogs)
! {
! ckpt_cached_elapsed = elapsed_xlogs;
! return false;
! }
}
/*
***************
*** 989,994 **** RequestCheckpoint(int flags)
--- 1102,1180 ----
}
/*
+ * Always runs in Startup process (see xlog.c)+ */+ void+ RequestRestartPoint(const XLogRecPtr ReadPtr, const CheckPoint *restartPoint, bool sendToBGWriter)+ {+ /*+ * Should we just do it ourselves?+ */+ if (!IsPostmasterEnvironment || !sendToBGWriter)+ {+ CreateRestartPoint(ReadPtr, restartPoint, CHECKPOINT_IMMEDIATE);+ return;+ }+ + /*+ * Push requested values into shared memory, then signal to request restartpoint.+ */+ if (BgWriterShmem->bgwriter_pid == 0)+ elog(LOG, "could not request restartpoint because bgwriter not running");+ + SpinLockAcquire(&BgWriterShmem->ckpt_lck);+ BgWriterShmem->ReadPtr = ReadPtr;+ memcpy(&BgWriterShmem->restartPoint, restartPoint, sizeof(CheckPoint));+ SpinLockRelease(&BgWriterShmem->ckpt_lck);+ + if (kill(BgWriterShmem->bgwriter_pid, SIGINT) != 0)+ elog(LOG, "could not signal for restartpoint: %m"); + }+ + /* + * Sends another checkpoint request signal to bgwriter, which causes it+ * to avoid smoothed writes and continue processing as if it had been+ * called with CHECKPOINT_IMMEDIATE. This is used at the end of recovery.+ */+ void+ RequestRestartPointCompletion(void)+ {+ if (BgWriterShmem->bgwriter_pid != 0 &&+ kill(BgWriterShmem->bgwriter_pid, SIGINT) != 0)+ elog(LOG, "could not signal for restartpoint immediate: %m");+ }+ + XLogRecPtr+ GetRedoLocationForArchiveCheckpoint(void)+ {+ XLogRecPtr redo;+ + SpinLockAcquire(&BgWriterShmem->ckpt_lck);+ redo = BgWriterShmem->ReadPtr;+ SpinLockRelease(&BgWriterShmem->ckpt_lck);+ + return redo;+ }+ + /* + * Store the information needed for a checkpoint at the end of recovery.+ * Returns true if bgwriter can perform checkpoint, or false if bgwriter+ * not active or otherwise unable to comply.+ */+ bool+ SetRedoLocationForArchiveCheckpoint(XLogRecPtr redo)+ {+ SpinLockAcquire(&BgWriterShmem->ckpt_lck);+ BgWriterShmem->ReadPtr = redo;+ SpinLockRelease(&BgWriterShmem->ckpt_lck);+ + if (BgWriterShmem->bgwriter_pid == 0 || !IsPostmasterEnvironment)+ return false;+ + return true;+ }+ + /*
* ForwardFsyncRequest
* Forward a file-fsync request from a backend to the bgwriter
*
*** src/backend/postmaster/postmaster.c
--- src/backend/postmaster/postmaster.c
***************
*** 230,237 **** static bool FatalError = false; /* T if recovering from backend crash */
* We use a simple state machine to control startup, shutdown, and
* crash recovery (which is rather like shutdown followed by startup).
*
! * Normal child backends can only be launched when we are in PM_RUN state.
! * (We also allow it in PM_WAIT_BACKUP state, but only for superusers.)
* In other states we handle connection requests by launching "dead_end"
* child processes, which will simply send the client an error message and
* quit. (We track these in the BackendList so that we can know when they
--- 230,239 ----
* We use a simple state machine to control startup, shutdown, and
* crash recovery (which is rather like shutdown followed by startup).
*
! * Normal child backends can only be launched when we are in PM_RUN or
! * PM_RECOVERY state. Any transaction started in PM_RECOVERY state will
! * be read-only for the whole of its life. (We also allow launch of normal
! * child backends in PM_WAIT_BACKUP state, but only for superusers.)
* In other states we handle connection requests by launching "dead_end"
* child processes, which will simply send the client an error message and
* quit. (We track these in the BackendList so that we can know when they
***************
*** 254,259 **** typedef enum
--- 256,266 ----
{
PM_INIT, /* postmaster starting */
PM_STARTUP, /* waiting for startup subprocess */
+ PM_RECOVERY, /* consistent recovery mode; state only+ * entered for archive and streaming recovery,+ * and only after the point where the + * all data is in consistent state.+ */
PM_RUN, /* normal "database is alive" state */
PM_WAIT_BACKUP, /* waiting for online backup mode to end */
PM_WAIT_BACKENDS, /* waiting for live backends to exit */
***************
*** 1302,1308 **** ServerLoop(void)
* state that prevents it, start one. It doesn't matter if this
* fails, we'll just try again later.
*/
! if (BgWriterPID == 0 && pmState == PM_RUN)
BgWriterPID = StartBackgroundWriter();
/*
--- 1309,1315 ----
* state that prevents it, start one. It doesn't matter if this
* fails, we'll just try again later.
*/
! if (BgWriterPID == 0 && (pmState == PM_RUN || pmState == PM_RECOVERY))
BgWriterPID = StartBackgroundWriter();
/*
***************
*** 1651,1661 **** retry1:
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
errmsg("the database system is shutting down")));
break;
- case CAC_RECOVERY:- ereport(FATAL,- (errcode(ERRCODE_CANNOT_CONNECT_NOW),- errmsg("the database system is in recovery mode")));- break;
case CAC_TOOMANY:
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
--- 1658,1663 ----
***************
*** 1664,1669 **** retry1:
--- 1666,1672 ----
case CAC_WAITBACKUP:
/* OK for now, will check in InitPostgres */
break;
+ case CAC_RECOVERY:
case CAC_OK:
break;
}
***************
*** 1982,1991 **** pmdie(SIGNAL_ARGS)
ereport(LOG,
(errmsg("received smart shutdown request")));
! if (pmState == PM_RUN)
{
/* autovacuum workers are told to shut down immediately */
! SignalAutovacWorkers(SIGTERM);
/* and the autovac launcher too */
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGTERM);
--- 1985,1995 ----
ereport(LOG,
(errmsg("received smart shutdown request")));
! if (pmState == PM_RUN || pmState == PM_RECOVERY)
{
/* autovacuum workers are told to shut down immediately */
! if (pmState == PM_RUN)
! SignalAutovacWorkers(SIGTERM);
/* and the autovac launcher too */
if (AutoVacPID != 0)
signal_child(AutoVacPID, SIGTERM);
***************
*** 2019,2025 **** pmdie(SIGNAL_ARGS)
if (StartupPID != 0)
signal_child(StartupPID, SIGTERM);
! if (pmState == PM_RUN || pmState == PM_WAIT_BACKUP)
{
ereport(LOG,
(errmsg("aborting any active transactions")));
--- 2023,2029 ----
if (StartupPID != 0)
signal_child(StartupPID, SIGTERM);
! if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_WAIT_BACKUP)
{
ereport(LOG,
(errmsg("aborting any active transactions")));
***************
*** 2115,2122 **** reaper(SIGNAL_ARGS)
*/
if (pid == StartupPID)
{
StartupPID = 0;
! Assert(pmState == PM_STARTUP);
/* FATAL exit of startup is treated as catastrophic */
if (!EXIT_STATUS_0(exitstatus))
--- 2119,2129 ----
*/
if (pid == StartupPID)
{
+ bool leavingRecovery = (pmState == PM_RECOVERY);+
StartupPID = 0;
! Assert(pmState == PM_STARTUP || pmState == PM_RECOVERY ||
! pmState == PM_WAIT_BACKUP || pmState == PM_WAIT_BACKENDS);
/* FATAL exit of startup is treated as catastrophic */
if (!EXIT_STATUS_0(exitstatus))
***************
*** 2124,2130 **** reaper(SIGNAL_ARGS)
LogChildExit(LOG, _("startup process"),
pid, exitstatus);
ereport(LOG,
! (errmsg("aborting startup due to startup process failure")));
ExitPostmaster(1);
}
--- 2131,2137 ----
LogChildExit(LOG, _("startup process"),
pid, exitstatus);
ereport(LOG,
! (errmsg("aborting startup due to startup process failure")));
ExitPostmaster(1);
}
***************
*** 2157,2166 **** reaper(SIGNAL_ARGS)
load_role();
/*
! * Crank up the background writer. It doesn't matter if this
! * fails, we'll just try again later.
*/
! Assert(BgWriterPID == 0);
BgWriterPID = StartBackgroundWriter();
/*
--- 2164,2173 ----
load_role();
/*
! * Check whether we need to start background writer, if not
! * already running.
*/
! if (BgWriterPID == 0)
BgWriterPID = StartBackgroundWriter();
/*
***************
*** 2177,2184 **** reaper(SIGNAL_ARGS)
PgStatPID = pgstat_start();
/* at this point we are really open for business */
! ereport(LOG,
! (errmsg("database system is ready to accept connections")));
continue;
}
--- 2184,2195 ----
PgStatPID = pgstat_start();
/* at this point we are really open for business */
! if (leavingRecovery)
! ereport(LOG,
! (errmsg("database can now be accessed with read and write transactions")));
! else
! ereport(LOG,
! (errmsg("database system is ready to accept connections")));
continue;
}
***************
*** 2898,2904 **** BackendStartup(Port *port)
bn->pid = pid;
bn->cancel_key = MyCancelKey;
bn->is_autovacuum = false;
! bn->dead_end = (port->canAcceptConnections != CAC_OK &&
port->canAcceptConnections != CAC_WAITBACKUP);
DLAddHead(BackendList, DLNewElem(bn));
#ifdef EXEC_BACKEND
--- 2909,2916 ----
bn->pid = pid;
bn->cancel_key = MyCancelKey;
bn->is_autovacuum = false;
! bn->dead_end = (!(port->canAcceptConnections == CAC_RECOVERY ||
! port->canAcceptConnections == CAC_OK) &&
port->canAcceptConnections != CAC_WAITBACKUP);
DLAddHead(BackendList, DLNewElem(bn));
#ifdef EXEC_BACKEND
***************
*** 3845,3850 **** sigusr1_handler(SIGNAL_ARGS)
--- 3857,3910 ----
PG_SETMASK(&BlockSig);
+ if (CheckPostmasterSignal(PMSIGNAL_RECOVERY_START))+ {+ Assert(pmState == PM_STARTUP);+ + /*+ * Go to shutdown mode if a shutdown request was pending.+ */+ if (Shutdown > NoShutdown)+ {+ pmState = PM_WAIT_BACKENDS;+ /* PostmasterStateMachine logic does the rest */+ }+ else+ {+ /*+ * Startup process has entered recovery+ */+ pmState = PM_RECOVERY;+ + /*+ * Load the flat authorization file into postmaster's cache. The+ * startup process won't have recomputed this from the database+ * yet, so it may change following recovery. We'll reload it+ * after the startup process ends.+ */+ load_role();+ + /*+ * Crank up the background writer. It doesn't matter if this+ * fails, we'll just try again later.+ */+ Assert(BgWriterPID == 0);+ BgWriterPID = StartBackgroundWriter();+ + /*+ * Likewise, start other special children as needed.+ */+ Assert(PgStatPID == 0);+ PgStatPID = pgstat_start();+ + /* We can now accept read-only connections */+ ereport(LOG,+ (errmsg("database system is ready to accept connections")));+ ereport(LOG,+ (errmsg("database can now be accessed with read only transactions")));+ }+ }+
if (CheckPostmasterSignal(PMSIGNAL_PASSWORD_CHANGE))
{
/*
*** src/backend/utils/init/flatfiles.c
--- src/backend/utils/init/flatfiles.c
***************
*** 678,686 **** write_auth_file(Relation rel_authid, Relation rel_authmem)
/*
* This routine is called once during database startup, after completing
* WAL replay if needed. Its purpose is to sync the flat files with the
! * current state of the database tables. This is particularly important
! * during PITR operation, since the flat files will come from the
! * base backup which may be far out of sync with the current state.
*
* In theory we could skip rebuilding the flat files if no WAL replay
* occurred, but it seems best to just do it always. We have to
--- 678,687 ----
/*
* This routine is called once during database startup, after completing
* WAL replay if needed. Its purpose is to sync the flat files with the
! * current state of the database tables.
! *
! * In 8.4 we also run this during xact_redo_commit() if the transaction
! * wrote a new database or auth flat file.
*
* In theory we could skip rebuilding the flat files if no WAL replay
* occurred, but it seems best to just do it always. We have to
***************
*** 716,723 **** BuildFlatFiles(bool database_only)
/*
* We don't have any hope of running a real relcache, but we can use the
* same fake-relcache facility that WAL replay uses.
- *- * No locking is needed because no one else is alive yet.
*/
rel_db = CreateFakeRelcacheEntry(rnode);
write_database_file(rel_db, true);
--- 717,722 ----
***************
*** 832,845 **** AtEOXact_UpdateFlatFiles(bool isCommit)
/* Okay to write the files */
if (database_file_update_subid != InvalidSubTransactionId)
{
! database_file_update_subid = InvalidSubTransactionId;
write_database_file(drel, false);
heap_close(drel, NoLock);
}
if (auth_file_update_subid != InvalidSubTransactionId)
{
! auth_file_update_subid = InvalidSubTransactionId;
write_auth_file(arel, mrel);
heap_close(arel, NoLock);
heap_close(mrel, NoLock);
--- 831,844 ----
/* Okay to write the files */
if (database_file_update_subid != InvalidSubTransactionId)
{
! /* reset database_file_update_subid later during commit */
write_database_file(drel, false);
heap_close(drel, NoLock);
}
if (auth_file_update_subid != InvalidSubTransactionId)
{
! /* reset auth_file_update_subid later during commit */
write_auth_file(arel, mrel);
heap_close(arel, NoLock);
heap_close(mrel, NoLock);
*** src/include/access/xact.h
--- src/include/access/xact.h
***************
*** 17,22 ****
--- 17,23 ----
#include "access/xlog.h"
#include "nodes/pg_list.h"
#include "storage/relfilenode.h"
+ #include "utils/snapshot.h"
#include "utils/timestamp.h"
***************
*** 84,111 **** typedef void (*SubXactCallback) (SubXactEvent event, SubTransactionId mySubid,
#define XLOG_XACT_ABORT 0x20
#define XLOG_XACT_COMMIT_PREPARED 0x30
#define XLOG_XACT_ABORT_PREPARED 0x40
typedef struct xl_xact_commit
{
! TimestampTz xact_time; /* time of commit */
! int nrels; /* number of RelFileNodes */
! int nsubxacts; /* number of subtransaction XIDs */
! /* Array of RelFileNode(s) to drop at commit */
! RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
! /* ARRAY OF COMMITTED SUBTRANSACTION XIDs FOLLOWS */
} xl_xact_commit;
#define MinSizeOfXactCommit offsetof(xl_xact_commit, xnodes)
typedef struct xl_xact_abort
{
TimestampTz xact_time; /* time of abort */
int nrels; /* number of RelFileNodes */
int nsubxacts; /* number of subtransaction XIDs */
/* Array of RelFileNode(s) to drop at abort */
RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
/* ARRAY OF ABORTED SUBTRANSACTION XIDs FOLLOWS */
} xl_xact_abort;
#define MinSizeOfXactAbort offsetof(xl_xact_abort, xnodes)
--- 85,162 ----
#define XLOG_XACT_ABORT 0x20
#define XLOG_XACT_COMMIT_PREPARED 0x30
#define XLOG_XACT_ABORT_PREPARED 0x40
+ #define XLOG_XACT_ASSIGNMENT 0x50+ #define XLOG_XACT_RUNNING_XACTS 0x60+ /* 0x70 can also be used, if required */+ + typedef struct xl_xact_assignment+ {+ TransactionId xassign; /* assigned xid */+ TransactionId xparent; /* assigned xids parent, if any */+ bool isSubXact; /* is a subtransaction */+ int slotId; /* slotId in procarray */+ } xl_xact_assignment;+ + /* + * xl_xact_running_xacts is in utils/snapshot.h so it can be passed+ * around to the same places as snapshots. Not snapmgr.h+ */
typedef struct xl_xact_commit
{
! TimestampTz xact_time; /* time of commit */
! int slotId; /* slotId in procarray */
! uint xinfo; /* info flags */
! int nrels; /* number of RelFileForks */
! int nsubxacts; /* number of subtransaction XIDs */
! int nmsgs; /* number of shared inval msgs */
! /* Array of RelFileFork(s) to drop at commit */
! RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
! /* ARRAY OF COMMITTED SUBTRANSACTION XIDs FOLLOWS */
! /* ARRAY OF SHARED INVALIDATION MESSAGES FOLLOWS */
} xl_xact_commit;
#define MinSizeOfXactCommit offsetof(xl_xact_commit, xnodes)
+ #define OffsetSharedInvalInXactCommit() \+ ( \+ MinSizeOfXactCommit + \+ (xlrec->nsubxacts * sizeof(TransactionId)) + \+ (xlrec->nrels * sizeof(RelFileNode)) \+ )+ + /*+ * These flags are set in the xinfo fields of transaction+ * completion WAL records. They indicate a number of actions+ * that need to occur when emulating transaction completion.+ * They are named XactCompletion... to differentiate them from+ * EOXact... routines which run at the end of the original+ * transaction completion.+ */+ #define XACT_COMPLETION_UNMARKED_SUBXIDS 0x01+ + /* These next states only occur on commit record types */+ #define XACT_COMPLETION_UPDATE_DB_FILE 0x02+ #define XACT_COMPLETION_UPDATE_AUTH_FILE 0x04+ #define XACT_COMPLETION_UPDATE_RELCACHE_FILE 0x08+ + /* Access macros for above flags */+ #define XactCompletionHasUnMarkedSubxids(xlrec) ((xlrec)->xinfo & XACT_COMPLETION_UNMARKED_SUBXIDS)+ #define XactCompletionUpdateDBFile(xlrec) ((xlrec)->xinfo & XACT_COMPLETION_UPDATE_DB_FILE)+ #define XactCompletionUpdateAuthFile(xlrec) ((xlrec)->xinfo & XACT_COMPLETION_UPDATE_AUTH_FILE)+ #define XactCompletionRelcacheInitFileInval(xlrec) ((xlrec)->xinfo & XACT_COMPLETION_UPDATE_RELCACHE_FILE)
typedef struct xl_xact_abort
{
TimestampTz xact_time; /* time of abort */
+ int slotId; /* slotId in procarray */+ uint xinfo; /* info flags */
int nrels; /* number of RelFileNodes */
int nsubxacts; /* number of subtransaction XIDs */
/* Array of RelFileNode(s) to drop at abort */
RelFileNode xnodes[1]; /* VARIABLE LENGTH ARRAY */
/* ARRAY OF ABORTED SUBTRANSACTION XIDs FOLLOWS */
} xl_xact_abort;
+ /* Note the intentional lack of an invalidation message array c.f. commit */
#define MinSizeOfXactAbort offsetof(xl_xact_abort, xnodes)
***************
*** 185,190 **** extern TransactionId RecordTransactionCommit(void);
--- 236,252 ----
extern int xactGetCommittedChildren(TransactionId **ptr);
+ extern void LogCurrentRunningXacts(void);+ extern bool IsRunningXactDataValid(void);+ extern void GetStandbyInfoForTransaction(RmgrId rmid, uint8 info,+ XLogRecData *rdata,+ TransactionId *xid2, + uint16 *info2);+ + extern void InitRecoveryTransactionEnvironment(void);+ extern void XactResolveRecoveryConflicts(TransactionId latestRemovedXid, Oid recDatabaseOid);+ extern void RecordKnownAssignedTransactionIds(XLogRecPtr lsn, XLogRecord *record);+
extern void xact_redo(XLogRecPtr lsn, XLogRecord *record);
extern void xact_desc(StringInfo buf, uint8 xl_info, char *rec);
*** src/include/access/xlog.h
--- src/include/access/xlog.h
***************
*** 133,139 **** typedef struct XLogRecData
} XLogRecData;
extern TimeLineID ThisTimeLineID; /* current TLI */
! extern bool InRecovery;
extern XLogRecPtr XactLastRecEnd;
/* these variables are GUC parameters related to XLOG */
--- 133,147 ----
} XLogRecData;
extern TimeLineID ThisTimeLineID; /* current TLI */
! /*
! * Prior to 8.4, all activity during recovery were carried out by Startup
! * process. This local variable continues to be used in many parts of the
! * code to indicate actions taken by RecoveryManagers. Other processes who
! * potentially perform work during recovery should check
! * IsRecoveryProcessingMode(), see XLogCtl notes in xlog.c
! */
! extern bool InRecovery;
! extern bool InArchiveRecovery;
extern XLogRecPtr XactLastRecEnd;
/* these variables are GUC parameters related to XLOG */
***************
*** 166,171 **** extern bool XLOG_DEBUG;
--- 174,180 ----
/* These indicate the cause of a checkpoint request */
#define CHECKPOINT_CAUSE_XLOG 0x0010 /* XLOG consumption */
#define CHECKPOINT_CAUSE_TIME 0x0020 /* Elapsed time */
+ #define CHECKPOINT_RESTARTPOINT 0x0040 /* Restartpoint during recovery */
/* Checkpoint statistics */
typedef struct CheckpointStatsData
***************
*** 197,202 **** extern void XLogSetAsyncCommitLSN(XLogRecPtr record);
--- 206,214 ----
extern void xlog_redo(XLogRecPtr lsn, XLogRecord *record);
extern void xlog_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern bool IsRecoveryProcessingMode(void);+ +
extern void UpdateControlFile(void);
extern Size XLOGShmemSize(void);
extern void XLOGShmemInit(void);
*** src/include/access/xlog_internal.h
--- src/include/access/xlog_internal.h
***************
*** 17,22 ****
--- 17,23 ----
#define XLOG_INTERNAL_H
#include "access/xlog.h"
+ #include "catalog/pg_control.h"
#include "fmgr.h"
#include "pgtime.h"
#include "storage/block.h"
***************
*** 245,250 **** extern const RmgrData RmgrTable[];
--- 246,254 ----
extern pg_time_t GetLastSegSwitchTime(void);
extern XLogRecPtr RequestXLogSwitch(void);
+ extern void CreateRestartPoint(const XLogRecPtr ReadPtr, + const CheckPoint *restartPoint, int flags);+
/*
* These aren't in xlog.h because I'd rather not include fmgr.h there.
*/
*** src/include/catalog/pg_control.h
--- src/include/catalog/pg_control.h
***************
*** 46,51 **** typedef struct CheckPoint
--- 46,52 ----
#define XLOG_NOOP 0x20
#define XLOG_NEXTOID 0x30
#define XLOG_SWITCH 0x40
+ #define XLOG_RECOVERY_END 0x50
/* System status indicator */
***************
*** 102,107 **** typedef struct ControlFileData
--- 103,109 ----
CheckPoint checkPointCopy; /* copy of last check point record */
XLogRecPtr minRecoveryPoint; /* must replay xlog to here */
+ XLogRecPtr minSafeStartPoint; /* safe point after recovery crashes */
/*
* This data is used to check for hardware-architecture compatibility of
*** src/include/postmaster/bgwriter.h
--- src/include/postmaster/bgwriter.h
***************
*** 12,17 ****
--- 12,18 ----
#ifndef _BGWRITER_H
#define _BGWRITER_H
+ #include "catalog/pg_control.h"
#include "storage/block.h"
#include "storage/relfilenode.h"
***************
*** 25,30 **** extern double CheckPointCompletionTarget;
--- 26,36 ----
extern void BackgroundWriterMain(void);
extern void RequestCheckpoint(int flags);
+ extern void RequestRestartPoint(const XLogRecPtr ReadPtr, const CheckPoint *restartPoint, bool sendToBGWriter);+ extern void RequestRestartPointCompletion(void);+ extern XLogRecPtr GetRedoLocationForArchiveCheckpoint(void);+ extern bool SetRedoLocationForArchiveCheckpoint(XLogRecPtr redo);+
extern void CheckpointWriteDelay(int flags, double progress);
extern bool ForwardFsyncRequest(RelFileNode rnode, ForkNumber forknum,
*** src/include/storage/pmsignal.h
--- src/include/storage/pmsignal.h
***************
*** 22,27 ****
--- 22,28 ----
*/
typedef enum
{
+ PMSIGNAL_RECOVERY_START, /* move to PM_RECOVERY state */
PMSIGNAL_PASSWORD_CHANGE, /* pg_auth file has changed */
PMSIGNAL_WAKEN_ARCHIVER, /* send a NOTIFY signal to xlog archiver */
PMSIGNAL_ROTATE_LOGFILE, /* send SIGUSR1 to syslogger to rotate logfile */
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-patches@postgresql.org
Cc: heikki.linnakangas@enterprisedb.com, simon@2ndQuadrant.com, alvherre@commandprompt.com, pavan.deolasee@gmail.com, pgsql-hackers@postgresql.org
Subject: Re: [PATCHES] Infrastructure changes for recovery (v8)
In-Reply-To: <494FF631.90908@enterprisedb.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox