From 1f9891ba666e50849570e5de0908200747054909 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Mon, 9 Jul 2018 21:54:03 +1200
Subject: [PATCH] Track the next xid using 64 bits.

Instead of tracking the epoch independently, start using a 64 bit transaction
ID in several places.  This fix an unlikely bug where an epoch increment could
be missed if you managed to consume more than 2^32 transactions between
checkpoints.

Work in progress!

Discussion: https://postgr.es/m/CAA4eK1%2BMv%2Bmb0HFfWM9Srtc6MVe160WFurXV68iAFMcagRZ0dQ%40mail.gmail.com
---
 src/backend/access/nbtree/nbtpage.c     |   4 +-
 src/backend/access/rmgrdesc/xlogdesc.c  |   4 +-
 src/backend/access/transam/clog.c       |   4 +-
 src/backend/access/transam/commit_ts.c  |   5 +-
 src/backend/access/transam/multixact.c  |   9 +--
 src/backend/access/transam/subtrans.c   |   2 +-
 src/backend/access/transam/twophase.c   |  17 +---
 src/backend/access/transam/varsup.c     |  53 +++++++++---
 src/backend/access/transam/xact.c       |  21 +----
 src/backend/access/transam/xlog.c       | 103 ++++++------------------
 src/backend/commands/vacuum.c           |  10 +--
 src/backend/postmaster/autovacuum.c     |   4 +-
 src/backend/replication/walreceiver.c   |   5 +-
 src/backend/replication/walsender.c     |  26 +-----
 src/backend/storage/ipc/procarray.c     |  21 ++---
 src/backend/storage/lmgr/predicate.c    |   2 +-
 src/backend/utils/adt/txid.c            |  13 ++-
 src/backend/utils/misc/pg_controldata.c |   5 +-
 src/bin/pg_controldata/pg_controldata.c |   5 +-
 src/bin/pg_resetwal/pg_resetwal.c       |  19 +++--
 src/include/access/transam.h            |  20 ++++-
 src/include/access/xlog.h               |   1 -
 src/include/c.h                         |   2 +
 src/include/catalog/pg_control.h        |   3 +-
 src/include/storage/standby.h           |   2 +-
 25 files changed, 154 insertions(+), 206 deletions(-)

diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 2e959da5f85..89a85b04430 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -1946,7 +1946,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
 	 * Mark the page itself deleted.  It can be recycled when all current
 	 * transactions are gone.  Storing GetTopTransactionId() would work, but
 	 * we're in VACUUM and would not otherwise have an XID.  Having already
-	 * updated links to the target, ReadNewTransactionId() suffices as an
+	 * updated links to the target, ReadNextBigTransactionId() suffices as an
 	 * upper bound.  Any scan having retained a now-stale link is advertising
 	 * in its PGXACT an xmin less than or equal to the value we read here.  It
 	 * will continue to do so, holding back RecentGlobalXmin, for the duration
@@ -1956,7 +1956,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, bool *rightsib_empty)
 	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
 	opaque->btpo_flags &= ~BTP_HALF_DEAD;
 	opaque->btpo_flags |= BTP_DELETED;
-	opaque->btpo.xact = ReadNewTransactionId();
+	opaque->btpo.xact = XidFromBigTransactionId(ReadNextBigTransactionId());
 
 	/* And update the metapage, if needed */
 	if (BufferIsValid(metabuf))
diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c
index 00741c7b09e..2c3ea10af94 100644
--- a/src/backend/access/rmgrdesc/xlogdesc.c
+++ b/src/backend/access/rmgrdesc/xlogdesc.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_control.h"
@@ -52,7 +53,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record)
 						 checkpoint->ThisTimeLineID,
 						 checkpoint->PrevTimeLineID,
 						 checkpoint->fullPageWrites ? "true" : "false",
-						 checkpoint->nextXidEpoch, checkpoint->nextXid,
+						 EpochFromBigTransactionId(checkpoint->nextBigXid),
+						 XidFromBigTransactionId(checkpoint->nextBigXid),
 						 checkpoint->nextOid,
 						 checkpoint->nextMulti,
 						 checkpoint->nextMultiOffset,
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 8b7ff5b0c24..fa2dfb50b7a 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -754,7 +754,7 @@ ZeroCLOGPage(int pageno, bool writeXlog)
 void
 StartupCLOG(void)
 {
-	TransactionId xid = ShmemVariableCache->nextXid;
+	TransactionId xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	int			pageno = TransactionIdToPage(xid);
 
 	LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);
@@ -773,7 +773,7 @@ StartupCLOG(void)
 void
 TrimCLOG(void)
 {
-	TransactionId xid = ShmemVariableCache->nextXid;
+	TransactionId xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	int			pageno = TransactionIdToPage(xid);
 
 	LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 73fac1ba81d..5637ee4f162 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -644,7 +644,7 @@ ActivateCommitTs(void)
 	}
 	LWLockRelease(CommitTsLock);
 
-	xid = ShmemVariableCache->nextXid;
+	xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	pageno = TransactionIdToCTsPage(xid);
 
 	/*
@@ -671,7 +671,8 @@ ActivateCommitTs(void)
 	if (ShmemVariableCache->oldestCommitTsXid == InvalidTransactionId)
 	{
 		ShmemVariableCache->oldestCommitTsXid =
-			ShmemVariableCache->newestCommitTsXid = ReadNewTransactionId();
+			ShmemVariableCache->newestCommitTsXid =
+			XidFromBigTransactionId(ReadNextBigTransactionId());
 	}
 	LWLockRelease(CommitTsLock);
 
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index a9a51055e96..f008fdf11d4 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -3283,14 +3283,7 @@ multixact_redo(XLogReaderState *record)
 		 * process doesn't need to hold a lock while checking this. We still
 		 * acquire the lock to modify it, though.
 		 */
