From 74411532903ec17cc9c29e17786fdd35ba1b0eac Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Tue, 19 Mar 2024 20:48:28 -0400
Subject: [PATCH v4 19/19] Streamline XLOG_HEAP2_PRUNE record and use for
 freeze and vacuum

xl_heap_prune struct for the XLOG_HEAP2_PRUNE record type had members
for counting the number of freeze plans and number of redirected, dead,
and newly unused line pointers. However, only some of those are used in
many XLOG_HEAP2_PRUNE records.

Put all of those members in the XLOG buffer data and use flags to
indicate which are present. This makes it feasible to use the
XLOG_HEAP2_PRUNE record smaller than it was and smaller than the
previously used XLOG_HEAP2_VACUUM and XLOG_HEAP2_FREEZE records.

The snapshot conflict horizon is not used for vacuum but is for the
other record types. It must go in the main data (not per buffer data) so
that it can be used even if the record contains an FPI.

The new prune record is composed of sub-records for each type of
modification freezing tuples and setting line pointers unused, redirect,
or dead.
---
 src/backend/access/heap/heapam.c       |  33 +++----
 src/backend/access/heap/pruneheap.c    |  98 ++++++++++++++-----
 src/backend/access/heap/vacuumlazy.c   |  24 +++--
 src/backend/access/rmgrdesc/heapdesc.c |  27 ++---
 src/backend/access/rmgrdesc/xactdesc.c |  68 +++++++++++++
 src/include/access/heapam_xlog.h       | 130 +++++++++++++++----------
 src/tools/pgindent/typedefs.list       |   3 +
 7 files changed, 273 insertions(+), 110 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 2ef7decdd05..adc259fdca7 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -8728,15 +8728,15 @@ heap_xlog_prune(XLogReaderState *record)
 	 * heap_page_prune_and_freeze(), heap_page_prune_execute() will call
 	 * PageRepairFragementation() which expects a full cleanup lock.
 	 */
-	get_cleanup_lock = xlrec->nredirected > 0 ||
-		xlrec->ndead > 0 ||
-		(xlrec->nunused > 0 && !lp_truncate_only);
+	get_cleanup_lock = xlrec->flags & XLHP_HAS_REDIRECTIONS ||
+		xlrec->flags & XLHP_HAS_DEAD_ITEMS ||
+		(xlrec->flags & XLHP_HAS_NOW_UNUSED_ITEMS && !lp_truncate_only);
 
 	if (lp_truncate_only)
 	{
-		Assert(xlrec->nredirected == 0);
-		Assert(xlrec->ndead == 0);
-		Assert(xlrec->nunused > 0);
+		Assert(!(xlrec->flags & XLHP_HAS_REDIRECTIONS));
+		Assert(!(xlrec->flags & XLHP_HAS_DEAD_ITEMS));
+		Assert(xlrec->flags & XLHP_HAS_NOW_UNUSED_ITEMS);
 	}
 
 	/*
@@ -8745,9 +8745,13 @@ heap_xlog_prune(XLogReaderState *record)
 	 * tuples are still visible or which consider the frozen xids as running.
 	 */
 	if (xlrec->flags & XLHP_HAS_CONFLICT_HORIZON && InHotStandby)
