From 06f82b29616cd9effcaefd99c6b6e2e80697482f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 4 Jan 2010 12:50:50 +0000
Subject: [PATCH] Write an end-of-backup WAL record at pg_stop_backup(), and
 wait for it at recovery instead of reading the backup history file. This is
 more robust, as it stops you from prematurely starting up an inconsisten
 cluster if the backup history file is lost for some reason, or if the base
 backup was never finished with pg_stop_backup().

This also paves the way for a simpler streaming replication patch, which
doesn't need to care about backup history files anymore.

The backup history file is still created and archived as before, but it's
not used by the system anymore. It's just for informational purposes now.

Bump PG_CONTROL_VERSION as the location of the backup startpoint is now
written to a new field in pg_control, and catversion because initdb is
required

Original patch by Fujii Masao per Simon's idea, with further fixes by me.
---
 src/backend/access/transam/xlog.c       | 188 ++++++++++++------------
 src/bin/pg_controldata/pg_controldata.c |   7 +-
 src/bin/pg_resetxlog/pg_resetxlog.c     |   4 +-
 src/include/catalog/catversion.h        |   4 +-
 src/include/catalog/pg_control.h        |  27 +++-
 5 files changed, 128 insertions(+), 102 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c045807dc35..9a13a98cbc1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.356 2010/01/02 16:57:35 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.357 2010/01/04 12:50:49 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -515,8 +515,7 @@ static void xlog_outrec(StringInfo buf, XLogRecord *record);
 #endif
 static void issue_xlog_fsync(void);
 static void pg_start_backup_callback(int code, Datum arg);
-static bool read_backup_label(XLogRecPtr *checkPointLoc,
-				  XLogRecPtr *minRecoveryLoc);
+static bool read_backup_label(XLogRecPtr *checkPointLoc);
 static void rm_redo_error_callback(void *arg);
 static int	get_sync_bit(int method);
 
@@ -5355,7 +5354,6 @@ StartupXLOG(void)
 	bool		haveBackupLabel = false;
 	XLogRecPtr	RecPtr,
 				checkPointLoc,
-				backupStopLoc,
 				EndOfLog;
 	uint32		endLogId;
 	uint32		endLogSeg;
@@ -5454,7 +5452,7 @@ StartupXLOG(void)
 						recoveryTargetTLI,
 						ControlFile->checkPointCopy.ThisTimeLineID)));
 
-	if (read_backup_label(&checkPointLoc, &backupStopLoc))
+	if (read_backup_label(&checkPointLoc))
 	{
 		/*
 		 * When a backup_label file is present, we want to roll forward from
@@ -5597,11 +5595,23 @@ StartupXLOG(void)
 		ControlFile->prevCheckPoint = ControlFile->checkPoint;
 		ControlFile->checkPoint = checkPointLoc;
 		ControlFile->checkPointCopy = checkPoint;
-		if (backupStopLoc.xlogid != 0 || backupStopLoc.xrecoff != 0)
+		if (InArchiveRecovery)
+		{
+			/* initialize minRecoveryPoint if not set yet */
+			if (XLByteLT(ControlFile->minRecoveryPoint, checkPoint.redo))
+				ControlFile->minRecoveryPoint = checkPoint.redo;
+		}
+		else
 		{
-			if (XLByteLT(ControlFile->minRecoveryPoint, backupStopLoc))
-				ControlFile->minRecoveryPoint = backupStopLoc;
+			XLogRecPtr	InvalidXLogRecPtr = {0, 0};
+			ControlFile->minRecoveryPoint = InvalidXLogRecPtr;
 		}
+		/*
+		 * set backupStartupPoint if we're starting archive recovery from a
+		 * base backup
+		 */
+		if (haveBackupLabel)
+			ControlFile->backupStartPoint = checkPoint.redo;
 		ControlFile->time = (pg_time_t) time(NULL);
 		/* No need to hold ControlFileLock yet, we aren't up far enough */
 		UpdateControlFile();
