Subject: [PATCH v12] Avoid creating archive status ".ready" files too early

From: Nathan Bossart <[email-masked]>
Date: Tue, 17 Aug 2021 03:52:43 +0000

WAL records may span multiple segments, but XLogWrite() does not
wait for the entire record to be written out to disk before
creating archive status files.  Instead, as soon as the last WAL page of
the segment is written, the archive status file is created, and the
archiver may process it.  If PostgreSQL crashes before it is able to
write and flush the rest of the record (in the next WAL segment), the
wrong version of the first segment file lingers in the archive, which
causes operations such as point-in-time restores to fail.

To fix this, keep track of records that span across segments and ensure
that segments are only marked ready-for-archival once such records have
been completely written to disk.

XXX Note that this may add a new LWLock acquisition in the transaction
commit path.  (It doesn't happen in all cases.)

Author: Nathan Bossart <[email-masked]>
Reviewed-by: Kyotaro Horiguchi <[email-masked]>
Reviewed-by: Ryo Matsumura <[email-masked]>
Reviewed-by: Andrey Borodin <[email-masked]>
Discussion: https://postgr.es/m/[email-masked]
---
 src/backend/access/transam/timeline.c    |   2 +-
 src/backend/access/transam/xlog.c        | 307 ++++++++++++++++++++++-
 src/backend/access/transam/xlogarchive.c |  17 +-
 src/backend/postmaster/walwriter.c       |   7 +
 src/backend/replication/walreceiver.c    |   6 +-
 src/backend/storage/lmgr/lwlocknames.txt |   1 +
 src/include/access/xlog.h                |   1 +
 src/include/access/xlogarchive.h         |   4 +-
 src/include/access/xlogdefs.h            |   1 +
 src/tools/pgindent/typedefs.list         |   1 +
 10 files changed, 323 insertions(+), 24 deletions(-)

diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 8d0903c175..acd5c2431d 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -452,7 +452,7 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
 	if (XLogArchivingActive())
 	{
 		TLHistoryFileName(histfname, newTLI);
-		XLogArchiveNotify(histfname);
+		XLogArchiveNotify(histfname, true);
 	}
 }
 
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e51a7a749d..01da4ab7b8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -512,6 +512,16 @@ typedef enum ExclusiveBackupState
  */
 static SessionBackupState sessionBackupState = SESSION_BACKUP_NONE;
 
+/*
+ * Entries for SegmentBoundaryMap.  Each such entry represents a WAL
+ * record that ends in endpos and crosses the WAL segment boundary.
+ */
+typedef struct SegmentBoundaryEntry
+{
+	XLogSegNo	seg;			/* must be first */
+	XLogRecPtr	endpos;
+} SegmentBoundaryEntry;
+
 /*
  * Shared state data for WAL insertion.
  */
@@ -723,6 +733,12 @@ typedef struct XLogCtlData
 	 */
 	XLogRecPtr	lastFpwDisableRecPtr;
 
+	/*
+	 * The last segment we've marked ready for archival.  Protected by
+	 * info_lck.
+	 */
+	XLogSegNo	lastNotifiedSeg;
+
 	slock_t		info_lck;		/* locks shared variables shown above */
 } XLogCtlData;
 
@@ -736,6 +752,23 @@ static WALInsertLockPadded *WALInsertLocks = NULL;
  */
 static ControlFileData *ControlFile = NULL;
 