-		ResolveRecoveryConflictWithSnapshot(xlrec->snapshotConflictHorizon,
-											xlrec->isCatalogRel,
+	{
+		xlhp_conflict_horizon *horizon = (xlhp_conflict_horizon *) (xlrec + SizeOfHeapPrune);
+
+		ResolveRecoveryConflictWithSnapshot(horizon->xid,
+											xlrec->flags & XLHP_IS_CATALOG_REL,
 											rlocator);
+	}
 
 	/*
 	 * If we have a full-page image, restore it and we're done.
@@ -8770,16 +8774,11 @@ heap_xlog_prune(XLogReaderState *record)
 		OffsetNumber *frz_offsets = NULL;
 		int			curoff = 0;
 
-		nplans = xlrec->nplans;
-		nredirected = xlrec->nredirected;
-		ndead = xlrec->ndead;
-		nunused = xlrec->nunused;
+		char	   *cursor = XLogRecGetBlockData(record, 0, &datalen);
 
-		plans = (xl_heap_freeze_plan *) XLogRecGetBlockData(record, 0, &datalen);
-		redirected = (OffsetNumber *) &plans[nplans];
-		nowdead = redirected + (nredirected * 2);
-		nowunused = nowdead + ndead;
-		frz_offsets = nowunused + nunused;
+		heap_xlog_deserialize_prune_and_freeze(cursor, xlrec->flags,
+											   &nredirected, &redirected, &ndead, &nowdead,
+											   &nunused, &nowunused, &nplans, &plans, &frz_offsets);
 
 		/* Update all line pointers per the record, and repair fragmentation */
 		if (nredirected > 0 || ndead > 0 || nunused > 0)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 5decb1127d0..26147f63c4c 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -741,19 +741,22 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 						  PruneState *prstate, PruneFreezeResult *presult)
 {
 	xl_heap_prune xlrec;
+	xlhp_conflict_horizon horizon;
 	XLogRecPtr	recptr;
+	xlhp_freeze freeze;
+	xlhp_prune_items redirect,
+				dead,
+				unused;
 
+	int			nplans = 0;
 	xl_heap_freeze_plan plans[MaxHeapTuplesPerPage];
-	OffsetNumber offsets[MaxHeapTuplesPerPage];
-	bool		do_freeze = presult->nfrozen > 0;
+	OffsetNumber frz_offsets[MaxHeapTuplesPerPage];
+	bool		do_freeze = (presult->nfrozen > 0);
 
 	xlrec.flags = 0;
 
-	xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(relation);
-	xlrec.nredirected = prstate->nredirected;
-	xlrec.ndead = prstate->ndead;
-	xlrec.nunused = prstate->nunused;
-	xlrec.nplans = 0;
+	if (RelationIsAccessibleInLogicalDecoding(relation))
+		xlrec.flags |= XLHP_IS_CATALOG_REL;
 
 	xlrec.flags |= XLHP_HAS_CONFLICT_HORIZON;
 
@@ -767,22 +770,25 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 	 * youngest tuple this record will freeze will conflict.
 	 */
 	if (do_freeze)
-		xlrec.snapshotConflictHorizon = Max(prstate->snapshotConflictHorizon,
-											presult->frz_conflict_horizon);
+		horizon.xid = Max(prstate->snapshotConflictHorizon,
+						  presult->frz_conflict_horizon);
 	else
-		xlrec.snapshotConflictHorizon = prstate->snapshotConflictHorizon;
+		horizon.xid = prstate->snapshotConflictHorizon;
 
 	/*
 	 * Prepare deduplicated representation for use in WAL record Destructively
 	 * sorts tuples array in-place.
 	 */
 	if (do_freeze)
-		xlrec.nplans = heap_log_freeze_plan(prstate->frozen,
-											presult->nfrozen, plans, offsets);
-
+		nplans = heap_log_freeze_plan(prstate->frozen,
+									  presult->nfrozen, plans,
+									  frz_offsets);
 	XLogBeginInsert();
 	XLogRegisterData((char *) &xlrec, SizeOfHeapPrune);
 
+	xlrec.flags |= XLHP_HAS_CONFLICT_HORIZON;
+	XLogRegisterData((char *) &horizon, SizeOfSnapshotConflictHorizon);
+
 	XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
 
 	/*
@@ -790,25 +796,73 @@ log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 	 * that they are.  When XLogInsert stores the whole buffer, the offset
 	 * arrays need not be stored too.
 	 */
-	if (xlrec.nplans > 0)
+	if (nplans > 0)
+	{
+		xlrec.flags |= XLHP_HAS_FREEZE_PLANS;
+
+		freeze = (xlhp_freeze)
+		{
+			.nplans = nplans
+		};
+
+		XLogRegisterBufData(0, (char *) &freeze, offsetof(xlhp_freeze, plans));
+
 		XLogRegisterBufData(0, (char *) plans,
-							xlrec.nplans * sizeof(xl_heap_freeze_plan));
+							sizeof(xl_heap_freeze_plan) * freeze.nplans);
+	}
+
 
 	if (prstate->nredirected > 0)