-		if (TransactionIdFollowsOrEquals(max_xid,
-										 ShmemVariableCache->nextXid))
-		{
-			LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-			ShmemVariableCache->nextXid = max_xid;
-			TransactionIdAdvance(ShmemVariableCache->nextXid);
-			LWLockRelease(XidGenLock);
-		}
+		AdvanceNextBigTransactionIdPast(max_xid, true);
 	}
 	else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
 	{
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 4faa21f5aef..4f225d56c86 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -261,7 +261,7 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
 	LWLockAcquire(SubtransControlLock, LW_EXCLUSIVE);
 
 	startPage = TransactionIdToPage(oldestActiveXID);
-	endPage = TransactionIdToPage(ShmemVariableCache->nextXid);
+	endPage = TransactionIdToPage(XidFromBigTransactionId(ShmemVariableCache->nextBigXid));
 
 	while (startPage != endPage)
 	{
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e8d4e37fe30..3cdc5dfddeb 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1879,7 +1879,7 @@ restoreTwoPhaseData(void)
 TransactionId
 PrescanPreparedTransactions(TransactionId **xids_p, int *nxids_p)
 {
-	TransactionId origNextXid = ShmemVariableCache->nextXid;
+	TransactionId origNextXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	TransactionId result = origNextXid;
 	TransactionId *xids = NULL;
 	int			nxids = 0;
@@ -2104,7 +2104,7 @@ ProcessTwoPhaseBuffer(TransactionId xid,
 					  bool fromdisk,
 					  bool setParent, bool setNextXid)
 {
-	TransactionId origNextXid = ShmemVariableCache->nextXid;
+	TransactionId origNextXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	TransactionId *subxids;
 	char	   *buf;
 	TwoPhaseFileHeader *hdr;
@@ -2210,23 +2210,14 @@ ProcessTwoPhaseBuffer(TransactionId xid,
 		Assert(TransactionIdFollows(subxid, xid));
 
 		/* update nextXid if needed */
-		if (setNextXid &&
-			TransactionIdFollowsOrEquals(subxid,
-										 ShmemVariableCache->nextXid))
+		if (setNextXid)
 		{
 			/*
 			 * We don't expect anyone else to modify nextXid, hence we don't
 			 * need to hold a lock while examining it.  We still acquire the
 			 * lock to modify it, though, so we recheck.
 			 */
-			LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-			if (TransactionIdFollowsOrEquals(subxid,
-											 ShmemVariableCache->nextXid))
-			{
-				ShmemVariableCache->nextXid = subxid;
-				TransactionIdAdvance(ShmemVariableCache->nextXid);
-			}
-			LWLockRelease(XidGenLock);
+			AdvanceNextBigTransactionIdPast(subxid, true);
 		}
 
 		if (setParent)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 394843f7e91..c84edf1ccde 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -73,7 +73,7 @@ GetNewTransactionId(bool isSubXact)
 
 	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
 
-	xid = ShmemVariableCache->nextXid;
+	xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 
 	/*----------
 	 * Check to see if it's safe to assign another XID.  This protects against
@@ -156,7 +156,7 @@ GetNewTransactionId(bool isSubXact)
 
 		/* Re-acquire lock and start over */
 		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		xid = ShmemVariableCache->nextXid;
+		xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	}
 
 	/*
@@ -173,12 +173,12 @@ GetNewTransactionId(bool isSubXact)
 	ExtendSUBTRANS(xid);
 
 	/*
-	 * Now advance the nextXid counter.  This must not happen until after we
+	 * Now advance the nextBigXid counter.  This must not happen until after we
 	 * have successfully completed ExtendCLOG() --- if that routine fails, we
 	 * want the next incoming transaction to try it again.  We cannot assign
 	 * more XIDs until there is CLOG space for them.
 	 */
-	TransactionIdAdvance(ShmemVariableCache->nextXid);
+	BigTransactionIdAdvance(ShmemVariableCache->nextBigXid);
 
 	/*
 	 * We must store the new XID into the shared ProcArray before releasing
@@ -244,18 +244,47 @@ GetNewTransactionId(bool isSubXact)
 }
 
 /*
- * Read nextXid but don't allocate it.
+ * Read nextBigXid but don't allocate it.
  */
-TransactionId
-ReadNewTransactionId(void)
+BigTransactionId
+ReadNextBigTransactionId(void)
 {
-	TransactionId xid;
+	BigTransactionId bigXid;
 
 	LWLockAcquire(XidGenLock, LW_SHARED);
-	xid = ShmemVariableCache->nextXid;
+	bigXid = ShmemVariableCache->nextBigXid;
 	LWLockRelease(XidGenLock);
 
-	return xid;
+	return bigXid;
+}
+
+/*
+ * Advance nextBigXid to the value after a given xid.  The epoch is inferred.
+ * If lock_free_check is true, then the caller must be sure that it's safe to
+ * read nextBigXid without holding XidGenLock (ie during recovery).
+ */
+void
+AdvanceNextBigTransactionIdPast(TransactionId xid, bool lock_free_check)
+{
+	TransactionId current_xid;
+	uint32 epoch;
+
+	if (lock_free_check &&
+		!TransactionIdFollowsOrEquals(xid,
+									  XidFromBigTransactionId(ShmemVariableCache->nextBigXid)))
+		return;
+
+	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
+	current_xid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
+	if (TransactionIdFollowsOrEquals(xid, current_xid))
+	{
+		epoch = EpochFromBigTransactionId(ShmemVariableCache->nextBigXid);
+		if (xid < current_xid)
+			++epoch; /* epoch wrapped */
+		ShmemVariableCache->nextBigXid = MakeBigTransactionId(epoch, xid);
+		BigTransactionIdAdvance(ShmemVariableCache->nextBigXid);
+	}
+	LWLockRelease(XidGenLock);
 }
 
 /*
@@ -359,7 +388,7 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
 	ShmemVariableCache->xidStopLimit = xidStopLimit;
 	ShmemVariableCache->xidWrapLimit = xidWrapLimit;
 	ShmemVariableCache->oldestXidDB = oldest_datoid;
-	curXid = ShmemVariableCache->nextXid;
+	curXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	LWLockRelease(XidGenLock);
 
 	/* Log the info */
@@ -435,7 +464,7 @@ ForceTransactionIdLimitUpdate(void)
 
 	/* Locking is probably not really necessary, but let's be careful */
 	LWLockAcquire(XidGenLock, LW_SHARED);
-	nextXid = ShmemVariableCache->nextXid;
+	nextXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	xidVacLimit = ShmemVariableCache->xidVacLimit;
 	oldestXid = ShmemVariableCache->oldestXid;
 	oldestXidDB = ShmemVariableCache->oldestXidDB;
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 1da1f13ef33..dcc3b47c445 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -468,7 +468,7 @@ GetStableLatestTransactionId(void)
 		lxid = MyProc->lxid;
 		stablexid = GetTopTransactionIdIfAny();
 		if (!TransactionIdIsValid(stablexid))
-			stablexid = ReadNewTransactionId();
+			stablexid = XidFromBigTransactionId(ReadNextBigTransactionId());
 	}
 
 	Assert(TransactionIdIsValid(stablexid));
@@ -5529,14 +5529,7 @@ xact_redo_commit(xl_xact_parsed_commit *parsed,
 	 * hold a lock while checking this. We still acquire the lock to modify
 	 * it, though.
 	 */
-	if (TransactionIdFollowsOrEquals(max_xid,
-									 ShmemVariableCache->nextXid))
-	{
-		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextXid = max_xid;
-		TransactionIdAdvance(ShmemVariableCache->nextXid);
-		LWLockRelease(XidGenLock);
-	}
+	AdvanceNextBigTransactionIdPast(max_xid, true);
 
 	Assert(((parsed->xinfo & XACT_XINFO_HAS_ORIGIN) == 0) ==
 		   (origin_id == InvalidRepOriginId));
@@ -5688,15 +5681,7 @@ xact_redo_abort(xl_xact_parsed_abort *parsed, TransactionId xid)
 	max_xid = TransactionIdLatest(xid,
 								  parsed->nsubxacts,
 								  parsed->subxacts);
-
-	if (TransactionIdFollowsOrEquals(max_xid,
-									 ShmemVariableCache->nextXid))
-	{
-		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextXid = max_xid;
-		TransactionIdAdvance(ShmemVariableCache->nextXid);
-		LWLockRelease(XidGenLock);
-	}
+	AdvanceNextBigTransactionIdPast(max_xid, true);
 
 	if (standbyState == STANDBY_DISABLED)
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 20b23cb3609..0237d877414 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -579,8 +579,7 @@ typedef struct XLogCtlData
 	/* Protected by info_lck: */
 	XLogwrtRqst LogwrtRqst;
 	XLogRecPtr	RedoRecPtr;		/* a recent copy of Insert->RedoRecPtr */
-	uint32		ckptXidEpoch;	/* nextXID & epoch of latest checkpoint */
-	TransactionId ckptXid;
+	BigTransactionId ckptBigXid;	/* nextXID & epoch of latest checkpoint */
 	XLogRecPtr	asyncXactLSN;	/* LSN of newest async commit/abort */
 	XLogRecPtr	replicationSlotMinLSN;	/* oldest LSN needed by any slot */
 
@@ -5102,8 +5101,7 @@ BootStrapXLOG(void)
 	checkPoint.ThisTimeLineID = ThisTimeLineID;
 	checkPoint.PrevTimeLineID = ThisTimeLineID;
 	checkPoint.fullPageWrites = fullPageWrites;
-	checkPoint.nextXidEpoch = 0;
-	checkPoint.nextXid = FirstNormalTransactionId;
+	checkPoint.nextBigXid = MakeBigTransactionId(0, FirstNormalTransactionId);
 	checkPoint.nextOid = FirstBootstrapObjectId;
 	checkPoint.nextMulti = FirstMultiXactId;
 	checkPoint.nextMultiOffset = 0;
@@ -5116,7 +5114,7 @@ BootStrapXLOG(void)
 	checkPoint.time = (pg_time_t) time(NULL);
 	checkPoint.oldestActiveXid = InvalidTransactionId;
 
-	ShmemVariableCache->nextXid = checkPoint.nextXid;
+	ShmemVariableCache->nextBigXid = checkPoint.nextBigXid;
 	ShmemVariableCache->nextOid = checkPoint.nextOid;
 	ShmemVariableCache->oidCount = 0;
 	MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
@@ -6700,7 +6698,8 @@ StartupXLOG(void)
 							 wasShutdown ? "true" : "false")));
 	ereport(DEBUG1,
 			(errmsg_internal("next transaction ID: %u:%u; next OID: %u",
-							 checkPoint.nextXidEpoch, checkPoint.nextXid,
+							 EpochFromBigTransactionId(checkPoint.nextBigXid),
+							 XidFromBigTransactionId(checkPoint.nextBigXid),
 							 checkPoint.nextOid)));
 	ereport(DEBUG1,
 			(errmsg_internal("next MultiXactId: %u; next MultiXactOffset: %u",
@@ -6715,12 +6714,12 @@ StartupXLOG(void)
 			(errmsg_internal("commit timestamp Xid oldest/newest: %u/%u",
 							 checkPoint.oldestCommitTsXid,
 							 checkPoint.newestCommitTsXid)));
-	if (!TransactionIdIsNormal(checkPoint.nextXid))
+	if (!TransactionIdIsNormal(XidFromBigTransactionId(checkPoint.nextBigXid)))
 		ereport(PANIC,
 				(errmsg("invalid next transaction ID")));
 
 	/* initialize shared memory variables from the checkpoint record */
-	ShmemVariableCache->nextXid = checkPoint.nextXid;
+	ShmemVariableCache->nextBigXid = checkPoint.nextBigXid;
 	ShmemVariableCache->nextOid = checkPoint.nextOid;
 	ShmemVariableCache->oidCount = 0;
 	MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
@@ -6729,8 +6728,7 @@ StartupXLOG(void)
 	SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB, true);
 	SetCommitTsLimit(checkPoint.oldestCommitTsXid,
 					 checkPoint.newestCommitTsXid);
-	XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
-	XLogCtl->ckptXid = checkPoint.nextXid;
+	XLogCtl->ckptBigXid = checkPoint.nextBigXid;
 
 	/*
 	 * Initialize replication slots, before there's a chance to remove
@@ -7000,7 +6998,7 @@ StartupXLOG(void)
 			Assert(TransactionIdIsValid(oldestActiveXID));
 
 			/* Tell procarray about the range of xids it has to deal with */
-			ProcArrayInitRecovery(ShmemVariableCache->nextXid);
+			ProcArrayInitRecovery(XidFromBigTransactionId(ShmemVariableCache->nextBigXid));
 
 			/*
 			 * Startup commit log and subtrans only.  MultiXact and commit
@@ -7030,9 +7028,9 @@ StartupXLOG(void)
 				running.xcnt = nxids;
 				running.subxcnt = 0;
 				running.subxid_overflow = false;
-				running.nextXid = checkPoint.nextXid;
+				running.nextXid = XidFromBigTransactionId(checkPoint.nextBigXid);
 				running.oldestRunningXid = oldestActiveXID;
-				latestCompletedXid = checkPoint.nextXid;
+				latestCompletedXid = XidFromBigTransactionId(checkPoint.nextBigXid);
 				TransactionIdRetreat(latestCompletedXid);
 				Assert(TransactionIdIsNormal(latestCompletedXid));
 				running.latestCompletedXid = latestCompletedXid;
@@ -7208,14 +7206,7 @@ StartupXLOG(void)
 				 * don't need to hold a lock while examining it.  We still
 				 * acquire the lock to modify it, though.
 				 */
-				if (TransactionIdFollowsOrEquals(record->xl_xid,
-												 ShmemVariableCache->nextXid))
-				{
-					LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-					ShmemVariableCache->nextXid = record->xl_xid;
-					TransactionIdAdvance(ShmemVariableCache->nextXid);
-					LWLockRelease(XidGenLock);
-				}
+				AdvanceNextBigTransactionIdPast(record->xl_xid, true);
 
 				/*
 				 * Before replaying this record, check if this record causes
@@ -7782,7 +7773,7 @@ StartupXLOG(void)
 
 	/* also initialize latestCompletedXid, to nextXid - 1 */
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
-	ShmemVariableCache->latestCompletedXid = ShmemVariableCache->nextXid;
+	ShmemVariableCache->latestCompletedXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	TransactionIdRetreat(ShmemVariableCache->latestCompletedXid);
 	LWLockRelease(ProcArrayLock);
 
@@ -8375,41 +8366,6 @@ GetLastSegSwitchData(XLogRecPtr *lastSwitchLSN)
 	return result;
 }
 
-/*
- * GetNextXidAndEpoch - get the current nextXid value and associated epoch
- *
- * This is exported for use by code that would like to have 64-bit XIDs.
- * We don't really support such things, but all XIDs within the system
- * can be presumed "close to" the result, and thus the epoch associated
- * with them can be determined.
- */
-void
-GetNextXidAndEpoch(TransactionId *xid, uint32 *epoch)
-{
-	uint32		ckptXidEpoch;
-	TransactionId ckptXid;
-	TransactionId nextXid;
-
-	/* Must read checkpoint info first, else have race condition */
-	SpinLockAcquire(&XLogCtl->info_lck);
-	ckptXidEpoch = XLogCtl->ckptXidEpoch;
-	ckptXid = XLogCtl->ckptXid;
-	SpinLockRelease(&XLogCtl->info_lck);
-
-	/* Now fetch current nextXid */
-	nextXid = ReadNewTransactionId();
-
-	/*
-	 * nextXid is certainly logically later than ckptXid.  So if it's
-	 * numerically less, it must have wrapped into the next epoch.
-	 */
-	if (nextXid < ckptXid)
-		ckptXidEpoch++;
-
-	*xid = nextXid;
-	*epoch = ckptXidEpoch;
-}
-
 /*
  * This must be called ONCE during postmaster or standalone-backend shutdown
  */
@@ -8819,7 +8775,7 @@ CreateCheckPoint(int flags)
 	 * there.
 	 */
 	LWLockAcquire(XidGenLock, LW_SHARED);
-	checkPoint.nextXid = ShmemVariableCache->nextXid;
+	checkPoint.nextBigXid = ShmemVariableCache->nextBigXid;
 	checkPoint.oldestXid = ShmemVariableCache->oldestXid;
 	checkPoint.oldestXidDB = ShmemVariableCache->oldestXidDB;
 	LWLockRelease(XidGenLock);
@@ -8829,11 +8785,6 @@ CreateCheckPoint(int flags)
 	checkPoint.newestCommitTsXid = ShmemVariableCache->newestCommitTsXid;
 	LWLockRelease(CommitTsLock);
 
-	/* Increase XID epoch if we've wrapped around since last checkpoint */
-	checkPoint.nextXidEpoch = ControlFile->checkPointCopy.nextXidEpoch;
-	if (checkPoint.nextXid < ControlFile->checkPointCopy.nextXid)
-		checkPoint.nextXidEpoch++;
-
 	LWLockAcquire(OidGenLock, LW_SHARED);
 	checkPoint.nextOid = ShmemVariableCache->nextOid;
 	if (!shutdown)
@@ -8977,8 +8928,7 @@ CreateCheckPoint(int flags)
 
 	/* Update shared-memory copy of checkpoint XID/epoch */
 	SpinLockAcquire(&XLogCtl->info_lck);
-	XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
-	XLogCtl->ckptXid = checkPoint.nextXid;
+	XLogCtl->ckptBigXid = checkPoint.nextBigXid;
 	SpinLockRelease(&XLogCtl->info_lck);
 
 	/*
@@ -9733,7 +9683,7 @@ xlog_redo(XLogReaderState *record)
 		memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
 		/* In a SHUTDOWN checkpoint, believe the counters exactly */
 		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextXid = checkPoint.nextXid;
+		ShmemVariableCache->nextBigXid = checkPoint.nextBigXid;
 		LWLockRelease(XidGenLock);
 		LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
 		ShmemVariableCache->nextOid = checkPoint.nextOid;
@@ -9787,9 +9737,9 @@ xlog_redo(XLogReaderState *record)
 			running.xcnt = nxids;
 			running.subxcnt = 0;
 			running.subxid_overflow = false;
-			running.nextXid = checkPoint.nextXid;
+			running.nextXid = XidFromBigTransactionId(checkPoint.nextBigXid);
 			running.oldestRunningXid = oldestActiveXID;
-			latestCompletedXid = checkPoint.nextXid;
+			latestCompletedXid = XidFromBigTransactionId(checkPoint.nextBigXid);
 			TransactionIdRetreat(latestCompletedXid);
 			Assert(TransactionIdIsNormal(latestCompletedXid));
 			running.latestCompletedXid = latestCompletedXid;
@@ -9801,13 +9751,11 @@ xlog_redo(XLogReaderState *record)
 		}
 
 		/* ControlFile->checkPointCopy always tracks the latest ckpt XID */
