From 8af31945cdc9031a3212d87659b201b5c62ed61a Mon Sep 17 00:00:00 2001
From: Maxim Orlov <orlovmg@gmail.com>
Date: Fri, 27 Feb 2026 16:59:51 +0300
Subject: [PATCH 3/3] WIP - make clog 64 bit

---
 src/backend/access/rmgrdesc/clogdesc.c      |   6 +-
 src/backend/access/transam/clog.c           | 204 ++++++++++----------
 src/backend/access/transam/transam.c        |  26 ++-
 src/backend/access/transam/twophase.c       |   8 +-
 src/backend/access/transam/varsup.c         |  10 +-
 src/backend/commands/indexcmds.c            |   2 +-
 src/backend/commands/vacuum.c               |  12 +-
 src/backend/storage/ipc/procarray.c         |  58 +++---
 src/backend/storage/lmgr/lock.c             |   4 +-
 src/backend/storage/lmgr/proc.c             |   8 +-
 src/bin/pg_verifybackup/t/003_corruption.pl |   2 +-
 src/include/access/clog.h                   |  15 +-
 src/include/access/transam.h                |   1 +
 src/include/storage/proc.h                  |   6 +-
 src/test/recovery/t/003_recovery_targets.pl |  12 ++
 15 files changed, 210 insertions(+), 164 deletions(-)

diff --git a/src/backend/access/rmgrdesc/clogdesc.c b/src/backend/access/rmgrdesc/clogdesc.c
index 022376045d..138bc2fce1 100644
--- a/src/backend/access/rmgrdesc/clogdesc.c
+++ b/src/backend/access/rmgrdesc/clogdesc.c
@@ -35,8 +35,10 @@ clog_desc(StringInfo buf, XLogReaderState *record)
 		xl_clog_truncate xlrec;
 
 		memcpy(&xlrec, rec, sizeof(xl_clog_truncate));
-		appendStringInfo(buf, "page %" PRId64 "; oldestXact %u",
-						 xlrec.pageno, xlrec.oldestXact);
+		appendStringInfo(buf, "page %" PRId64 "; oldestXact %u%u",
+						 xlrec.pageno,
+						 EpochFromFullTransactionId(xlrec.oldestXact),
+						 XidFromFullTransactionId(xlrec.oldestXact));
 	}
 }
 
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 3de650a18d..660dbe6e11 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -79,27 +79,27 @@
  * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE.
  */
 static inline int64
-TransactionIdToPage(TransactionId xid)
+FullTransactionIdToPage(FullTransactionId fxid)
 {
-	return xid / (int64) CLOG_XACTS_PER_PAGE;
+	return U64FromFullTransactionId(fxid) / (int64) CLOG_XACTS_PER_PAGE;
 }
 
 static inline int
-TransactionIdToPgIndex(TransactionId xid)
+FullTransactionIdToPgIndex(FullTransactionId fxid)
 {
-	return xid % (TransactionId) CLOG_XACTS_PER_PAGE;
+	return U64FromFullTransactionId(fxid) % (uint64) CLOG_XACTS_PER_PAGE;
 }
 
 static inline int
-TransactionIdToByte(TransactionId xid)
+FullTransactionIdToByte(FullTransactionId fxid)
 {
-	return TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE;
+	return FullTransactionIdToPgIndex(fxid) / CLOG_XACTS_PER_BYTE;
 }
 
 static inline int
-TransactionIdToBIndex(TransactionId xid)
+FullTransactionIdToBIndex(FullTransactionId fxid)
 {
-	return xid % (TransactionId) CLOG_XACTS_PER_BYTE;
+	return U64FromFullTransactionId(fxid) % (uint64) CLOG_XACTS_PER_BYTE;
 }
 
 /* We store the latest async LSN for each group of transactions */
@@ -107,10 +107,17 @@ TransactionIdToBIndex(TransactionId xid)
 #define CLOG_LSNS_PER_PAGE	(CLOG_XACTS_PER_PAGE / CLOG_XACTS_PER_LSN_GROUP)
 
 static inline int
-GetLSNIndex(int slotno, TransactionId xid)
+GetLSNIndex(int slotno, FullTransactionId fxid)
 {
 	return slotno * CLOG_LSNS_PER_PAGE +
-			TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_LSN_GROUP;
+			FullTransactionIdToPgIndex(fxid) / CLOG_XACTS_PER_LSN_GROUP;
+}
+
+static inline FullTransactionId
+AdjustToFullTransactionId(TransactionId xid)
+{
+	Assert(TransactionIdIsValid(xid));
+	return FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(), xid);
 }
 
 /*
@@ -129,20 +136,22 @@ static SlruCtlData XactCtlData;
 
 
 static bool CLOGPagePrecedes(int64 page1, int64 page2);
-static void WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact,
+static void WriteTruncateXlogRec(int64 pageno, FullTransactionId oldestXact,
 								 Oid oldestXactDb);
-static void TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
+static void TransactionIdSetPageStatus(FullTransactionId fxid, int nsubxids,
 									   TransactionId *subxids, XidStatus status,
 									   XLogRecPtr lsn, int64 pageno,
 									   bool all_xact_same_page);
-static void TransactionIdSetStatusBit(TransactionId xid, XidStatus status,
+static void TransactionIdSetStatusBit(FullTransactionId fxid, XidStatus status,
 									  XLogRecPtr lsn, int slotno);
 static void set_status_by_pages(int nsubxids, TransactionId *subxids,
 								XidStatus status, XLogRecPtr lsn);
-static bool TransactionGroupUpdateXidStatus(TransactionId xid,
+static bool TransactionGroupUpdateXidStatus(FullTransactionId fxid,
 											XidStatus status, XLogRecPtr lsn, int64 pageno);
-static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
-											   TransactionId *subxids, XidStatus status,
+static void TransactionIdSetPageStatusInternal(FullTransactionId fxid,
+											   int nsubxids,
+											   TransactionId *subxids,
+											   XidStatus status,
 											   XLogRecPtr lsn, int64 pageno);
 
 
@@ -196,11 +205,12 @@ static void TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
  * cache yet.
  */
 void
-TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
+TransactionIdSetTreeStatus(FullTransactionId fxid, int nsubxids,
 						   TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
 {
-	int64		pageno = TransactionIdToPage(xid);	/* get page of parent */
+	int64		pageno = FullTransactionIdToPage(fxid);	/* get page of parent */
 	int			i;
+	FullTransactionId next;
 
 	Assert(status == TRANSACTION_STATUS_COMMITTED ||
 		   status == TRANSACTION_STATUS_ABORTED);
@@ -209,9 +219,14 @@ TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
 	 * See how many subxids, if any, are on the same page as the parent, if
 	 * any.
 	 */
+	next = ReadNextFullTransactionId();
 	for (i = 0; i < nsubxids; i++)
 	{
-		if (TransactionIdToPage(subxids[i]) != pageno)
+		FullTransactionId subfxid = FullTransactionIdFromAllowableAt(next,
+																	 subxids[i]);
+		int64		subxid_pageno = FullTransactionIdToPage(subfxid);
+
+		if (subxid_pageno != pageno)
 			break;
 	}
 
@@ -223,7 +238,7 @@ TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
 		/*
 		 * Set the parent and all subtransactions in a single call
 		 */
-		TransactionIdSetPageStatus(xid, nsubxids, subxids, status, lsn,
+		TransactionIdSetPageStatus(fxid, nsubxids, subxids, status, lsn,
 								   pageno, true);
 	}
 	else
@@ -249,9 +264,9 @@ TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
 		 * Now set the parent and subtransactions on same page as the parent,
 		 * if any
 		 */
-		pageno = TransactionIdToPage(xid);
-		TransactionIdSetPageStatus(xid, nsubxids_on_first_page, subxids, status,
-								   lsn, pageno, false);
+		pageno = FullTransactionIdToPage(fxid);
+		TransactionIdSetPageStatus(fxid, nsubxids_on_first_page, subxids,
+								   status, lsn, pageno, false);
 
 		/*
 		 * Now work through the rest of the subxids one clog page at a time,
@@ -273,7 +288,8 @@ static void
 set_status_by_pages(int nsubxids, TransactionId *subxids,
 					XidStatus status, XLogRecPtr lsn)
 {
-	int64		pageno = TransactionIdToPage(subxids[0]);
+	FullTransactionId fxid = AdjustToFullTransactionId(subxids[0]);
+	int64		pageno = FullTransactionIdToPage(fxid);
 	int			offset = 0;
 	int			i = 0;
 
@@ -286,14 +302,15 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
 
 		do
 		{
-			nextpageno = TransactionIdToPage(subxids[i]);
+			fxid = AdjustToFullTransactionId(subxids[i]);
+			nextpageno = FullTransactionIdToPage(fxid);
 			if (nextpageno != pageno)
 				break;
 			num_on_page++;
 			i++;
 		} while (i < nsubxids);
 
-		TransactionIdSetPageStatus(InvalidTransactionId,
+		TransactionIdSetPageStatus(InvalidFullTransactionId,
 								   num_on_page, subxids + offset,
 								   status, lsn, pageno, false);
 		offset = i;
@@ -306,7 +323,7 @@ set_status_by_pages(int nsubxids, TransactionId *subxids,
  * entries on a single page.  Atomic only on this page.
  */
 static void
-TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
+TransactionIdSetPageStatus(FullTransactionId fxid, int nsubxids,
 						   TransactionId *subxids, XidStatus status,
 						   XLogRecPtr lsn, int64 pageno,
 						   bool all_xact_same_page)
@@ -334,7 +351,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
 	 * sub-XIDs and all of the XIDs for which we're adjusting clog should be
 	 * on the same page.  Check those conditions, too.
 	 */
-	if (all_xact_same_page && xid == MyProc->xid &&
+	if (all_xact_same_page && FullTransactionIdEquals(fxid, MyProc->xid) &&
 		nsubxids <= THRESHOLD_SUBTRANS_CLOG_OPT &&
 		nsubxids == MyProc->subxidStatus.count &&
 		(nsubxids == 0 ||
@@ -350,12 +367,12 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
 		if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
 		{
 			/* Got the lock without waiting!  Do the update. */
-			TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
+			TransactionIdSetPageStatusInternal(fxid, nsubxids, subxids, status,
 											   lsn, pageno);
 			LWLockRelease(lock);
 			return;
 		}
-		else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
+		else if (TransactionGroupUpdateXidStatus(fxid, status, lsn, pageno))
 		{
 			/* Group update mechanism has done the work. */
 			return;
@@ -366,7 +383,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
 
 	/* Group update not applicable, or couldn't accept this page number. */
 	LWLockAcquire(lock, LW_EXCLUSIVE);
-	TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
+	TransactionIdSetPageStatusInternal(fxid, nsubxids, subxids, status,
 									   lsn, pageno);
 	LWLockRelease(lock);
 }
@@ -377,7 +394,7 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
  * We don't do any locking here; caller must handle that.
  */
 static void
-TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
+TransactionIdSetPageStatusInternal(FullTransactionId fxid, int nsubxids,
 								   TransactionId *subxids, XidStatus status,
 								   XLogRecPtr lsn, int64 pageno)
 {
@@ -386,7 +403,8 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
 
 	Assert(status == TRANSACTION_STATUS_COMMITTED ||
 		   status == TRANSACTION_STATUS_ABORTED ||
-		   (status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
+		   (status == TRANSACTION_STATUS_SUB_COMMITTED &&
+			!FullTransactionIdIsValid(fxid)));
 	Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
 								LW_EXCLUSIVE));
 
@@ -400,7 +418,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
 	 * we think.
 	 */
 	slotno = SimpleLruReadPage(XactCtl, pageno, !XLogRecPtrIsValid(lsn),
-							   xid);
+							   XidFromFullTransactionId(fxid));
 
 	/*
 	 * Set the main transaction id, if any.
@@ -411,29 +429,40 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
 	 * could break atomicity, so we subcommit the subxids first before we mark
 	 * the top-level commit.
 	 */
-	if (TransactionIdIsValid(xid))
+	if (FullTransactionIdIsValid(fxid))
 	{
 		/* Subtransactions first, if needed ... */
 		if (status == TRANSACTION_STATUS_COMMITTED)
 		{
+			FullTransactionId	next = ReadNextFullTransactionId();
+
 			for (i = 0; i < nsubxids; i++)
 			{
-				Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
-				TransactionIdSetStatusBit(subxids[i],
+				FullTransactionId subxid =
+					FullTransactionIdFromAllowableAt(next, subxids[i]);
+				int64	subxid_pageno PG_USED_FOR_ASSERTS_ONLY =
+					FullTransactionIdToPage(subxid);
+
+				Assert(XactCtl->shared->page_number[slotno] == subxid_pageno);
+				TransactionIdSetStatusBit(subxid,
 										  TRANSACTION_STATUS_SUB_COMMITTED,
 										  lsn, slotno);
 			}
 		}
 
 		/* ... then the main transaction */
-		TransactionIdSetStatusBit(xid, status, lsn, slotno);
+		TransactionIdSetStatusBit(fxid, status, lsn, slotno);
 	}
 
 	/* Set the subtransactions */
 	for (i = 0; i < nsubxids; i++)
 	{
-		Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
-		TransactionIdSetStatusBit(subxids[i], status, lsn, slotno);
+		FullTransactionId subfxid = AdjustToFullTransactionId(subxids[i]);
+		int64		subxid_pageno PG_USED_FOR_ASSERTS_ONLY;
+
+		subxid_pageno = FullTransactionIdToPage(subfxid);
+		Assert(XactCtl->shared->page_number[slotno] == subxid_pageno);
+		TransactionIdSetStatusBit(subfxid, status, lsn, slotno);
 	}
 
 	XactCtl->shared->page_dirty[slotno] = true;
@@ -455,7 +484,7 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
  * number we need to update differs from those processes already waiting.
  */
 static bool
-TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
+TransactionGroupUpdateXidStatus(FullTransactionId fxid, XidStatus status,
 								XLogRecPtr lsn, int64 pageno)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
@@ -466,14 +495,14 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
 	LWLock	   *prevlock = NULL;
 
 	/* We should definitely have an XID whose status needs to be updated. */
-	Assert(TransactionIdIsValid(xid));
+	Assert(FullTransactionIdIsValid(fxid));
 
 	/*
 	 * Prepare to add ourselves to the list of processes needing a group XID
 	 * status update.
 	 */
 	proc->clogGroupMember = true;
-	proc->clogGroupMemberXid = xid;
+	proc->clogGroupMemberXid = fxid;
 	proc->clogGroupMemberXidStatus = status;
 	proc->clogGroupMemberPage = pageno;
 	proc->clogGroupMemberLsn = lsn;
@@ -675,15 +704,16 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
  * Caller must hold the corresponding SLRU bank lock, will be held at exit.
  */
 static void
-TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
+TransactionIdSetStatusBit(FullTransactionId fxid, XidStatus status,
+						  XLogRecPtr lsn, int slotno)
 {
-	int			byteno = TransactionIdToByte(xid);
-	int			bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
+	int			byteno = FullTransactionIdToByte(fxid);
+	int			bshift = FullTransactionIdToBIndex(fxid) * CLOG_BITS_PER_XACT;
 	char	   *byteptr;
 	char		byteval;
 	char		curval;
 
-	Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(xid));
+	Assert(XactCtl->shared->page_number[slotno] == FullTransactionIdToPage(fxid));
 	Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl,
 													 XactCtl->shared->page_number[slotno]),
 								LW_EXCLUSIVE));
@@ -726,7 +756,7 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
 	 */
 	if (XLogRecPtrIsValid(lsn))
 	{
-		int			lsnindex = GetLSNIndex(slotno, xid);
+		int			lsnindex = GetLSNIndex(slotno, fxid);
 
 		if (XactCtl->shared->group_lsn[lsnindex] < lsn)
 			XactCtl->shared->group_lsn[lsnindex] = lsn;
@@ -749,11 +779,11 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
  * for most uses; TransactionLogFetch() in transam.c is the intended caller.
  */
 XidStatus
-TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
+TransactionIdGetStatus(FullTransactionId fxid, XLogRecPtr *lsn)
 {
-	int64		pageno = TransactionIdToPage(xid);
-	int			byteno = TransactionIdToByte(xid);
-	int			bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
+	int64		pageno = FullTransactionIdToPage(fxid);
+	int			byteno = FullTransactionIdToByte(fxid);
+	int			bshift = FullTransactionIdToBIndex(fxid) * CLOG_BITS_PER_XACT;
 	int			slotno;
 	int			lsnindex;
 	char	   *byteptr;
@@ -761,12 +791,13 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
 
 	/* lock is acquired by SimpleLruReadPage_ReadOnly */
 
-	slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid);
+	slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno,
+										XidFromFullTransactionId(fxid));
 	byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
 
 	status = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
 
-	lsnindex = GetLSNIndex(slotno, xid);
+	lsnindex = GetLSNIndex(slotno, fxid);
 	*lsn = XactCtl->shared->group_lsn[lsnindex];
 
 	LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
@@ -827,8 +858,8 @@ CLOGShmemInit(void)
 	XactCtl->PagePrecedes = CLOGPagePrecedes;
 	SimpleLruInit(XactCtl, "transaction", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
 				  "pg_xact", LWTRANCHE_XACT_BUFFER,
-				  LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
-	SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
+				  LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, true);
+	/* doesn't meet criteria for unit tests */
 }
 
 /*
@@ -860,8 +891,7 @@ BootStrapCLOG(void)
 void
 StartupCLOG(void)
 {
-	TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
-	int64		pageno = TransactionIdToPage(xid);
+	int64		pageno = FullTransactionIdToPage(TransamVariables->nextXid);
 
 	/*
 	 * Initialize our idea of the latest page number.
@@ -875,8 +905,9 @@ StartupCLOG(void)
 void
 TrimCLOG(void)
 {
-	TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
-	int64		pageno = TransactionIdToPage(xid);
+	FullTransactionId fxid = TransamVariables->nextXid;
+	TransactionId xid = XidFromFullTransactionId(fxid);
+	int64		pageno = FullTransactionIdToPage(fxid);
 	LWLock	   *lock = SimpleLruGetBankLock(XactCtl, pageno);
 
 	LWLockAcquire(lock, LW_EXCLUSIVE);
@@ -893,10 +924,10 @@ TrimCLOG(void)
 	 * nextXid is exactly at a page boundary; and it's likely that the
 	 * "current" page doesn't exist yet in that case.)
 	 */
-	if (TransactionIdToPgIndex(xid) != 0)
+	if (FullTransactionIdToPgIndex(fxid) != 0)
 	{
-		int			byteno = TransactionIdToByte(xid);
-		int			bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
+		int			byteno = FullTransactionIdToByte(fxid);
+		int			bshift = FullTransactionIdToBIndex(fxid) * CLOG_BITS_PER_XACT;
 		int			slotno;
 		char	   *byteptr;
 
@@ -940,7 +971,7 @@ CheckPointCLOG(void)
  * in shared memory.
  */
 void
-ExtendCLOG(TransactionId newestXact)
+ExtendCLOG(FullTransactionId newestXact)
 {
 	int64		pageno;
 	LWLock	   *lock;
@@ -949,11 +980,11 @@ ExtendCLOG(TransactionId newestXact)
 	 * No work except at first XID of a page.  But beware: just after
 	 * wraparound, the first XID of page zero is FirstNormalTransactionId.
 	 */
-	if (TransactionIdToPgIndex(newestXact) != 0 &&
-		!TransactionIdEquals(newestXact, FirstNormalTransactionId))
+	if (FullTransactionIdToPgIndex(newestXact) != 0 &&
+		!FullTransactionIdEquals(newestXact, FirstNormalFullTransactionId))
 		return;
 
-	pageno = TransactionIdToPage(newestXact);
+	pageno = FullTransactionIdToPage(newestXact);
 	lock = SimpleLruGetBankLock(XactCtl, pageno);
 
 	LWLockAcquire(lock, LW_EXCLUSIVE);
@@ -982,15 +1013,13 @@ ExtendCLOG(TransactionId newestXact)
  * the XLOG flush unless we have confirmed that there is a removable segment.
  */
 void
-TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
+TruncateCLOG(FullTransactionId oldestXact, Oid oldestxid_datoid)
 {
-	int64		cutoffPage;
-
 	/*
 	 * The cutoff point is the start of the segment containing oldestXact. We
 	 * pass the *page* containing oldestXact to SimpleLruTruncate.
 	 */
-	cutoffPage = TransactionIdToPage(oldestXact);
+	int64		cutoffPage = FullTransactionIdToPage(oldestXact);
 
 	/* Check to see if there's any files that could be removed */
 	if (!SlruScanDirectory(XactCtl, SlruScanDirCbReportPresence, &cutoffPage))
@@ -1003,7 +1032,7 @@ TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
 	 * It's only necessary to do this if we will actually truncate away clog
 	 * pages.
 	 */
-	AdvanceOldestClogXid(oldestXact);
+	AdvanceOldestClogXid(XidFromFullTransactionId(oldestXact));
 
 	/*
 	 * Write XLOG record and flush XLOG to disk. We record the oldest xid
@@ -1020,35 +1049,11 @@ TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid)
 
 /*
  * Decide whether a CLOG page number is "older" for truncation purposes.
- *
- * We need to use comparison of TransactionIds here in order to do the right
- * thing with wraparound XID arithmetic.  However, TransactionIdPrecedes()
- * would get weird about permanent xact IDs.  So, offset both such that xid1,
- * xid2, and xid2 + CLOG_XACTS_PER_PAGE - 1 are all normal XIDs; this offset
- * is relevant to page 0 and to the page preceding page 0.
- *
- * The page containing oldestXact-2^31 is the important edge case.  The
- * portion of that page equaling or following oldestXact-2^31 is expendable,
- * but the portion preceding oldestXact-2^31 is not.  When oldestXact-2^31 is
- * the first XID of a page and segment, the entire page and segment is
- * expendable, and we could truncate the segment.  Recognizing that case would
- * require making oldestXact, not just the page containing oldestXact,
- * available to this callback.  The benefit would be rare and small, so we
- * don't optimize that edge case.
  */
 static bool
 CLOGPagePrecedes(int64 page1, int64 page2)
 {
-	TransactionId xid1;
-	TransactionId xid2;
-
-	xid1 = ((TransactionId) page1) * CLOG_XACTS_PER_PAGE;
-	xid1 += FirstNormalTransactionId + 1;
-	xid2 = ((TransactionId) page2) * CLOG_XACTS_PER_PAGE;
-	xid2 += FirstNormalTransactionId + 1;
-
-	return (TransactionIdPrecedes(xid1, xid2) &&
-			TransactionIdPrecedes(xid1, xid2 + CLOG_XACTS_PER_PAGE - 1));
+	return page1 < page2;
 }
 
 
@@ -1059,7 +1064,8 @@ CLOGPagePrecedes(int64 page1, int64 page2)
  * in TruncateCLOG().
  */
 static void
-WriteTruncateXlogRec(int64 pageno, TransactionId oldestXact, Oid oldestXactDb)
+WriteTruncateXlogRec(int64 pageno, FullTransactionId oldestXact,
+					 Oid oldestXactDb)
 {
 	XLogRecPtr	recptr;
 	xl_clog_truncate xlrec;
@@ -1098,7 +1104,7 @@ clog_redo(XLogReaderState *record)
 
 		memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_clog_truncate));
 
-		AdvanceOldestClogXid(xlrec.oldestXact);
+		AdvanceOldestClogXid(XidFromFullTransactionId(xlrec.oldestXact));
 
 		SimpleLruTruncate(XactCtl, xlrec.pageno);
 	}
diff --git a/src/backend/access/transam/transam.c b/src/backend/access/transam/transam.c
index 682182fb4a..065807697e 100644
--- a/src/backend/access/transam/transam.c
+++ b/src/backend/access/transam/transam.c
@@ -53,6 +53,7 @@ TransactionLogFetch(TransactionId transactionId)
 {
 	XidStatus	xidstatus;
 	XLogRecPtr	xidlsn;
+	FullTransactionId fxid;
 
 	/*
 	 * Before going to the commit log manager, check our single item cache to
@@ -73,10 +74,13 @@ TransactionLogFetch(TransactionId transactionId)
 		return TRANSACTION_STATUS_ABORTED;
 	}
 
+	fxid = FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(),
+											transactionId);
+
 	/*
 	 * Get the transaction status.
 	 */
-	xidstatus = TransactionIdGetStatus(transactionId, &xidlsn);
+	xidstatus = TransactionIdGetStatus(fxid, &xidlsn);
 
 	/*
 	 * Cache it, but DO NOT cache status for unfinished or sub-committed
@@ -239,7 +243,10 @@ TransactionIdDidAbort(TransactionId transactionId)
 void
 TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids)
 {
-	TransactionIdSetTreeStatus(xid, nxids, xids,
+	FullTransactionId fxid =
+		FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(), xid);
+
+	TransactionIdSetTreeStatus(fxid, nxids, xids,
 							   TRANSACTION_STATUS_COMMITTED,
 							   InvalidXLogRecPtr);
 }
@@ -252,7 +259,10 @@ void
 TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids,
 							 XLogRecPtr lsn)
 {
-	TransactionIdSetTreeStatus(xid, nxids, xids,
+	FullTransactionId fxid =
+		FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(), xid);
+
+	TransactionIdSetTreeStatus(fxid, nxids, xids,
 							   TRANSACTION_STATUS_COMMITTED, lsn);
 }
 
@@ -269,7 +279,10 @@ TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids,
 void
 TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
 {
-	TransactionIdSetTreeStatus(xid, nxids, xids,
+	FullTransactionId fxid =
+		FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(), xid);
+
+	TransactionIdSetTreeStatus(fxid, nxids, xids,
 							   TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
 }
 
@@ -318,6 +331,7 @@ XLogRecPtr
 TransactionIdGetCommitLSN(TransactionId xid)
 {
 	XLogRecPtr	result;
+	FullTransactionId fxid;
 
 	/*
 	 * Currently, all uses of this function are for xids that were just
@@ -332,10 +346,12 @@ TransactionIdGetCommitLSN(TransactionId xid)
 	if (!TransactionIdIsNormal(xid))
 		return InvalidXLogRecPtr;
 
+	fxid = FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(), xid);
+
 	/*
 	 * Get the transaction status.
 	 */
-	(void) TransactionIdGetStatus(xid, &result);
+	(void) TransactionIdGetStatus(fxid, &result);
 
 	return result;
 }
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index e4340b5964..3622eed3b5 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -438,7 +438,6 @@ MarkAsPreparingGuts(GlobalTransaction gxact, FullTransactionId fxid,
 {
 	PGPROC	   *proc;
 	int			i;
-	TransactionId xid = XidFromFullTransactionId(fxid);
 
 	Assert(LWLockHeldByMeInMode(TwoPhaseStateLock, LW_EXCLUSIVE));
 
@@ -458,10 +457,10 @@ MarkAsPreparingGuts(GlobalTransaction gxact, FullTransactionId fxid,
 	{
 		Assert(AmStartupProcess() || !IsPostmasterEnvironment);
 		/* GetLockConflicts() uses this to specify a wait on the XID */
-		proc->vxid.lxid = xid;
+		proc->vxid.lxid = XidFromFullTransactionId(fxid);
 		proc->vxid.procNumber = INVALID_PROC_NUMBER;
 	}
-	proc->xid = xid;
+	proc->xid = fxid;
 	Assert(proc->xmin == InvalidTransactionId);
 	proc->delayChkptFlags = 0;
 	proc->statusFlags = 0;
@@ -778,7 +777,8 @@ pg_prepared_xact(PG_FUNCTION_ARGS)
 		 * Form tuple with appropriate data.
 		 */
 
-		values[0] = TransactionIdGetDatum(proc->xid);
+		values[0] = TransactionIdGetDatum(XidFromFullTransactionId(proc->xid));
+		/* XXX: cosider adding epoch */
 		values[1] = CStringGetTextDatum(gxact->gid);
 		values[2] = TimestampTzGetDatum(gxact->prepared_at);
 		values[3] = ObjectIdGetDatum(gxact->owner);
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 3e08c308d0..be57f2b9e0 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -93,8 +93,8 @@ GetNewTransactionId(bool isSubXact)
 	if (IsBootstrapProcessingMode())
 	{
 		Assert(!isSubXact);
-		MyProc->xid = BootstrapTransactionId;
-		ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+		MyProc->xid = BootstrapFullTransactionId;
+		ProcGlobal->xids[MyProc->pgxactoff] = BootstrapFullTransactionId;
 		return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
 	}
 
@@ -201,7 +201,7 @@ GetNewTransactionId(bool isSubXact)
 	 *
 	 * Extend pg_subtrans and pg_commit_ts too.
 	 */
-	ExtendCLOG(xid);
+	ExtendCLOG(full_xid);
 	ExtendCommitTs(xid);
 	ExtendSUBTRANS(xid);
 
@@ -255,8 +255,8 @@ GetNewTransactionId(bool isSubXact)
 		Assert(!MyProc->subxidStatus.overflowed);
 
 		/* LWLockRelease acts as barrier */
-		MyProc->xid = xid;
-		ProcGlobal->xids[MyProc->pgxactoff] = xid;
+		MyProc->xid = full_xid;
+		ProcGlobal->xids[MyProc->pgxactoff] = full_xid;
 	}
 	else
 	{
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 635679cc1f..169db61aa9 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4643,7 +4643,7 @@ set_indexsafe_procflags(void)
 	 * This should only be called before installing xid or xmin in MyProc;
 	 * otherwise, concurrent processes could see an Xmin that moves backwards.
 	 */
-	Assert(MyProc->xid == InvalidTransactionId &&
+	Assert(FullTransactionIdEquals(MyProc->xid, InvalidFullTransactionId) &&
 		   MyProc->xmin == InvalidTransactionId);
 
 	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03932f45c8..8a837b6cbf 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1962,9 +1962,15 @@ vac_truncate_clog(TransactionId frozenXID,
 	/*
 	 * Truncate CLOG, multixact and CommitTs to the oldest computed value.
 	 */
-	TruncateCLOG(frozenXID, oldestxid_datoid);
-	TruncateCommitTs(frozenXID);
-	TruncateMultiXact(minMulti, minmulti_datoid);
+	{
+		FullTransactionId frozen_fxid =
+			FullTransactionIdFromAllowableAt(ReadNextFullTransactionId(),
+											 frozenXID);
+
+		TruncateCLOG(frozen_fxid, oldestxid_datoid);
+		TruncateCommitTs(frozenXID);
+		TruncateMultiXact(minMulti, minmulti_datoid);
+	}
 
 	/*
 	 * Update the wrap limit for GetNewTransactionId and creation of new
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 40312df2ca..9281be81cd 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -588,7 +588,7 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
 
 	if (TransactionIdIsValid(latestXid))
 	{
-		Assert(TransactionIdIsValid(ProcGlobal->xids[myoff]));
+		Assert(FullTransactionIdIsValid(ProcGlobal->xids[myoff]));
 
 		/* Advance global latestCompletedXid while holding the lock */
 		MaintainLatestCompletedXid(latestXid);
@@ -596,17 +596,17 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
 		/* Same with xactCompletionCount  */
 		TransamVariables->xactCompletionCount++;
 
-		ProcGlobal->xids[myoff] = InvalidTransactionId;
+		ProcGlobal->xids[myoff] = InvalidFullTransactionId;
 		ProcGlobal->subxidStates[myoff].overflowed = false;
 		ProcGlobal->subxidStates[myoff].count = 0;
 	}
 	else
 	{
 		/* Shouldn't be trying to remove a live transaction here */
-		Assert(!TransactionIdIsValid(ProcGlobal->xids[myoff]));
+		Assert(!FullTransactionIdIsValid(ProcGlobal->xids[myoff]));
 	}
 
-	Assert(!TransactionIdIsValid(ProcGlobal->xids[myoff]));
+	Assert(!FullTransactionIdIsValid(ProcGlobal->xids[myoff]));
 	Assert(ProcGlobal->subxidStates[myoff].count == 0);
 	Assert(ProcGlobal->subxidStates[myoff].overflowed == false);
 
@@ -677,7 +677,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 		 * else is taking a snapshot.  See discussion in
 		 * src/backend/access/transam/README.
 		 */
-		Assert(TransactionIdIsValid(proc->xid));
+		Assert(FullTransactionIdIsValid(proc->xid));
 
 		/*
 		 * If we can immediately acquire ProcArrayLock, we clear our own XID
@@ -699,7 +699,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 		 * anyone else's calculation of a snapshot.  We might change their
 		 * estimate of global xmin, but that's OK.
 		 */
-		Assert(!TransactionIdIsValid(proc->xid));
+		Assert(!FullTransactionIdIsValid(proc->xid));
 		Assert(proc->subxidStatus.count == 0);
 		Assert(!proc->subxidStatus.overflowed);
 
@@ -740,11 +740,11 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
 	 * processes' PGPROC entries.
 	 */
 	Assert(LWLockHeldByMeInMode(ProcArrayLock, LW_EXCLUSIVE));
-	Assert(TransactionIdIsValid(ProcGlobal->xids[pgxactoff]));
-	Assert(ProcGlobal->xids[pgxactoff] == proc->xid);
+	Assert(FullTransactionIdIsValid(ProcGlobal->xids[pgxactoff]));
+	Assert(FullTransactionIdEquals(ProcGlobal->xids[pgxactoff], proc->xid));
 
-	ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
-	proc->xid = InvalidTransactionId;
+	ProcGlobal->xids[pgxactoff] = InvalidFullTransactionId;
+	proc->xid = InvalidFullTransactionId;
 	proc->vxid.lxid = InvalidLocalTransactionId;
 	proc->xmin = InvalidTransactionId;
 
@@ -800,7 +800,7 @@ ProcArrayGroupClearXid(PGPROC *proc, TransactionId latestXid)
 	uint32		wakeidx;
 
 	/* We should definitely have an XID to clear. */
-	Assert(TransactionIdIsValid(proc->xid));
+	Assert(FullTransactionIdIsValid(proc->xid));
 
 	/* Add ourselves to the list of processes needing a group XID clear. */
 	proc->procArrayGroupMember = true;
@@ -929,8 +929,8 @@ ProcArrayClearTransaction(PGPROC *proc)
 
 	pgxactoff = proc->pgxactoff;
 
-	ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
-	proc->xid = InvalidTransactionId;
+	ProcGlobal->xids[pgxactoff] = InvalidFullTransactionId;
+	proc->xid = InvalidFullTransactionId;
 
 	proc->vxid.lxid = InvalidLocalTransactionId;
 	proc->xmin = InvalidTransactionId;
@@ -1405,7 +1405,7 @@ bool
 TransactionIdIsInProgress(TransactionId xid)
 {
 	static TransactionId *xids = NULL;
-	static TransactionId *other_xids;
+	static FullTransactionId *other_xids;
 	XidCacheStatus *other_subxidstates;
 	int			nxids = 0;
 	ProcArrayStruct *arrayP = procArray;
@@ -1688,7 +1688,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
 	ProcArrayStruct *arrayP = procArray;
 	TransactionId kaxmin;
 	bool		in_recovery = RecoveryInProgress();
-	TransactionId *other_xids = ProcGlobal->xids;
+	FullTransactionId *other_xids = ProcGlobal->xids;
 
 	/* inferred after ProcArrayLock is released */
 	h->catalog_oldest_nonremovable = InvalidTransactionId;
@@ -1726,8 +1726,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
 		 * definition, can't be any newer changes in the temp table than
 		 * latestCompletedXid.
 		 */
-		if (TransactionIdIsValid(MyProc->xid))
-			h->temp_oldest_nonremovable = MyProc->xid;
+		if (FullTransactionIdIsValid(MyProc->xid))
+			h->temp_oldest_nonremovable = XidFromFullTransactionId(MyProc->xid);
 		else
 			h->temp_oldest_nonremovable = initial;
 	}
@@ -1749,7 +1749,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h)
 		TransactionId xmin;
 
 		/* Fetch xid just once - see GetNewTransactionId */
-		xid = UINT32_ACCESS_ONCE(other_xids[index]);
+		xid = XidFromFullTransactionId(other_xids[index]);
 		xmin = UINT32_ACCESS_ONCE(proc->xmin);
 
 		/*
@@ -2126,7 +2126,7 @@ Snapshot
 GetSnapshotData(Snapshot snapshot)
 {
 	ProcArrayStruct *arrayP = procArray;
-	TransactionId *other_xids = ProcGlobal->xids;
+	FullTransactionId *other_xids = ProcGlobal->xids;
 	TransactionId xmin;
 	TransactionId xmax;
 	int			count = 0;
@@ -2189,8 +2189,8 @@ GetSnapshotData(Snapshot snapshot)
 
 	latest_completed = TransamVariables->latestCompletedXid;
 	mypgxactoff = MyProc->pgxactoff;
-	myxid = other_xids[mypgxactoff];
-	Assert(myxid == MyProc->xid);
+	myxid = XidFromFullTransactionId(other_xids[mypgxactoff]);
+	Assert(myxid == XidFromFullTransactionId(MyProc->xid));
 
 	oldestxid = TransamVariables->oldestXid;
 	curXactCompletionCount = TransamVariables->xactCompletionCount;
@@ -2643,7 +2643,7 @@ GetRunningTransactionData(void)
 	static RunningTransactionsData CurrentRunningXactsData;
 
 	ProcArrayStruct *arrayP = procArray;
-	TransactionId *other_xids = ProcGlobal->xids;
+	FullTransactionId *other_xids = ProcGlobal->xids;
 	RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData;
 	TransactionId latestCompletedXid;
 	TransactionId oldestRunningXid;
@@ -2836,7 +2836,7 @@ TransactionId
 GetOldestActiveTransactionId(bool inCommitOnly, bool allDbs)
 {
 	ProcArrayStruct *arrayP = procArray;
-	TransactionId *other_xids = ProcGlobal->xids;
+	FullTransactionId *other_xids = ProcGlobal->xids;
 	TransactionId oldestRunningXid;
 	int			index;
 
@@ -2959,7 +2959,7 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
 	 */
 	if (!recovery_in_progress)
 	{
-		TransactionId *other_xids = ProcGlobal->xids;
+		FullTransactionId *other_xids = ProcGlobal->xids;
 
 		/*
 		 * Spin over procArray collecting min(ProcGlobal->xids[i])
@@ -2969,7 +2969,7 @@ GetOldestSafeDecodingTransactionId(bool catalogOnly)
 			TransactionId xid;
 
 			/* Fetch xid just once - see GetNewTransactionId */
-			xid = UINT32_ACCESS_ONCE(other_xids[index]);
+			xid = XidFromFullTransactionId(other_xids[index]);
 
 			if (!TransactionIdIsNormal(xid))
 				continue;
@@ -3140,7 +3140,7 @@ ProcNumberGetTransactionIds(ProcNumber procNumber, TransactionId *xid,
 
 	if (proc->pid != 0)
 	{
-		*xid = proc->xid;
+		*xid = XidFromFullTransactionId(proc->xid);
 		*xmin = proc->xmin;
 		*nsubxid = proc->subxidStatus.count;
 		*overflowed = proc->subxidStatus.overflowed;
@@ -3221,7 +3221,7 @@ BackendXidGetPid(TransactionId xid)
 {
 	int			result = 0;
 	ProcArrayStruct *arrayP = procArray;
-	TransactionId *other_xids = ProcGlobal->xids;
+	FullTransactionId *other_xids = ProcGlobal->xids;
 	int			index;
 
 	if (xid == InvalidTransactionId)	/* never match invalid xid */
@@ -3231,7 +3231,7 @@ BackendXidGetPid(TransactionId xid)
 
 	for (index = 0; index < arrayP->numProcs; index++)
 	{
-		if (other_xids[index] == xid)
+		if (XidFromFullTransactionId(other_xids[index]) == xid)
 		{
 			int			pgprocno = arrayP->pgprocnos[index];
 			PGPROC	   *proc = &allProcs[pgprocno];
@@ -3612,7 +3612,7 @@ MinimumActiveBackends(int min)
 			continue;			/* do not count deleted entries */
 		if (proc == MyProc)
 			continue;			/* do not count myself */
-		if (proc->xid == InvalidTransactionId)
+		if (FullTransactionIdEquals(proc->xid, InvalidFullTransactionId))
 			continue;			/* do not count if no XID assigned */
 		if (proc->pid == 0)
 			continue;			/* do not count prepared xacts */
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index d930c66cdb..9f7b8526cd 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -4212,7 +4212,7 @@ GetRunningTransactionLocks(int *nlocks)
 		{
 			PGPROC	   *proc = proclock->tag.myProc;
 			LOCK	   *lock = proclock->tag.myLock;
-			TransactionId xid = proc->xid;
+			TransactionId xid = XidFromFullTransactionId(proc->xid);
 
 			/*
 			 * Don't record locks for transactions if we know they have
@@ -4832,7 +4832,7 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
 	 * so we won't save an XID of a different VXID.  It doesn't matter whether
 	 * we save this before or after setting up the primary lock table entry.
 	 */
-	xid = proc->xid;
+	xid = XidFromFullTransactionId(proc->xid);
 
 	/* Done with proc->fpLockBits */
 	LWLockRelease(&proc->fpInfoLock);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e2f34075d3..55e7130267 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -247,7 +247,7 @@ InitProcGlobal(void)
 	 * XXX: It might make sense to increase padding for these arrays, given
 	 * how hotly they are accessed.
 	 */
-	ProcGlobal->xids = (TransactionId *) ptr;
+	ProcGlobal->xids = (FullTransactionId *) ptr;
 	ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->xids));
 
 	ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
@@ -461,7 +461,7 @@ InitProcess(void)
 	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
 	MyProc->fpVXIDLock = false;
 	MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
-	MyProc->xid = InvalidTransactionId;
+	MyProc->xid = InvalidFullTransactionId;
 	MyProc->xmin = InvalidTransactionId;
 	MyProc->pid = MyProcPid;
 	MyProc->vxid.procNumber = MyProcNumber;
@@ -512,7 +512,7 @@ InitProcess(void)
 
 	/* Initialize fields for group transaction status update. */
 	MyProc->clogGroupMember = false;
-	MyProc->clogGroupMemberXid = InvalidTransactionId;
+	MyProc->clogGroupMemberXid = InvalidFullTransactionId;
 	MyProc->clogGroupMemberXidStatus = TRANSACTION_STATUS_IN_PROGRESS;
 	MyProc->clogGroupMemberPage = -1;
 	MyProc->clogGroupMemberLsn = InvalidXLogRecPtr;
@@ -663,7 +663,7 @@ InitAuxiliaryProcess(void)
 	MyProc->waitStatus = PROC_WAIT_STATUS_OK;
 	MyProc->fpVXIDLock = false;
 	MyProc->fpLocalTransactionId = InvalidLocalTransactionId;
-	MyProc->xid = InvalidTransactionId;
+	MyProc->xid = InvalidFullTransactionId;
 	MyProc->xmin = InvalidTransactionId;
 	MyProc->vxid.procNumber = INVALID_PROC_NUMBER;
 	MyProc->vxid.lxid = InvalidLocalTransactionId;
diff --git a/src/bin/pg_verifybackup/t/003_corruption.pl b/src/bin/pg_verifybackup/t/003_corruption.pl
index b1d65b8aa0..4677e9584c 100644
--- a/src/bin/pg_verifybackup/t/003_corruption.pl
+++ b/src/bin/pg_verifybackup/t/003_corruption.pl
@@ -246,7 +246,7 @@ sub mutilate_extra_tablespace_file
 sub mutilate_missing_file
 {
 	my ($backup_path) = @_;
-	my $pathname = "$backup_path/pg_xact/0000";
+	my $pathname = "$backup_path/pg_xact/000000000000000";
 	unlink($pathname) || die "$pathname: $!";
 	return;
 }
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index a1cfed5f43..034d4e0973 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -11,6 +11,7 @@
 #ifndef CLOG_H
 #define CLOG_H
 
+#include "access/transam.h"
 #include "access/xlogreader.h"
 #include "storage/sync.h"
 #include "lib/stringinfo.h"
@@ -32,13 +33,15 @@ typedef int XidStatus;
 typedef struct xl_clog_truncate
 {
 	int64		pageno;
-	TransactionId oldestXact;
+	FullTransactionId oldestXact;
 	Oid			oldestXactDb;
 } xl_clog_truncate;
 
-extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
-									   TransactionId *subxids, XidStatus status, XLogRecPtr lsn);
-extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn);
+extern void TransactionIdSetTreeStatus(FullTransactionId fxid, int nsubxids,
+									   TransactionId *subxids, XidStatus status,
+									   XLogRecPtr lsn);
+extern XidStatus TransactionIdGetStatus(FullTransactionId fxid,
+										XLogRecPtr *lsn);
 
 extern Size CLOGShmemSize(void);
 extern void CLOGShmemInit(void);
@@ -46,8 +49,8 @@ extern void BootStrapCLOG(void);
 extern void StartupCLOG(void);
 extern void TrimCLOG(void);
 extern void CheckPointCLOG(void);
-extern void ExtendCLOG(TransactionId newestXact);
-extern void TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid);
+extern void ExtendCLOG(FullTransactionId newestXact);
+extern void TruncateCLOG(FullTransactionId oldestXact, Oid oldestxid_datoid);
 
 extern int	clogsyncfiletag(const FileTag *ftag, char *path);
 
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index f08362898c..7559076931 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -54,6 +54,7 @@
 #define FullTransactionIdFollowsOrEquals(a, b) ((a).value >= (b).value)
 #define FullTransactionIdIsValid(x)		TransactionIdIsValid(XidFromFullTransactionId(x))
 #define InvalidFullTransactionId		FullTransactionIdFromEpochAndXid(0, InvalidTransactionId)
+#define BootstrapFullTransactionId		FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId)
 #define FirstNormalFullTransactionId	FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId)
 #define FullTransactionIdIsNormal(x)	FullTransactionIdFollowsOrEquals(x, FirstNormalFullTransactionId)
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index a8d2e7db1a..f43d0a7114 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -232,7 +232,7 @@ typedef struct PGPROC
 									 * InvalidLocalTransactionId */
 	}			vxid;
 
-	TransactionId xid;			/* id of top-level transaction currently being
+	FullTransactionId xid;		/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
 								 * mirrored in ProcGlobal->xids[pgxactoff] */
@@ -361,7 +361,7 @@ typedef struct PGPROC
 
 	bool		clogGroupMember;	/* true, if member of clog group */
 	pg_atomic_uint32 clogGroupNext; /* next clog group member */
-	TransactionId clogGroupMemberXid;	/* transaction id of clog group member */
+	FullTransactionId clogGroupMemberXid;	/* transaction id of clog group member */
 	XidStatus	clogGroupMemberXidStatus;	/* transaction status of clog
 											 * group member */
 	int64		clogGroupMemberPage;	/* clog page corresponding to
@@ -447,7 +447,7 @@ typedef struct PROC_HDR
 	PGPROC	   *allProcs;
 
 	/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
-	TransactionId *xids;
+	FullTransactionId *xids;
 
 	/*
 	 * Array mirroring PGPROC.subxidStatus for each PGPROC currently in the
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index e0df1a2342..6986c93ea3 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -59,6 +59,18 @@ $node_primary->init(has_archiving => 1, allows_streaming => 1);
 # of recovery_target_xid parsing.
 system_or_bail('pg_resetwal', '--epoch' => '1', $node_primary->data_dir);
 
+# pg_xact is not wraparound any more, create an empty pg_xact segment.
+my $out = (run_command([ 'pg_resetwal', '--dry-run',
+			$node_primary->data_dir ]))[0];
+$out =~ /^Database block size: *(\d+)$/m or die;
+my $blcksz = $1;
+
+open my $fh, '>', $node_primary->data_dir . "/pg_xact/000000000001000"
+  or die "$0: could not open file $!\n";
+print $fh "\0" x $blcksz
+  or die "$0: could not write file $!\n";
+close $fh;
+
 # Start it
 $node_primary->start;
 
-- 
2.43.0