+/*
+ * Segment boundary map.
+ *
+ * This hash table tracks WAL records that cross WAL segment boundaries.
+ * For WAL archiving it is critical not to mark a segment as archivable
+ * until both halves of the record are flushed: otherwise if we crash
+ * after archiving the segment containing the first half and before
+ * flushing the second half of the record, then after recovery the
+ * primary would overwrite the first half with new data, making the
+ * segment archived prior to the crash a bogus copy.
+ *
+ * XXX note that an equivalent problem exists with streaming replication.
+ *
+ * Protected by SegmentBoundaryLock.
+ */
+static HTAB *SegmentBoundaryMap = NULL;
+
 /*
  * Calculate the amount of space left on the page after 'endptr'. Beware
  * multiple evaluation!
@@ -920,6 +953,13 @@ static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 						   XLogSegNo *endlogSegNo);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
+static void RegisterSegmentBoundary(XLogSegNo seg, XLogRecPtr pos);
+static XLogSegNo GetLastNotifiedSegment(void);
+static void SetLastNotifiedSegment(XLogSegNo seg);
+static bool GetLatestSegmentBoundary(XLogSegNo last_notified,
+									 XLogRecPtr flushed,
+									 XLogSegNo *latest_boundary_seg);
+static void RemoveSegmentBoundariesUpTo(XLogSegNo seg);
 static void CleanupBackupHistory(void);
 static void UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force);
 static XLogRecord *ReadRecord(XLogReaderState *xlogreader,
@@ -1154,23 +1194,56 @@ XLogInsertRecord(XLogRecData *rdata,
 	END_CRIT_SECTION();
 
 	/*
-	 * Update shared LogwrtRqst.Write, if we crossed page boundary.
+	 * If we crossed page boundary, update LogwrtRqst.Write; if we crossed
+	 * segment boundary, register that and wake up walwriter.
 	 */
 	if (StartPos / XLOG_BLCKSZ != EndPos / XLOG_BLCKSZ)
 	{
+		XLogSegNo	StartSeg;
+		XLogSegNo	EndSeg;
+
+		XLByteToSeg(StartPos, StartSeg, wal_segment_size);
+		XLByteToSeg(EndPos, EndSeg, wal_segment_size);
+
+		/*
+		 * Register our crossing the segment boundary if that occurred.
+		 *
+		 * Note that we did not use XLByteToPrevSeg() for determining the
+		 * ending segment.  This is so that a record that fits perfectly into
+		 * the end of the segment is marked ready for archival as soon as the
+		 * flushed pointer jumps to the next segment.
+		 */
+		if (StartSeg != EndSeg && XLogArchivingActive())
+			RegisterSegmentBoundary(EndSeg, EndPos);
+
+		/*
+		 * Advance LogwrtRqst.Write so that it includes new block(s).
+		 *
+		 * We do this after registering the segment boundary so that the
+		 * comparison with the flushed pointer below can use the latest value
+		 * known globally.
+		 */
 		SpinLockAcquire(&XLogCtl->info_lck);
-		/* advance global request to include new block(s) */
 		if (XLogCtl->LogwrtRqst.Write < EndPos)
 			XLogCtl->LogwrtRqst.Write = EndPos;
 		/* update local result copy while I have the chance */
 		LogwrtResult = XLogCtl->LogwrtResult;
 		SpinLockRelease(&XLogCtl->info_lck);
+
+		/*
+		 * There's a chance that the record was already flushed to disk and we
+		 * missed marking segments as ready for archive.  If this happens, we
+		 * nudge the WALWriter, which will take care of notifying segments as
+		 * needed.
+		 */
+		if (StartSeg != EndSeg && XLogArchivingActive() &&
+			LogwrtResult.Flush >= EndPos && ProcGlobal->walwriterLatch)
+			SetLatch(ProcGlobal->walwriterLatch);
 	}
 
 	/*
 	 * If this was an XLOG_SWITCH record, flush the record and the empty
-	 * padding space that fills the rest of the segment, and perform
-	 * end-of-segment actions (eg, notifying archiver).
+	 * padding space that fills the rest of the segment.
 	 */
 	if (isLogSwitch)
 	{
@@ -2421,6 +2494,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 
 	/* We should always be inside a critical section here */
 	Assert(CritSectionCount > 0);
+	Assert(LWLockHeldByMe(WALWriteLock));
 
 	/*
 	 * Update local LogwrtResult (caller probably did this already, but...)
@@ -2586,11 +2660,13 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 			 * later. Doing it here ensures that one and only one backend will
 			 * perform this fsync.
 			 *
-			 * This is also the right place to notify the Archiver that the
-			 * segment is ready to copy to archival storage, and to update the
-			 * timer for archive_timeout, and to signal for a checkpoint if
-			 * too many logfile segments have been used since the last
-			 * checkpoint.
+			 * If WAL archiving is active, we attempt to notify the archiver
+			 * of any segments that are now ready for archival.
+			 *
+			 * This is also the right place to update the timer for
+			 * archive_timeout and to signal for a checkpoint if too many
+			 * logfile segments have been used since the last checkpoint.
+			 *
 			 */
 			if (finishing_seg)
 			{
@@ -2602,7 +2678,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 				LogwrtResult.Flush = LogwrtResult.Write;	/* end of page */
 
 				if (XLogArchivingActive())
-					XLogArchiveNotifySeg(openLogSegNo);
+					NotifySegmentsReadyForArchive(LogwrtResult.Flush);
 
 				XLogCtl->lastSegSwitchTime = (pg_time_t) time(NULL);
 				XLogCtl->lastSegSwitchLSN = LogwrtResult.Flush;
@@ -2690,6 +2766,9 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 			XLogCtl->LogwrtRqst.Flush = LogwrtResult.Flush;
 		SpinLockRelease(&XLogCtl->info_lck);
 	}
+
+	if (XLogArchivingActive())
+		NotifySegmentsReadyForArchive(LogwrtResult.Flush);
 }
 
 /*
@@ -4328,6 +4407,189 @@ ValidateXLOGDirectoryStructure(void)
 	}
 }
 
+/*
+ * RegisterSegmentBoundary
+ *		Enter a new entry into the segment boundary map.
+ *
+ * An entry with the given key (the segment number) must not already exist in
+ * the map.
+ */
+static void
+RegisterSegmentBoundary(XLogSegNo seg, XLogRecPtr pos)
+{
+	SegmentBoundaryEntry *entry;
+	XLogSegNo	segno PG_USED_FOR_ASSERTS_ONLY;
+	bool		found;
+
+	/* verify caller computed segment number correctly */
+	AssertArg((XLByteToSeg(pos, segno, wal_segment_size), segno == seg));
+
+	LWLockAcquire(SegmentBoundaryLock, LW_EXCLUSIVE);
+
+	entry = (SegmentBoundaryEntry *) hash_search(SegmentBoundaryMap,
+												 (void *) &seg, HASH_ENTER,
+												 &found);
+	if (found)
+		elog(ERROR, "entry for segment already exists");
+
+	entry->endpos = pos;
+
+	LWLockRelease(SegmentBoundaryLock);
+}
+
+/*
+ * GetLatestSegmentBoundary
+ *
+ * Find the latest segment boundary in SegmentBoundaryMap that is less
+ * than or equal to the given "flushed" pointer and beyond the last
+ * notified segment.  If such a segment is found, latest_boundary_seg
+ * is populated and true is returned.  Otherwise, false is returned.
+ */
+static bool
+GetLatestSegmentBoundary(XLogSegNo last_notified, XLogRecPtr flushed,
+						 XLogSegNo *latest_boundary_seg)
+{
+	XLogSegNo	flushed_seg;
+	XLogSegNo	seg;
+
+	Assert(LWLockHeldByMe(SegmentBoundaryLock));
+	Assert(latest_boundary_seg != NULL);
+
+	XLByteToSeg(flushed, flushed_seg, wal_segment_size);
+
+	for (seg = flushed_seg; seg > last_notified; seg--)
+	{
+		SegmentBoundaryEntry *entry;
+
+		entry = (SegmentBoundaryEntry *) hash_search(SegmentBoundaryMap,
+													 (void *) &seg, HASH_FIND,
+													 NULL);
+
+		if (entry != NULL && flushed >= entry->endpos)
+		{
+			*latest_boundary_seg = entry->seg;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * RemoveSegmentBoundariesUpTo
+ *
+ * Remove all entries in the SegmentBoundaryMap with segment numbers
+ * up to and including seg.
+ */
+static void
+RemoveSegmentBoundariesUpTo(XLogSegNo seg)
+{
+	SegmentBoundaryEntry *entry;
+	HASH_SEQ_STATUS status;
+
+	Assert(LWLockHeldByMeInMode(SegmentBoundaryLock, LW_EXCLUSIVE));
+
+	hash_seq_init(&status, SegmentBoundaryMap);
+
+	while ((entry = (SegmentBoundaryEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (entry->seg <= seg)
+			(void) hash_search(SegmentBoundaryMap, (void *) &entry->seg,
+							   HASH_REMOVE, NULL);
+	}
+}
+
+/*
+ * NotifySegmentsReadyForArchive
+ *
+ * Mark segments as ready for archival, given that it is safe to do so.
+ * This function is idempotent.
+ */
+void
+NotifySegmentsReadyForArchive(XLogRecPtr flushRecPtr)
+{
+	XLogSegNo	flushed_seg;
+	XLogSegNo	latest_boundary_seg;
+	XLogSegNo	last_notified;
+
+	/*
+	 * We first do a quick sanity check to see if we can bail out without
+	 * taking the SegmentBoundaryLock at all.  It is expected that this
+	 * function will run frequently and that it will need to do nothing the
+	 * vast majority of the time.
+	 *
+	 * Specifically, we bail out if we've already marked the segment prior to
+	 * the segment that contains flushRecPtr as ready for archival.  We
+	 * intentionally use XLByteToSeg() instead of XLByteToPrevSeg() so that we
+	 * don't skip notifying when a record fits perfectly into the end of a
+	 * segment.  (flushRecPtr should point to the first byte of the record
+	 * _after_ the one that is known to be flushed to disk.)
+	 */
+	last_notified = GetLastNotifiedSegment();
+	XLByteToSeg(flushRecPtr, flushed_seg, wal_segment_size);
+	if (last_notified >= flushed_seg - 1)
+		return;
+
+	LWLockAcquire(SegmentBoundaryLock, LW_EXCLUSIVE);
+
+	/* Reobtain lastNotifiedSeg in case someone else changed it. */
+	last_notified = GetLastNotifiedSegment();
+
+	/* Retrieve the latest segment boundary to use for notifying segments. */
+	if (GetLatestSegmentBoundary(last_notified, flushRecPtr, &latest_boundary_seg))
+	{
+		/*
+		 * Update shared memory and discard segment boundaries that are no
+		 * longer needed.
+		 *
+		 * It is safe to update shared memory before we attempt to create the
+		 * .ready files.  If our calls to XLogArchiveNotifySeg() fail,
+		 * RemoveOldXlogFiles() will retry it as needed.
+		 */
+		SetLastNotifiedSegment(latest_boundary_seg - 1);
+		RemoveSegmentBoundariesUpTo(latest_boundary_seg);
+
+		LWLockRelease(SegmentBoundaryLock);
+
+		/*
+		 * Notify archiver about segments that are ready for archival (by
+		 * creating the corresponding .ready files).
+		 */
+		for (XLogSegNo seg = last_notified + 1; seg < latest_boundary_seg; seg++)
+			XLogArchiveNotifySeg(seg, false);
+
+		PgArchWakeup();
+	}
+	else
+		LWLockRelease(SegmentBoundaryLock);
+}
+
+/*
+ * GetLastNotifiedSegment
+ */
+XLogSegNo
+GetLastNotifiedSegment(void)
+{
+	XLogSegNo	seg;
+
+	SpinLockAcquire(&XLogCtl->info_lck);
+	seg = XLogCtl->lastNotifiedSeg;
+	SpinLockRelease(&XLogCtl->info_lck);
+
+	return seg;
+}
+
+/*
+ * SetLastNotifiedSegment
+ */
+static void
+SetLastNotifiedSegment(XLogSegNo seg)
+{
+	SpinLockAcquire(&XLogCtl->info_lck);
+	XLogCtl->lastNotifiedSeg = seg;
+	SpinLockRelease(&XLogCtl->info_lck);
+}
+
 /*
  * Remove previous backup history files.  This also retries creation of
  * .ready files for any backup history files for which XLogArchiveNotify
@@ -5117,6 +5379,9 @@ XLOGShmemSize(void)
 	/* and the buffers themselves */
 	size = add_size(size, mul_size(XLOG_BLCKSZ, XLOGbuffers));
 
+	/* hash table of segment-crossing WAL records */
+	size = add_size(size, hash_estimate_size(16, sizeof(SegmentBoundaryEntry)));
+
 	/*
 	 * Note: we don't count ControlFileData, it comes out of the "slop factor"
 	 * added by CreateSharedMemoryAndSemaphores.  This lets us use this
@@ -5134,6 +5399,7 @@ XLOGShmemInit(void)
 	char	   *allocptr;
 	int			i;
 	ControlFileData *localControlFile;
+	HASHCTL		info;
 
 #ifdef WAL_DEBUG
 
@@ -5227,12 +5493,20 @@ XLOGShmemInit(void)
 	XLogCtl->InstallXLogFileSegmentActive = false;
 	XLogCtl->SharedPromoteIsTriggered = false;
 	XLogCtl->WalWriterSleeping = false;
+	XLogCtl->lastNotifiedSeg = MaxXLogSegNo;
 
 	SpinLockInit(&XLogCtl->Insert.insertpos_lck);
 	SpinLockInit(&XLogCtl->info_lck);
 	SpinLockInit(&XLogCtl->ulsn_lck);
 	InitSharedLatch(&XLogCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
+
+	/* Initialize stuff for marking segments as ready for archival. */
+	memset(&info, 0, sizeof(info));
+	info.keysize = sizeof(XLogSegNo);
+	info.entrysize = sizeof(SegmentBoundaryEntry);
+	SegmentBoundaryMap = ShmemInitHash("Segment Boundary Table", 16, 16, &info,
+									   HASH_ELEM | HASH_BLOBS);
 }
 
 /*
@@ -7873,6 +8147,17 @@ StartupXLOG(void)
 	XLogCtl->LogwrtRqst.Write = EndOfLog;
 	XLogCtl->LogwrtRqst.Flush = EndOfLog;
 
+	/*
+	 * Initialize XLogCtl->lastNotifiedSeg to the previous WAL file.
+	 */
+	if (XLogArchivingActive())
+	{
+		XLogSegNo	EndOfLogSeg;
+
+		XLByteToSeg(EndOfLog, EndOfLogSeg, wal_segment_size);
+		SetLastNotifiedSegment(EndOfLogSeg - 1);
+	}
+
 	/*
 	 * Update full_page_writes in shared memory and write an XLOG_FPW_CHANGE
 	 * record before resource manager writes cleanup WAL records or checkpoint
@@ -8000,7 +8285,7 @@ StartupXLOG(void)
 				XLogArchiveCleanup(partialfname);
 
 				durable_rename(origpath, partialpath, ERROR);
-				XLogArchiveNotify(partialfname);
+				XLogArchiveNotify(partialfname, true);
 			}
 		}
 	}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 26b023e754..b9c19b2085 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -433,7 +433,7 @@ KeepFileRestoredFromArchive(const char *path, const char *xlogfname)
 	if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
 		XLogArchiveForceDone(xlogfname);
 	else
-		XLogArchiveNotify(xlogfname);
+		XLogArchiveNotify(xlogfname, true);
 
 	/*
 	 * If the existing file was replaced, since walsenders might have it open,
@@ -462,9 +462,12 @@ KeepFileRestoredFromArchive(const char *path, const char *xlogfname)
  * by the archiver, e.g. we write 0000000100000001000000C6.ready
  * and the archiver then knows to archive XLOGDIR/0000000100000001000000C6,
  * then when complete, rename it to 0000000100000001000000C6.done
+ *
+ * Optionally, nudge the archiver process so that it'll notice the file we
+ * create.
  */
 void
-XLogArchiveNotify(const char *xlog)
+XLogArchiveNotify(const char *xlog, bool nudge)
 {
 	char		archiveStatusPath[MAXPGPATH];
 	FILE	   *fd;
@@ -489,8 +492,8 @@ XLogArchiveNotify(const char *xlog)
 		return;
 	}
 
-	/* Notify archiver that it's got something to do */
-	if (IsUnderPostmaster)
+	/* If caller requested, let archiver know it's got work to do */
+	if (nudge)
 		PgArchWakeup();
 }
 
@@ -498,12 +501,12 @@ XLogArchiveNotify(const char *xlog)
  * Convenience routine to notify using segment number representation of filename
  */
 void
-XLogArchiveNotifySeg(XLogSegNo segno)
+XLogArchiveNotifySeg(XLogSegNo segno, bool nudge)
 {
 	char		xlog[MAXFNAMELEN];
 
 	XLogFileName(xlog, ThisTimeLineID, segno, wal_segment_size);
-	XLogArchiveNotify(xlog);
+	XLogArchiveNotify(xlog, nudge);
 }
 
 /*
@@ -608,7 +611,7 @@ XLogArchiveCheckDone(const char *xlog)
 		return true;
 
 	/* Retry creation of the .ready file */
-	XLogArchiveNotify(xlog);
+	XLogArchiveNotify(xlog, true);
 	return false;
 }
 
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 626fae8454..6a1e16edc2 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -248,6 +248,13 @@ WalWriterMain(void)
 		/* Process any signals received recently */
 		HandleWalWriterInterrupts();
 
+		/*
+		 * Notify the archiver of any WAL segments that are ready.  We do this
+		 * here to handle a race condition where WAL is flushed to disk prior
+		 * to registering the segment boundary.
+		 */
+		NotifySegmentsReadyForArchive(GetFlushRecPtr());
+
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
 		 * work to do, reset hibernation counter.
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 9a2bc37fd7..60de3be92c 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -622,7 +622,7 @@ WalReceiverMain(void)
 			if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
 				XLogArchiveForceDone(xlogfname);
 			else
-				XLogArchiveNotify(xlogfname);
+				XLogArchiveNotify(xlogfname, true);
 		}
 		recvFile = -1;
 
@@ -760,7 +760,7 @@ WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
 			if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
 				XLogArchiveForceDone(fname);
 			else
-				XLogArchiveNotify(fname);
+				XLogArchiveNotify(fname, true);
 
 			pfree(fname);
 			pfree(content);
@@ -915,7 +915,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
 				if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
 					XLogArchiveForceDone(xlogfname);
 				else
-					XLogArchiveNotify(xlogfname);
+					XLogArchiveNotify(xlogfname, true);
 			}
 			recvFile = -1;
 
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index 6c7cf6c295..14a742d655 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -53,3 +53,4 @@ XactTruncationLock					44
 # 45 was XactTruncationLock until removal of BackendRandomLock
 WrapLimitsVacuumLock				46
 NotifyQueueTailLock					47