-		ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
-		ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
+		ControlFile->checkPointCopy.nextBigXid = checkPoint.nextBigXid;
 
 		/* Update shared-memory copy of checkpoint XID/epoch */
 		SpinLockAcquire(&XLogCtl->info_lck);
-		XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
-		XLogCtl->ckptXid = checkPoint.nextXid;
+		XLogCtl->ckptBigXid = checkPoint.nextBigXid;
 		SpinLockRelease(&XLogCtl->info_lck);
 
 		/*
@@ -9828,9 +9776,8 @@ xlog_redo(XLogReaderState *record)
 		memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
 		/* In an ONLINE checkpoint, treat the XID counter as a minimum */
 		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		if (TransactionIdPrecedes(ShmemVariableCache->nextXid,
-								  checkPoint.nextXid))
-			ShmemVariableCache->nextXid = checkPoint.nextXid;
+		if (ShmemVariableCache->nextBigXid < checkPoint.nextBigXid)
+			ShmemVariableCache->nextBigXid = checkPoint.nextBigXid;
 		LWLockRelease(XidGenLock);
 
 		/*
@@ -9860,13 +9807,11 @@ xlog_redo(XLogReaderState *record)
 			SetTransactionIdLimit(checkPoint.oldestXid,
 								  checkPoint.oldestXidDB);
 		/* ControlFile->checkPointCopy always tracks the latest ckpt XID */