@@ -5703,15 +5713,9 @@ StartupXLOG(void)
 
 			InRedo = true;
 
-			if (minRecoveryPoint.xlogid == 0 && minRecoveryPoint.xrecoff == 0)
-				ereport(LOG,
-						(errmsg("redo starts at %X/%X",
-								ReadRecPtr.xlogid, ReadRecPtr.xrecoff)));
-			else
-				ereport(LOG,
-						(errmsg("redo starts at %X/%X, consistency will be reached at %X/%X",
-								ReadRecPtr.xlogid, ReadRecPtr.xrecoff,
-						minRecoveryPoint.xlogid, minRecoveryPoint.xrecoff)));
+			ereport(LOG,
+					(errmsg("redo starts at %X/%X",
+							ReadRecPtr.xlogid, ReadRecPtr.xrecoff)));
 
 			/*
 			 * Let postmaster know we've started redo now, so that it can
@@ -5771,7 +5775,8 @@ StartupXLOG(void)
 				 * Have we passed our safe starting point?
 				 */
 				if (!reachedMinRecoveryPoint &&
-					XLByteLE(minRecoveryPoint, EndRecPtr))
+					XLByteLE(minRecoveryPoint, EndRecPtr) &&
+					XLogRecPtrIsInvalid(ControlFile->backupStartPoint))
 				{
 					reachedMinRecoveryPoint = true;
 					ereport(LOG,
@@ -5877,7 +5882,9 @@ StartupXLOG(void)
 	 * be further ahead --- ControlFile->minRecoveryPoint cannot have been
 	 * advanced beyond the WAL we processed.
 	 */
-	if (InRecovery && XLByteLT(EndOfLog, minRecoveryPoint))
+	if (InArchiveRecovery &&
+		(XLByteLT(EndOfLog, minRecoveryPoint) ||
+		 !XLogRecPtrIsInvalid(ControlFile->backupStartPoint)))
 	{
 		if (reachedStopPoint)	/* stopped because of stop request */
 			ereport(FATAL,
@@ -7312,6 +7319,32 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
 	{
 		/* nothing to do here */
 	}
+	else if (info == XLOG_BACKUP_END)
+	{
+		XLogRecPtr	startpoint;
+		memcpy(&startpoint, XLogRecGetData(record), sizeof(startpoint));
+
+		if (XLByteEQ(ControlFile->backupStartPoint, startpoint))
+		{
+			/*
+			 * We have reached the end of base backup, the point where
+			 * pg_stop_backup() was done. The data on disk is now consistent.
+			 * Reset backupStartPoint, and update minRecoveryPoint to make
+			 * sure we don't allow starting up at an earlier point even if
+			 * recovery is stopped and restarted soon after this.
+			 */
+			elog(DEBUG1, "end of backup reached");
+
+			LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
+
+			if (XLByteLT(ControlFile->minRecoveryPoint, lsn))
+				ControlFile->minRecoveryPoint = lsn;
+			MemSet(&ControlFile->backupStartPoint, 0, sizeof(XLogRecPtr));
+			UpdateControlFile();
+
+			LWLockRelease(ControlFileLock);
+		}
+	}
 }
 
 void
@@ -7353,6 +7386,14 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
 	{
 		appendStringInfo(buf, "xlog switch");
 	}
+	else if (info == XLOG_BACKUP_END)
+	{
+		XLogRecPtr startpoint;
+
+		memcpy(&startpoint, rec, sizeof(XLogRecPtr));
+		appendStringInfo(buf, "backup end: %X/%X",
+						 startpoint.xlogid, startpoint.xrecoff);
+	}
 	else
 		appendStringInfo(buf, "UNKNOWN");
 }
@@ -7688,10 +7729,14 @@ pg_start_backup_callback(int code, Datum arg)
 /*
  * pg_stop_backup: finish taking an on-line backup dump
  *
- * We remove the backup label file created by pg_start_backup, and instead
- * create a backup history file in pg_xlog (whence it will immediately be
- * archived).  The backup history file contains the same info found in
- * the label file, plus the backup-end time and WAL location.
+ * We write an end-of-backup WAL record, and remove the backup label file
+ * created by pg_start_backup, creating a backup history file in pg_xlog
+ * instead (whence it will immediately be archived). The backup history file
+ * contains the same info found in the label file, plus the backup-end time
+ * and WAL location. Before 8.5, the backup-end time was read from the backup
+ * history file at the beginning of archive recovery, but we now use the WAL
+ * record for that and the file is for informational and debug purposes only.
+ *
  * Note: different from CancelBackup which just cancels online backup mode.
  */
 Datum
@@ -7699,6 +7744,7 @@ pg_stop_backup(PG_FUNCTION_ARGS)
 {
 	XLogRecPtr	startpoint;
 	XLogRecPtr	stoppoint;
+	XLogRecData	rdata;
 	pg_time_t	stamp_time;
 	char		strfbuf[128];
 	char		histfilepath[MAXPGPATH];
@@ -7739,22 +7785,6 @@ pg_stop_backup(PG_FUNCTION_ARGS)
 	XLogCtl->Insert.forcePageWrites = false;
 	LWLockRelease(WALInsertLock);
 
-	/*
-	 * Force a switch to a new xlog segment file, so that the backup is valid
-	 * as soon as archiver moves out the current segment file. We'll report
-	 * the end address of the XLOG SWITCH record as the backup stopping point.
-	 */
-	stoppoint = RequestXLogSwitch();
-
-	XLByteToSeg(stoppoint, _logId, _logSeg);
-	XLogFileName(stopxlogfilename, ThisTimeLineID, _logId, _logSeg);
-
-	/* Use the log timezone here, not the session timezone */
-	stamp_time = (pg_time_t) time(NULL);
-	pg_strftime(strfbuf, sizeof(strfbuf),
-				"%Y-%m-%d %H:%M:%S %Z",
-				pg_localtime(&stamp_time, log_timezone));
-
 	/*
 	 * Open the existing label file
 	 */
@@ -7782,6 +7812,30 @@ pg_stop_backup(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
 
+	/*
+	 * Write the backup-end xlog record
+	 */
+	rdata.data = (char *) (&startpoint);
+	rdata.len = sizeof(startpoint);
+	rdata.buffer = InvalidBuffer;
+	rdata.next = NULL;
+	stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END, &rdata);
+
+	/*
+	 * Force a switch to a new xlog segment file, so that the backup is valid
+	 * as soon as archiver moves out the current segment file.
+	 */
+	RequestXLogSwitch();
+
+	XLByteToSeg(stoppoint, _logId, _logSeg);
+	XLogFileName(stopxlogfilename, ThisTimeLineID, _logId, _logSeg);
+
+	/* Use the log timezone here, not the session timezone */
+	stamp_time = (pg_time_t) time(NULL);
+	pg_strftime(strfbuf, sizeof(strfbuf),
+				"%Y-%m-%d %H:%M:%S %Z",
+				pg_localtime(&stamp_time, log_timezone));
+
 	/*
 	 * Write the backup history file
 	 */
@@ -8088,33 +8142,18 @@ pg_xlogfile_name(PG_FUNCTION_ARGS)
  * later than the start of the dump, and so if we rely on it as the start
  * point, we will fail to restore a consistent database state.
  *
- * We also attempt to retrieve the corresponding backup history file.
- * If successful, set *minRecoveryLoc to constrain valid PITR stopping
- * points.
- *
  * Returns TRUE if a backup_label was found (and fills the checkpoint
  * location into *checkPointLoc); returns FALSE if not.
  */
 static bool
-read_backup_label(XLogRecPtr *checkPointLoc, XLogRecPtr *minRecoveryLoc)
+read_backup_label(XLogRecPtr *checkPointLoc)
 {
 	XLogRecPtr	startpoint;
-	XLogRecPtr	stoppoint;
-	char		histfilename[MAXFNAMELEN];
-	char		histfilepath[MAXPGPATH];
 	char		startxlogfilename[MAXFNAMELEN];
-	char		stopxlogfilename[MAXFNAMELEN];
 	TimeLineID	tli;
-	uint32		_logId;
-	uint32		_logSeg;
 	FILE	   *lfp;
-	FILE	   *fp;
 	char		ch;
 
-	/* Default is to not constrain recovery stop point */
-	minRecoveryLoc->xlogid = 0;
-	minRecoveryLoc->xrecoff = 0;
-
 	/*
 	 * See if label file is present
 	 */
@@ -8152,45 +8191,6 @@ read_backup_label(XLogRecPtr *checkPointLoc, XLogRecPtr *minRecoveryLoc)
 				 errmsg("could not read file \"%s\": %m",
 						BACKUP_LABEL_FILE)));
 
-	/*
-	 * Try to retrieve the backup history file (no error if we can't)
-	 */
-	XLByteToSeg(startpoint, _logId, _logSeg);
-	BackupHistoryFileName(histfilename, tli, _logId, _logSeg,
-						  startpoint.xrecoff % XLogSegSize);
-
-	if (InArchiveRecovery)
-		RestoreArchivedFile(histfilepath, histfilename, "RECOVERYHISTORY", 0);
-	else
-		BackupHistoryFilePath(histfilepath, tli, _logId, _logSeg,
-							  startpoint.xrecoff % XLogSegSize);
-
-	fp = AllocateFile(histfilepath, "r");
-	if (fp)
-	{
-		/*
-		 * Parse history file to identify stop point.
-		 */
-		if (fscanf(fp, "START WAL LOCATION: %X/%X (file %24s)%c",
-				   &startpoint.xlogid, &startpoint.xrecoff, startxlogfilename,
-				   &ch) != 4 || ch != '\n')
-			ereport(FATAL,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("invalid data in file \"%s\"", histfilename)));
-		if (fscanf(fp, "STOP WAL LOCATION: %X/%X (file %24s)%c",
-				   &stoppoint.xlogid, &stoppoint.xrecoff, stopxlogfilename,
-				   &ch) != 4 || ch != '\n')
-			ereport(FATAL,
-					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("invalid data in file \"%s\"", histfilename)));
-		*minRecoveryLoc = stoppoint;
-		if (ferror(fp) || FreeFile(fp))
-			ereport(FATAL,
-					(errcode_for_file_access(),
-					 errmsg("could not read file \"%s\": %m",
-							histfilepath)));
-	}
-
 	return true;
 }
 
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 9551a1d8786..2735a6611e2 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -6,7 +6,7 @@
  * copyright (c) Oliver Elphick <olly@lfix.co.uk>, 2001;
  * licence: BSD
  *