+SegmentBoundaryLock					48
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 0a8ede700d..6b6ae81c2d 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -315,6 +315,7 @@ extern XLogRecPtr GetInsertRecPtr(void);
 extern XLogRecPtr GetFlushRecPtr(void);
 extern XLogRecPtr GetLastImportantRecPtr(void);
 extern void RemovePromoteSignalFiles(void);
+extern void NotifySegmentsReadyForArchive(XLogRecPtr flushRecPtr);
 
 extern bool PromoteIsTriggered(void);
 extern bool CheckPromoteSignal(void);
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 3edd1a976c..935b4cb02d 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -23,8 +23,8 @@ extern bool RestoreArchivedFile(char *path, const char *xlogfname,
 extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
 								   bool failOnSignal);
 extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
-extern void XLogArchiveNotify(const char *xlog);
-extern void XLogArchiveNotifySeg(XLogSegNo segno);
+extern void XLogArchiveNotify(const char *xlog, bool nudge);
+extern void XLogArchiveNotifySeg(XLogSegNo segno, bool nudge);
 extern void XLogArchiveForceDone(const char *xlog);
 extern bool XLogArchiveCheckDone(const char *xlog);
 extern bool XLogArchiveIsBusy(const char *xlog);
diff --git a/src/include/access/xlogdefs.h b/src/include/access/xlogdefs.h
index 60348d1850..9b455e88e3 100644
--- a/src/include/access/xlogdefs.h
+++ b/src/include/access/xlogdefs.h
@@ -46,6 +46,7 @@ typedef uint64 XLogRecPtr;
  * XLogSegNo - physical log file sequence number.
  */
 typedef uint64 XLogSegNo;
+#define MaxXLogSegNo	((XLogSegNo) 0xFFFFFFFFFFFFFFFF)
 
 /*
  * TimeLineID (TLI) - identifies different database histories to prevent
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 37cf4b2f76..79694b049e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2334,6 +2334,7 @@ SecBufferDesc
 SecLabelItem
 SecLabelStmt
 SeenRelsEntry
+SegmentBoundaryEntry
 SelectLimit
 SelectStmt
 Selectivity
-- 
2.20.1

--ngwxbmustuw56o2o--