-		ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
-		ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
+		ControlFile->checkPointCopy.nextBigXid = checkPoint.nextBigXid;
 
 		/* Update shared-memory copy of checkpoint XID/epoch */
 		SpinLockAcquire(&XLogCtl->info_lck);
-		XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
-		XLogCtl->ckptXid = checkPoint.nextXid;
+		XLogCtl->ckptBigXid = checkPoint.nextBigXid;
 		SpinLockRelease(&XLogCtl->info_lck);
 
 		/* TLI should not change in an on-line checkpoint */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d90cb9a9022..fd3015af272 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -647,7 +647,7 @@ vacuum_set_xid_limits(Relation rel,
 	 * autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
 	 * freeze age of zero.
 	 */
-	safeLimit = ReadNewTransactionId() - autovacuum_freeze_max_age;
+	safeLimit = XidFromBigTransactionId(ReadNextBigTransactionId()) - autovacuum_freeze_max_age;
 	if (!TransactionIdIsNormal(safeLimit))
 		safeLimit = FirstNormalTransactionId;
 
@@ -725,7 +725,7 @@ vacuum_set_xid_limits(Relation rel,
 		 * Compute XID limit causing a full-table vacuum, being careful not to
 		 * generate a "permanent" XID.
 		 */
-		limit = ReadNewTransactionId() - freezetable;
+		limit = XidFromBigTransactionId(ReadNextBigTransactionId()) - freezetable;
 		if (!TransactionIdIsNormal(limit))
 			limit = FirstNormalTransactionId;
 
@@ -944,7 +944,7 @@ vac_update_relstats(Relation relation,
 	if (TransactionIdIsNormal(frozenxid) &&
 		pgcform->relfrozenxid != frozenxid &&
 		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
-		 TransactionIdPrecedes(ReadNewTransactionId(),
+		 TransactionIdPrecedes(XidFromBigTransactionId(ReadNextBigTransactionId()),
 							   pgcform->relfrozenxid)))
 	{
 		pgcform->relfrozenxid = frozenxid;
@@ -1021,7 +1021,7 @@ vac_update_datfrozenxid(void)
 	 * validly see during the scan.  These are conservative values, but it's
 	 * not really worth trying to be more exact.
 	 */
-	lastSaneFrozenXid = ReadNewTransactionId();
+	lastSaneFrozenXid = XidFromBigTransactionId(ReadNextBigTransactionId());
 	lastSaneMinMulti = ReadNextMultiXactId();
 
 	/*
@@ -1157,7 +1157,7 @@ vac_truncate_clog(TransactionId frozenXID,
 				  TransactionId lastSaneFrozenXid,
 				  MultiXactId lastSaneMinMulti)
 {
-	TransactionId nextXID = ReadNewTransactionId();
+	TransactionId nextXID = XidFromBigTransactionId(ReadNextBigTransactionId());
 	Relation	relation;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 02e6d8131e0..881e4327013 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1172,7 +1172,7 @@ do_start_worker(void)
 	 * pass without forcing a vacuum.  (This limit can be tightened for
 	 * particular tables, but not loosened.)
 	 */
-	recentXid = ReadNewTransactionId();
+	recentXid = XidFromBigTransactionId(ReadNextBigTransactionId());
 	xidForceLimit = recentXid - autovacuum_freeze_max_age;
 	/* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */
 	/* this can cause the limit to go backwards by 3, but that's OK */
@@ -1703,7 +1703,7 @@ AutoVacWorkerMain(int argc, char *argv[])
 			pg_usleep(PostAuthDelay * 1000000L);
 
 		/* And do an appropriate amount of work */
-		recentXid = ReadNewTransactionId();
+		recentXid = XidFromBigTransactionId(ReadNextBigTransactionId());
 		recentMulti = ReadNextMultiXactId();
 		do_autovacuum();
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 987bb84683c..09a869a3086 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1194,6 +1194,7 @@ static void
 XLogWalRcvSendHSFeedback(bool immed)
 {
 	TimestampTz now;
+	BigTransactionId nextBigXid;
 	TransactionId nextXid;
 	uint32		xmin_epoch,
 				catalog_xmin_epoch;
@@ -1272,7 +1273,9 @@ XLogWalRcvSendHSFeedback(bool immed)
 	 * Get epoch and adjust if nextXid and oldestXmin are different sides of
 	 * the epoch boundary.
 	 */
-	GetNextXidAndEpoch(&nextXid, &xmin_epoch);
+	nextBigXid = ReadNextBigTransactionId();
+	nextXid = XidFromBigTransactionId(nextBigXid);
+	xmin_epoch = EpochFromBigTransactionId(nextBigXid);
 	catalog_xmin_epoch = xmin_epoch;
 	if (nextXid < xmin)
 		xmin_epoch--;
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index e47ddca6bca..edf9d1226bf 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1888,35 +1888,17 @@ PhysicalReplicationSlotNewXmin(TransactionId feedbackXmin, TransactionId feedbac
  * Check that the provided xmin/epoch are sane, that is, not in the future
  * and not so far back as to be already wrapped around.
  *
- * Epoch of nextXid should be same as standby, or if the counter has
- * wrapped, then one greater than standby.
- *
  * This check doesn't care about whether clog exists for these xids
  * at all.
  */
 static bool
 TransactionIdInRecentPast(TransactionId xid, uint32 epoch)
 {
-	TransactionId nextXid;
-	uint32		nextEpoch;
-
-	GetNextXidAndEpoch(&nextXid, &nextEpoch);
-
-	if (xid <= nextXid)
-	{
-		if (epoch != nextEpoch)
-			return false;
-	}
-	else
-	{
-		if (epoch + 1 != nextEpoch)
-			return false;
-	}
-
-	if (!TransactionIdPrecedesOrEquals(xid, nextXid))
-		return false;			/* epoch OK, but it's wrapped around */
+	BigTransactionId nextBigXid = ReadNextBigTransactionId();
+	BigTransactionId bigXid = MakeBigTransactionId(epoch, xid);
 
-	return true;
+	return bigXid <= nextBigXid &&
+		nextBigXid - bigXid < (BigTransactionId) 1 << 32;
 }
 
 /*
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index bd20497d81a..b97f15f2c28 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -883,15 +883,10 @@ ProcArrayApplyRecoveryInfo(RunningTransactions running)
 	 * it, though.
 	 */
 	nextXid = latestObservedXid;
+	AdvanceNextBigTransactionIdPast(nextXid, true);
 	TransactionIdAdvance(nextXid);
-	if (TransactionIdFollows(nextXid, ShmemVariableCache->nextXid))
-	{
-		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextXid = nextXid;
-		LWLockRelease(XidGenLock);
-	}
 
-	Assert(TransactionIdIsValid(ShmemVariableCache->nextXid));
+	Assert(TransactionIdIsValid(XidFromBigTransactionId(ShmemVariableCache->nextBigXid)));
 
 	KnownAssignedXidsDisplay(trace_recovery(DEBUG3));
 	if (standbyState == STANDBY_SNAPSHOT_READY)
@@ -1979,7 +1974,7 @@ GetRunningTransactionData(void)
 
 	latestCompletedXid = ShmemVariableCache->latestCompletedXid;
 
-	oldestRunningXid = ShmemVariableCache->nextXid;
+	oldestRunningXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 
 	/*
 	 * Spin over procArray collecting all xids
@@ -2068,7 +2063,7 @@ GetRunningTransactionData(void)
 	CurrentRunningXacts->xcnt = count - subcount;
 	CurrentRunningXacts->subxcnt = subcount;
 	CurrentRunningXacts->subxid_overflow = suboverflowed;
-	CurrentRunningXacts->nextXid = ShmemVariableCache->nextXid;
+	CurrentRunningXacts->nextXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	CurrentRunningXacts->oldestRunningXid = oldestRunningXid;
 	CurrentRunningXacts->latestCompletedXid = latestCompletedXid;
 
@@ -2113,7 +2108,7 @@ GetOldestActiveTransactionId(void)
 	 * have already completed), when we spin over it.
 	 */
 	LWLockAcquire(XidGenLock, LW_SHARED);
-	oldestRunningXid = ShmemVariableCache->nextXid;
+	oldestRunningXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 	LWLockRelease(XidGenLock);
 
 	/*
@@ -2181,7 +2176,7 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
 	 * a safe, albeit pessimal, value.
 	 */
 	LWLockAcquire(XidGenLock, LW_SHARED);
-	oldestSafeXid = ShmemVariableCache->nextXid;
+	oldestSafeXid = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 
 	/*
 	 * If there's already a slot pegging the xmin horizon, we can start with
@@ -3237,10 +3232,8 @@ RecordKnownAssignedTransactionIds(TransactionId xid)
 
 		/* ShmemVariableCache->nextXid must be beyond any observed xid */
 		next_expected_xid = latestObservedXid;
+		AdvanceNextBigTransactionIdPast(next_expected_xid, false);
 		TransactionIdAdvance(next_expected_xid);
-		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
-		ShmemVariableCache->nextXid = next_expected_xid;
-		LWLockRelease(XidGenLock);
 	}
 }
 
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index e8390311d03..caf4cb1b915 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -3270,7 +3270,7 @@ ReleasePredicateLocks(bool isCommit)
 	 * transaction to complete before freeing some RAM; correctness of visible
 	 * behavior is not affected.
 	 */
-	MySerializableXact->finishedBefore = ShmemVariableCache->nextXid;
+	MySerializableXact->finishedBefore = XidFromBigTransactionId(ShmemVariableCache->nextBigXid);
 
 	/*
 	 * If it's not a commit it's a rollback, and we can clear our locks
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index 7974c0bd3d8..ac355684587 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -91,7 +91,10 @@ typedef struct
 static void
 load_xid_epoch(TxidEpoch *state)
 {
-	GetNextXidAndEpoch(&state->last_xid, &state->epoch);
+	BigTransactionId bigXid = ReadNextBigTransactionId();
+
+	state->last_xid = XidFromBigTransactionId(bigXid);
+	state->epoch = EpochFromBigTransactionId(bigXid);
 }
 
 /*
@@ -114,8 +117,11 @@ TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
 	TransactionId xid = (TransactionId) xid_with_epoch;
 	uint32		now_epoch;
 	TransactionId now_epoch_last_xid;
+	BigTransactionId now_xid;
 
-	GetNextXidAndEpoch(&now_epoch_last_xid, &now_epoch);
+	now_xid = ReadNextBigTransactionId();
+	now_epoch_last_xid = XidFromBigTransactionId(now_xid);
+	now_epoch = EpochFromBigTransactionId(now_xid);
 
 	if (extracted_xid != NULL)
 		*extracted_xid = xid;
@@ -128,8 +134,7 @@ TransactionIdInRecentPast(uint64 xid_with_epoch, TransactionId *extracted_xid)
 		return true;
 
 	/* If the transaction ID is in the future, throw an error. */
-	if (xid_epoch > now_epoch
-		|| (xid_epoch == now_epoch && xid > now_epoch_last_xid))
+	if (xid_with_epoch > now_xid)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("transaction ID %s is in the future",
diff --git a/src/backend/utils/misc/pg_controldata.c b/src/backend/utils/misc/pg_controldata.c
index 3fc8b6a8a84..461344f9dcc 100644
--- a/src/backend/utils/misc/pg_controldata.c
+++ b/src/backend/utils/misc/pg_controldata.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/transam.h"
 #include "access/xlog_internal.h"
 #include "access/xlog.h"
 #include "catalog/pg_control.h"
@@ -164,8 +165,8 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
 	nulls[5] = false;
 
 	values[6] = CStringGetTextDatum(psprintf("%u:%u",
-											 ControlFile->checkPointCopy.nextXidEpoch,
-											 ControlFile->checkPointCopy.nextXid));
+											 EpochFromBigTransactionId(ControlFile->checkPointCopy.nextBigXid),
+											 XidFromBigTransactionId(ControlFile->checkPointCopy.nextBigXid)));
 	nulls[6] = false;
 
 	values[7] = ObjectIdGetDatum(ControlFile->checkPointCopy.nextOid);
diff --git a/src/bin/pg_controldata/pg_controldata.c b/src/bin/pg_controldata/pg_controldata.c
index 895a51f89d5..f6ecfb850c9 100644
--- a/src/bin/pg_controldata/pg_controldata.c
+++ b/src/bin/pg_controldata/pg_controldata.c
@@ -20,6 +20,7 @@
 
 #include <time.h>
 
+#include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_control.h"
@@ -256,8 +257,8 @@ main(int argc, char *argv[])
 	printf(_("Latest checkpoint's full_page_writes: %s\n"),
 		   ControlFile->checkPointCopy.fullPageWrites ? _("on") : _("off"));
 	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   ControlFile->checkPointCopy.nextXidEpoch,
-		   ControlFile->checkPointCopy.nextXid);
+		   EpochFromBigTransactionId(ControlFile->checkPointCopy.nextBigXid),
+		   XidFromBigTransactionId(ControlFile->checkPointCopy.nextBigXid));
 	printf(_("Latest checkpoint's NextOID:          %u\n"),
 		   ControlFile->checkPointCopy.nextOid);
 	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 8cff5356925..485e04920ce 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -431,11 +431,15 @@ main(int argc, char *argv[])
 	 * if any, includes these values.)
 	 */
 	if (set_xid_epoch != -1)
