From c9430477f9323903f94359461793df61c91c6a1c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 11 Sep 2026 15:55:34 -0400
Subject: [PATCH v1] Avoid setting pd_prune_xid when inserting frozen tuples

378a216187a set pd_prune_xid on inserts so that on-access pruning could
later set the pages all-visible. heap_multi_insert() skipped this only
when setting the page all-frozen, but it can insert frozen tuples even
when not setting the page all-frozen. Inserting frozen tuples should not
set pd_prune_xid as they introduce no work for on-access pruning. Fix by
only setting pd_prune_xid when inserting non-frozen tuples.
---
 src/backend/access/heap/heapam.c      |  6 +++---
 src/backend/access/heap/heapam_xlog.c | 21 ++++++++++++++++-----
 2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 72d6541734c..1afd5dc9ca0 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2493,10 +2493,10 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
 
 		/*
 		 * Set pd_prune_xid. See heap_insert() for more on why we do this when
-		 * inserting tuples. This only makes sense if we aren't already
-		 * setting the page frozen in the VM and we're not in bootstrap mode.
+		 * inserting tuples. This only makes sense if the tuples aren't frozen
+		 * and we're not in bootstrap mode.
 		 */
-		if (!all_frozen_set && TransactionIdIsNormal(xid))
+		if (TransactionIdIsNormal(xid) && !(options & HEAP_INSERT_FROZEN))
 			PageSetPrunable(page, xid);
 
 		MarkBufferDirty(buffer);
diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c
index 7a7bc7ea740..88f99bb9c5f 100644
--- a/src/backend/access/heap/heapam_xlog.c
+++ b/src/backend/access/heap/heapam_xlog.c
@@ -583,6 +583,7 @@ heap_xlog_multi_insert(XLogReaderState *record)
 		char	   *tupdata;
 		char	   *endptr;
 		Size		len;
+		bool		inserted_tuples_frozen = false;
 
 		/* Tuples are stored as block data */
 		tupdata = XLogRecGetBlockData(record, HEAP_MULTI_INSERT_BLKREF_HEAP,
@@ -630,6 +631,10 @@ heap_xlog_multi_insert(XLogReaderState *record)
 			ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
 			ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
 
+			/* If one inserted tuple was frozen, they all were */
+			if (i == 0)
+				inserted_tuples_frozen = HeapTupleHeaderXminFrozen(htup);
+
 			offnum = PageAddItem(page, htup, newlen, offnum, true, true);
 			if (offnum == InvalidOffsetNumber)
 				elog(PANIC, "failed to add tuple");
@@ -645,17 +650,23 @@ heap_xlog_multi_insert(XLogReaderState *record)
 			PageClearAllVisible(page);
 
 		/*
-		 * XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible. If
-		 * we are not setting the page frozen, then set the page's prunable
-		 * hint so that we trigger on-access pruning later which may set the
-		 * page all-visible in the VM.
+		 * XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible, so
+		 * set PD_ALL_VISIBLE and clear pd_prune_xid.
+		 *
+		 * If the page isn't being set all-frozen and we aren't inserting
+		 * frozen tuples, set pd_prune_xid so that the page gets on-access
+		 * pruned.
+		 *
+		 * Frozen tuples may be added to an already all-frozen page or to a
+		 * page containing non-frozen tuples, but they introduce nothing new
+		 * for on-access pruning, so preserve the existing hint.
 		 */
 		if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
 		{
 			PageSetAllVisible(page);
 			PageClearPrunable(page);
 		}
-		else
+		else if (!inserted_tuples_frozen)
 			PageSetPrunable(page, XLogRecGetXid(record));
 
 		MarkBufferDirty(buffer);
-- 
2.43.0