+	{
+		xlrec.flags |= XLHP_HAS_REDIRECTIONS;
+
+		redirect = (xlhp_prune_items)
+		{
+			.ntargets = prstate->nredirected
+		};
+
+		XLogRegisterBufData(0, (char *) &redirect,
+							offsetof(xlhp_prune_items, data));
+
 		XLogRegisterBufData(0, (char *) prstate->redirected,
-							prstate->nredirected *
-							sizeof(OffsetNumber) * 2);
+							sizeof(OffsetNumber[2]) * prstate->nredirected);
+	}
 
 	if (prstate->ndead > 0)
+	{
+		xlrec.flags |= XLHP_HAS_DEAD_ITEMS;
+
+		dead = (xlhp_prune_items)
+		{
+			.ntargets = prstate->ndead
+		};
+
+		XLogRegisterBufData(0, (char *) &dead,
+							offsetof(xlhp_prune_items, data));
+
 		XLogRegisterBufData(0, (char *) prstate->nowdead,
-							prstate->ndead * sizeof(OffsetNumber));
+							sizeof(OffsetNumber) * dead.ntargets);
+	}
 
 	if (prstate->nunused > 0)
+	{
+		xlrec.flags |= XLHP_HAS_NOW_UNUSED_ITEMS;
+
+		unused = (xlhp_prune_items)
+		{
+			.ntargets = prstate->nunused
+		};
+
+		XLogRegisterBufData(0, (char *) &unused,
+							offsetof(xlhp_prune_items, data));
+
 		XLogRegisterBufData(0, (char *) prstate->nowunused,
-							prstate->nunused * sizeof(OffsetNumber));
-	if (xlrec.nplans > 0)
-		XLogRegisterBufData(0, (char *) offsets,
-							presult->nfrozen * sizeof(OffsetNumber));
+							sizeof(OffsetNumber) * unused.ntargets);
+	}
+
+	if (nplans > 0)
+		XLogRegisterBufData(0, (char *) frz_offsets,
+							sizeof(OffsetNumber) * presult->nfrozen);
 
 	recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PRUNE);
 
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 295846b854f..1e79cbbb107 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2253,21 +2253,27 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
 	if (RelationNeedsWAL(vacrel->rel))
 	{
 		xl_heap_prune xlrec;
+		xlhp_prune_items unused_rec;
 		XLogRecPtr	recptr;
 
-		xlrec.flags = XLHP_LP_TRUNCATE_ONLY;
-		xlrec.snapshotConflictHorizon = InvalidTransactionId;
-		xlrec.nplans = 0;
-		xlrec.nredirected = 0;
-		xlrec.ndead = 0;
-		xlrec.nunused = nunused;
-		xlrec.isCatalogRel = RelationIsAccessibleInLogicalDecoding(vacrel->rel);
+		xlrec.flags = XLHP_HAS_NOW_UNUSED_ITEMS;
+
+		xlrec.flags |= XLHP_LP_TRUNCATE_ONLY;
+
+		unused_rec = (xlhp_prune_items)
+		{
+			.ntargets = nunused
+		};
 
 		XLogBeginInsert();
 		XLogRegisterData((char *) &xlrec, SizeOfHeapPrune);
-
 		XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
-		XLogRegisterBufData(0, (char *) unused, nunused * sizeof(OffsetNumber));
+
+		XLogRegisterBufData(0, (char *) &unused_rec,
+							offsetof(xlhp_prune_items, data));
+
+		XLogRegisterBufData(0, (char *) unused,
+							sizeof(OffsetNumber) * unused_rec.ntargets);
 
 		recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PRUNE);
 