-		ControlFile.checkPointCopy.nextXidEpoch = set_xid_epoch;
+		ControlFile.checkPointCopy.nextBigXid =
+			MakeBigTransactionId(set_xid_epoch,
+								 XidFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid));
 
 	if (set_xid != 0)
 	{
-		ControlFile.checkPointCopy.nextXid = set_xid;
+		ControlFile.checkPointCopy.nextBigXid =
+			MakeBigTransactionId(EpochFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid),
+								 set_xid);
 
 		/*
 		 * For the moment, just set oldestXid to a value that will force
@@ -705,8 +709,7 @@ GuessControlValues(void)
 	ControlFile.checkPointCopy.ThisTimeLineID = 1;
 	ControlFile.checkPointCopy.PrevTimeLineID = 1;
 	ControlFile.checkPointCopy.fullPageWrites = false;
-	ControlFile.checkPointCopy.nextXidEpoch = 0;
-	ControlFile.checkPointCopy.nextXid = FirstNormalTransactionId;
+	ControlFile.checkPointCopy.nextBigXid = MakeBigTransactionId(0, FirstNormalTransactionId);
 	ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId;
 	ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
 	ControlFile.checkPointCopy.nextMultiOffset = 0;
@@ -786,8 +789,8 @@ PrintControlValues(bool guessed)
 	printf(_("Latest checkpoint's full_page_writes: %s\n"),
 		   ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off"));
 	printf(_("Latest checkpoint's NextXID:          %u:%u\n"),
-		   ControlFile.checkPointCopy.nextXidEpoch,
-		   ControlFile.checkPointCopy.nextXid);
+		   EpochFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid),
+		   XidFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid));
 	printf(_("Latest checkpoint's NextOID:          %u\n"),
 		   ControlFile.checkPointCopy.nextOid);
 	printf(_("Latest checkpoint's NextMultiXactId:  %u\n"),
@@ -879,7 +882,7 @@ PrintNewControlValues(void)
 	if (set_xid != 0)
 	{
 		printf(_("NextXID:                              %u\n"),
-			   ControlFile.checkPointCopy.nextXid);
+			   XidFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid));
 		printf(_("OldestXID:                            %u\n"),
 			   ControlFile.checkPointCopy.oldestXid);
 		printf(_("OldestXID's DB:                       %u\n"),
@@ -889,7 +892,7 @@ PrintNewControlValues(void)
 	if (set_xid_epoch != -1)
 	{
 		printf(_("NextXID epoch:                        %u\n"),
-			   ControlFile.checkPointCopy.nextXidEpoch);
+			   EpochFromBigTransactionId(ControlFile.checkPointCopy.nextBigXid));
 	}
 
 	if (set_oldest_commit_ts_xid != 0)
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 83ec3f19797..af40efd079d 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -44,6 +44,10 @@
 #define TransactionIdStore(xid, dest)	(*(dest) = (xid))
 #define StoreInvalidTransactionId(dest) (*(dest) = InvalidTransactionId)
 
+#define EpochFromBigTransactionId(x)	((uint32) ((x) >> 32))
+#define XidFromBigTransactionId(x)		((uint32) (x))
+#define MakeBigTransactionId(epoch, xid)((((uint64)(epoch)) << 32 ) | (xid))
+
 /* advance a transaction ID variable, handling wraparound correctly */
 #define TransactionIdAdvance(dest)	\
 	do { \
@@ -52,6 +56,15 @@
 			(dest) = FirstNormalTransactionId; \
 	} while(0)
 