- * $PostgreSQL: pgsql/src/bin/pg_controldata/pg_controldata.c,v 1.45 2009/12/19 01:32:38 sriggs Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_controldata/pg_controldata.c,v 1.46 2010/01/04 12:50:49 heikki Exp $
  */
 #include "postgres_fe.h"
 
@@ -196,13 +196,16 @@ main(int argc, char *argv[])
 		   ControlFile.checkPointCopy.oldestXid);
 	printf(_("Latest checkpoint's oldestXID's DB:   %u\n"),
 		   ControlFile.checkPointCopy.oldestXidDB);
-	printf(_("Latest checkpoint's oldestActiveXID:   %u\n"),
+	printf(_("Latest checkpoint's oldestActiveXID:  %u\n"),
 		   ControlFile.checkPointCopy.oldestActiveXid);
 	printf(_("Time of latest checkpoint:            %s\n"),
 		   ckpttime_str);
 	printf(_("Minimum recovery ending location:     %X/%X\n"),
 		   ControlFile.minRecoveryPoint.xlogid,
 		   ControlFile.minRecoveryPoint.xrecoff);
+	printf(_("Backup start location:                %X/%X\n"),
+		   ControlFile.backupStartPoint.xlogid,
+		   ControlFile.backupStartPoint.xrecoff);
 	printf(_("Maximum data alignment:               %u\n"),
 		   ControlFile.maxAlign);
 	/* we don't print floatFormat since can't say much useful about it */
diff --git a/src/bin/pg_resetxlog/pg_resetxlog.c b/src/bin/pg_resetxlog/pg_resetxlog.c
index 8e0ba5073dd..07ea5fd13bc 100644
--- a/src/bin/pg_resetxlog/pg_resetxlog.c
+++ b/src/bin/pg_resetxlog/pg_resetxlog.c
@@ -23,7 +23,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_resetxlog/pg_resetxlog.c,v 1.76 2010/01/02 16:57:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_resetxlog/pg_resetxlog.c,v 1.77 2010/01/04 12:50:49 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -625,6 +625,8 @@ RewriteControlFile(void)
 	ControlFile.prevCheckPoint.xrecoff = 0;
 	ControlFile.minRecoveryPoint.xlogid = 0;
 	ControlFile.minRecoveryPoint.xrecoff = 0;
+	ControlFile.backupStartPoint.xlogid = 0;
+	ControlFile.backupStartPoint.xrecoff = 0;
 
 	/* Now we can force the recorded xlog seg size to the right thing. */
 	ControlFile.xlog_seg_size = XLogSegSize;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index aed93501a8a..49bb9198183 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.565 2010/01/02 16:58:01 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.566 2010/01/04 12:50:49 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201001011
+#define CATALOG_VERSION_NO	201001041
 
 #endif
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 454fc23a306..bac3e2a7ee9 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_control.h,v 1.47 2010/01/02 16:58:01 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_control.h,v 1.48 2010/01/04 12:50:50 heikki Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,7 +21,7 @@
 
 
 /* Version identifier for this pg_control format */
-#define PG_CONTROL_VERSION	852
+#define PG_CONTROL_VERSION	853
 
 /*
  * Body of CheckPoint XLOG records.  This is declared here because we keep
@@ -62,6 +62,7 @@ typedef struct CheckPoint
 #define XLOG_NOOP						0x20
 #define XLOG_NEXTOID					0x30
 #define XLOG_SWITCH						0x40
+#define XLOG_BACKUP_END					0x50
 
 
 /* System status indicator */
@@ -117,7 +118,27 @@ typedef struct ControlFileData
 
 	CheckPoint	checkPointCopy; /* copy of last check point record */
 
-	XLogRecPtr	minRecoveryPoint;		/* must replay xlog to here */
+	/*
+	 * These two values determine the minimum point we must recover up to
+	 * before starting up:
+	 *
+	 * minRecoveryPoint is updated to the latest replayed LSN whenever we
+	 * flush a data change during archive recovery. That guards against
+	 * starting archive recovery, aborting it, and restarting with an earlier
+	 * stop location. If we've already flushed data changes from WAL record X
+	 * to disk, we mustn't start up until we reach X again. Zero when not
+	 * doing archive recovery.
+	 *
+	 * backupStartPoint is the redo pointer of the backup start checkpoint, if
+	 * we are recovering from an online backup and haven't reached the end of
+	 * backup yet. It is reset to zero when the end of backup is reached, and
+	 * we mustn't start up before that. A boolean would suffice otherwise, but
+	 * we use the redo pointer as a cross-check when we see an end-of-backup
+	 * record, to make sure the end-of-backup record corresponds the base
+	 * backup we're recovering from.
+	 */
+	XLogRecPtr	minRecoveryPoint;
+	XLogRecPtr	backupStartPoint;
 
 	/*
 	 * This data is used to check for hardware-architecture compatibility of
-- 
GitLab