diff --git a/src/backend/access/rmgrdesc/heapdesc.c b/src/backend/access/rmgrdesc/heapdesc.c
index 1fe5c78031f..f542bcb94b6 100644
--- a/src/backend/access/rmgrdesc/heapdesc.c
+++ b/src/backend/access/rmgrdesc/heapdesc.c
@@ -179,9 +179,16 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 	{
 		xl_heap_prune *xlrec = (xl_heap_prune *) rec;
 
-		appendStringInfo(buf, "snapshotConflictHorizon: %u, isCatalogRel: %c",
-						 xlrec->snapshotConflictHorizon,
-						 xlrec->isCatalogRel ? 'T' : 'F');
+		if (xlrec->flags & XLHP_HAS_CONFLICT_HORIZON)
+		{
+			xlhp_conflict_horizon *horizon = (xlhp_conflict_horizon *) (xlrec + SizeOfHeapPrune);
+
+			appendStringInfo(buf, "snapshotConflictHorizon: %u",
+							 horizon->xid);
+		}
+
+		appendStringInfo(buf, ", isCatalogRel: %c",
+						 xlrec->flags & XLHP_IS_CATALOG_REL ? 'T' : 'F');
 
 		if (XLogRecHasBlockData(record, 0))
 		{
@@ -196,16 +203,12 @@ heap2_desc(StringInfo buf, XLogReaderState *record)
 			xl_heap_freeze_plan *plans = NULL;
 			OffsetNumber *frz_offsets;
 
-			nplans = xlrec->nplans;
-			nredirected = xlrec->nredirected;
-			ndead = xlrec->ndead;
-			nunused = xlrec->nunused;
+			char	   *cursor = XLogRecGetBlockData(record, 0, &datalen);
+
+			heap_xlog_deserialize_prune_and_freeze(cursor, xlrec->flags,
+												   &nredirected, &redirected, &ndead, &nowdead,
+												   &nunused, &nowunused, &nplans, &plans, &frz_offsets);
 
-			plans = (xl_heap_freeze_plan *) XLogRecGetBlockData(record, 0, &datalen);
-			redirected = (OffsetNumber *) &plans[nplans];
-			nowdead = redirected + (nredirected * 2);
-			nowunused = nowdead + ndead;
-			frz_offsets = nowunused + nunused;
 
 			appendStringInfo(buf, ", nredirected: %u, ndead: %u, nunused: %u, nplans: %u,",
 							 nredirected,
diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c
index 41b842d80ec..e120805e5e0 100644
--- a/src/backend/access/rmgrdesc/xactdesc.c
+++ b/src/backend/access/rmgrdesc/xactdesc.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "replication/origin.h"
@@ -21,6 +22,73 @@
 #include "storage/standbydefs.h"
 #include "utils/timestamp.h"
 
+/*
+ * Given a MAXALIGNed buffer returned by XLogRecGetBlockData() and pointed to
+ * by cursor and any xl_heap_prune flags, deserialize the arrays of
+ * OffsetNumbers contained in an xl_heap_prune record. This is in this file so
+ * it can be shared between heap2_redo and heap2_desc code, the latter of which
+ * is used in frontend code.
+ */
+void
+heap_xlog_deserialize_prune_and_freeze(char *cursor, uint8 flags,
+									   int *nredirected, OffsetNumber **redirected,
+									   int *ndead, OffsetNumber **nowdead,
+									   int *nunused, OffsetNumber **nowunused,
+									   int *nplans, xl_heap_freeze_plan **plans,
+									   OffsetNumber **frz_offsets)
+{
+	if (flags & XLHP_HAS_FREEZE_PLANS)
+	{
+		xlhp_freeze *freeze = (xlhp_freeze *) cursor;
+
+		*nplans = freeze->nplans;
+		Assert(*nplans > 0);
+		*plans = freeze->plans;
+
+		cursor += offsetof(xlhp_freeze, plans);
+		cursor += sizeof(xl_heap_freeze_plan) * freeze->nplans;
+	}
+
+	if (flags & XLHP_HAS_REDIRECTIONS)
+	{
+		xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
+
+		*nredirected = subrecord->ntargets;
+		Assert(nredirected > 0);
+		*redirected = &subrecord->data[0];
+
+		cursor += offsetof(xlhp_prune_items, data);
+		cursor += sizeof(OffsetNumber[2]) * *nredirected;
+	}
+
+	if (flags & XLHP_HAS_DEAD_ITEMS)
+	{
+		xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
+
+		*ndead = subrecord->ntargets;
+		Assert(ndead > 0);
+		*nowdead = subrecord->data;
+
+		cursor += offsetof(xlhp_prune_items, data);
+		cursor += sizeof(OffsetNumber) * *ndead;
+	}
+
+	if (flags & XLHP_HAS_NOW_UNUSED_ITEMS)
+	{
+		xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
+
+		*nunused = subrecord->ntargets;
+		Assert(nunused > 0);
+		*nowunused = subrecord->data;
+
+		cursor += offsetof(xlhp_prune_items, data);
+		cursor += sizeof(OffsetNumber) * *nunused;
+	}
+
+	if (nplans > 0)
+		*frz_offsets = (OffsetNumber *) cursor;
+}
+
 /*
  * Parse the WAL format of an xact commit and abort records into an easier to
  * understand format.
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 2393540cf68..dfeb703d136 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -224,9 +224,64 @@ typedef struct xl_heap_update
 
 #define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_offnum) + sizeof(OffsetNumber))
 
+/*
+ * This is what we need to know about page pruning and freezing, both during
+ * VACUUM and during opportunistic pruning.
+ *
+ * If XLPH_HAS_REDIRECTIONS, XLHP_HAS_DEAD_ITEMS, or XLHP_HAS_NOW_UNUSED is set,
+ * acquires a full cleanup lock. Otherwise an ordinary exclusive lock is
+ * enough. This can happen if freezing was the only modification to the page.
+ *
+ * The data for block reference 0 contains "sub-records" depending on which
+ * of the XLHP_HAS_* flags are set. See xlhp_* struct definitions below.
+ * The layout is in the same order as the XLHP_* flags.
+ *
+ * OFFSET NUMBERS are in the block reference 0
+ *
+ * If only unused item offsets are included because the record is constructed
+ * during vacuum's second pass (marking LP_DEAD items LP_UNUSED) then only an
+ * ordinary exclusive lock is required to replay.
+ */
+typedef struct xl_heap_prune
+{
+	uint8		flags;
+} xl_heap_prune;
+
+/* to handle recovery conflict during logical decoding on standby */
+#define		XLHP_IS_CATALOG_REL			(1 << 1)
+
+/*
+ * During vacuum's second pass which sets LP_DEAD items LP_UNUSED, we will only
+ * truncate the line pointer array, not call PageRepairFragmentation. We need
+ * this flag to differentiate what kind of lock (exclusive or cleanup) to take
+ * on the buffer and whether to call PageTruncateLinePointerArray() or
+ * PageRepairFragementation().
+ */
+#define		XLHP_LP_TRUNCATE_ONLY       (1 << 2)
+
+/*
+ * Vacuum's first pass and on-access pruning may need to include a snapshot
+ * conflict horizon.
+ */
+#define		XLHP_HAS_CONFLICT_HORIZON   (1 << 3)
+#define		XLHP_HAS_FREEZE_PLANS		(1 << 4)
+#define		XLHP_HAS_REDIRECTIONS		(1 << 5)
+#define		XLHP_HAS_DEAD_ITEMS	        (1 << 6)
+#define		XLHP_HAS_NOW_UNUSED_ITEMS   (1 << 7)
+
+#define SizeOfHeapPrune (offsetof(xl_heap_prune, flags) + sizeof(uint8))
+
+typedef struct xlhp_conflict_horizon
+{
+	TransactionId xid;
+} xlhp_conflict_horizon;
+
+#define SizeOfSnapshotConflictHorizon (offsetof(xlhp_conflict_horizon, xid) + sizeof(uint32))
+
 /*
  * This struct represents a 'freeze plan', which describes how to freeze a
- * group of one or more heap tuples (appears in xl_heap_prune record)
+ * group of one or more heap tuples (appears in xl_heap_prune's xlhp_freeze
+ * record)
  */
 /* 0x01 was XLH_FREEZE_XMIN */
 #define		XLH_FREEZE_XVAC		0x02
@@ -246,64 +301,32 @@ typedef struct xl_heap_freeze_plan
 /*
  * As of Postgres 17, XLOG_HEAP2_PRUNE records replace
  * XLOG_HEAP2_FREEZE_PAGE records.
- */
-
-/*
- * This is what we need to know about page pruning (both during VACUUM and
- * during opportunistic pruning)
  *
- * The array of OffsetNumbers following the fixed part of the record contains:
- *	* for each freeze plan: the freeze plan
- *	* for each redirected item: the item offset, then the offset redirected to
- *	* for each now-dead item: the item offset
- *	* for each now-unused item: the item offset
- *	* for each tuple frozen by the freeze plans: the offset of the item corresponding to that tuple
- * The total number of OffsetNumbers is therefore
- * (2*nredirected) + ndead + nunused + (sum[plan.ntuples for plan in plans])
+ * This is what we need to know about a block being frozen during vacuum
  *
- * Acquires a full cleanup lock if heap_page_prune_execute() must be called
+ * Backup block 0's data contains an array of xl_heap_freeze_plan structs
+ * (with nplans elements), followed by one or more page offset number arrays.
+ * Each such page offset number array corresponds to a single freeze plan
+ * (REDO routine freezes corresponding heap tuples using freeze plan).
  */
-typedef struct xl_heap_prune
+typedef struct xlhp_freeze
 {
-	uint8		flags;
-	TransactionId snapshotConflictHorizon;
 	uint16		nplans;
-	uint16		nredirected;
-	uint16		ndead;
-	uint16		nunused;
-	bool		isCatalogRel;	/* to handle recovery conflict during logical
-								 * decoding on standby */
-	/*--------------------------------------------------------------------
-	 * OFFSET NUMBERS and freeze plans are in the block reference 0 in the
-	 * following order:
-	 *
-	 *		* xl_heap_freeze_plan plans[nplans];
-	 * 		* OffsetNumber redirected[2 * nredirected];
-	 * 		* OffsetNumber nowdead[ndead];
-	 *		* OffsetNumber nowunused[nunused];
-	 * 		* OffsetNumber frz_offsets[...];
-	 *--------------------------------------------------------------------
-	 */
-} xl_heap_prune;
-
-#define SizeOfHeapPrune (offsetof(xl_heap_prune, isCatalogRel) + sizeof(bool))
-
-/* Flags for xl_heap_prune */
+	xl_heap_freeze_plan plans[FLEXIBLE_ARRAY_MEMBER];
+} xlhp_freeze;
 
 /*
- * During vacuum's second pass which sets LP_DEAD items LP_UNUSED, we will only
- * truncate the line pointer array, not call PageRepairFragmentation. We need
- * this flag to differentiate what kind of lock (exclusive or cleanup) to take
- * on the buffer and whether to call PageTruncateLinePointerArray() or
- * PageRepairFragementation().
+ * Sub-record type contained in block reference 0 of a prune record if
+ * XLHP_HAS_REDIRECTIONS/XLHP_HAS_DEAD_ITEMS/XLHP_HAS_NOW_UNUSED_ITEMS is set.
+ * Note that in the XLHP_HAS_REDIRECTIONS variant, there are actually 2 *
+ * length number of OffsetNumbers in the data.
  */
-#define		XLHP_LP_TRUNCATE_ONLY       (1 << 1)
+typedef struct xlhp_prune_items
+{
+	uint16		ntargets;
+	OffsetNumber data[FLEXIBLE_ARRAY_MEMBER];
+} xlhp_prune_items;
 
-/*
- * Vacuum's first pass and on-access pruning may need to include a snapshot
- * conflict horizon.
- */
-#define		XLHP_HAS_CONFLICT_HORIZON   (1 << 2)
 
 /* flags for infobits_set */
 #define XLHL_XMAX_IS_MULTI		0x01
@@ -416,4 +439,11 @@ extern XLogRecPtr log_heap_visible(Relation rel, Buffer heap_buffer,
 								   TransactionId snapshotConflictHorizon,
 								   uint8 vmflags);
 
+extern void heap_xlog_deserialize_prune_and_freeze(char *cursor, uint8 flags,
+												   int *nredirected, OffsetNumber **redirected,
+												   int *ndead, OffsetNumber **nowdead,
+												   int *nunused, OffsetNumber **nowunused,
+												   int *nplans, xl_heap_freeze_plan **plans,
+												   OffsetNumber **frz_offsets);
+
 #endif							/* HEAPAM_XLOG_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b2ddc1e2549..40fb694c836 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4007,6 +4007,9 @@ xl_xact_stats_items
 xl_xact_subxacts
 xl_xact_twophase
 xl_xact_xinfo
+xlhp_conflict_horizon
+xlhp_freeze
+xlhp_prune_items
 xmlBuffer
 xmlBufferPtr
 xmlChar
-- 
2.40.1