+/* advance a BigTransactionId lvalue, handling wraparound correctly */
+#define BigTransactionIdAdvance(dest) \
+	do { \
+		(dest)++; \
+		if (XidFromBigTransactionId(dest) < FirstNormalTransactionId) \
+			(dest) = MakeBigTransactionId(EpochFromBigTransactionId(dest), \
+										  FirstNormalTransactionId); \
+	} while (0)
+
 /* back up a transaction ID variable, handling wraparound correctly */
 #define TransactionIdRetreat(dest)	\
 	do { \
@@ -114,12 +127,12 @@ typedef struct VariableCacheData
 	/*
 	 * These fields are protected by XidGenLock.
 	 */
-	TransactionId nextXid;		/* next XID to assign */
+	BigTransactionId nextBigXid;	/* next XID to assign */
 
 	TransactionId oldestXid;	/* cluster-wide minimum datfrozenxid */
 	TransactionId xidVacLimit;	/* start forcing autovacuums here */
 	TransactionId xidWarnLimit; /* start complaining here */
-	TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */
+	TransactionId xidStopLimit; /* refuse to advance nextBigXid beyond here */
 	TransactionId xidWrapLimit; /* where the world ends */
 	Oid			oldestXidDB;	/* database with minimum datfrozenxid */
 
@@ -176,7 +189,8 @@ extern XLogRecPtr TransactionIdGetCommitLSN(TransactionId xid);
 
 /* in transam/varsup.c */
 extern TransactionId GetNewTransactionId(bool isSubXact);
-extern TransactionId ReadNewTransactionId(void);
+extern void AdvanceNextBigTransactionIdPast(TransactionId xid, bool lock_free_check);
+extern BigTransactionId ReadNextBigTransactionId(void);
 extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 					  Oid oldest_datoid);
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 421ba6d7755..3c9d3401df5 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -276,7 +276,6 @@ extern XLogRecPtr GetRedoRecPtr(void);
 extern XLogRecPtr GetInsertRecPtr(void);
 extern XLogRecPtr GetFlushRecPtr(void);
 extern XLogRecPtr GetLastImportantRecPtr(void);
-extern void GetNextXidAndEpoch(TransactionId *xid, uint32 *epoch);
 extern void RemovePromoteSignalFiles(void);
 
 extern bool CheckPromoteSignal(void);
diff --git a/src/include/c.h b/src/include/c.h
index 1e50103095b..7f643799601 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -471,6 +471,8 @@ typedef double float8;
 typedef Oid regproc;
 typedef regproc RegProcedure;
 
+typedef uint64 BigTransactionId;	/* epoch and xid as one value */
+
 typedef uint32 TransactionId;
 
 typedef uint32 LocalTransactionId;
diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h
index 773d9e6ebae..0d8e52c091e 100644
--- a/src/include/catalog/pg_control.h
+++ b/src/include/catalog/pg_control.h
@@ -39,8 +39,7 @@ typedef struct CheckPoint
 	TimeLineID	PrevTimeLineID; /* previous TLI, if this record begins a new
 								 * timeline (equals ThisTimeLineID otherwise) */
 	bool		fullPageWrites; /* current full_page_writes */
-	uint32		nextXidEpoch;	/* higher-order bits of nextXid */
-	TransactionId nextXid;		/* next free XID */
+	BigTransactionId nextBigXid;	/* next free XID */
 	Oid			nextOid;		/* next free OID */
 	MultiXactId nextMulti;		/* next free MultiXactId */
 	MultiXactOffset nextMultiOffset;	/* next free MultiXact offset */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 1fcd8cf1b59..1c62202f4e9 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -72,7 +72,7 @@ typedef struct RunningTransactionsData
 	int			xcnt;			/* # of xact ids in xids[] */
 	int			subxcnt;		/* # of subxact ids in xids[] */
 	bool		subxid_overflow;	/* snapshot overflowed, subxids missing */
-	TransactionId nextXid;		/* copy of ShmemVariableCache->nextXid */
+	TransactionId nextXid;	/* xid from ShmemVariableCache->nextBigXid */
 	TransactionId oldestRunningXid; /* *not* oldestXmin */
 	TransactionId latestCompletedXid;	/* so we can set xmax */
 
-- 
2.17.0

