From 77a8d20e1c332444d1bde1a41682186713e51e5f Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 18 Mar 2024 20:12:18 -0400
Subject: [PATCH v4 15/19] Set hastup in heap_page_prune

lazy_scan_prune() loops through the line pointers and tuple visibility
information for each tuple on a page, setting hastup to true if there
are any LP_REDIRECT line pointers or tuples with storage which will not
be removed. We want to remove this extra loop from lazy_scan_prune(),
and we know about non-removable tuples during heap_page_prune() anyway.
Set hastup when recording LP_REDIRECT line pointers in
heap_prune_chain() and when LP_NORMAL line pointers refer to tuples
whose visibility status is not HEAPTUPLE_DEAD.
---
 src/backend/access/heap/pruneheap.c  | 64 ++++++++++++++++++----------
 src/backend/access/heap/vacuumlazy.c | 24 +----------
 src/include/access/heapam.h          |  2 +
 3 files changed, 45 insertions(+), 45 deletions(-)

diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 135fe2dba3e..d183912a402 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -71,7 +71,8 @@ static int	heap_prune_chain(Buffer buffer,
 
 static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid);
 static void heap_prune_record_redirect(PruneState *prstate,
-									   OffsetNumber offnum, OffsetNumber rdoffnum);
+									   OffsetNumber offnum, OffsetNumber rdoffnum,
+									   PruneFreezeResult *presult);
 static void heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum,
 								   PruneFreezeResult *presult);
 static void heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber offnum,
@@ -277,6 +278,8 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
 	presult->nnewlpdead = 0;
 	presult->nfrozen = 0;
 
+	presult->hastup = false;
+
 	/*
 	 * Caller will update the VM after pruning, collecting LP_DEAD items, and
 	 * freezing tuples. Keep track of whether or not the page is all_visible
@@ -419,30 +422,42 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
 				break;
 		}
 
-		/*
-		 * Consider freezing any normal tuples which will not be removed
-		 */
-		if (presult->htsv[offnum] != HEAPTUPLE_DEAD && pagefrz)
+		if (presult->htsv[offnum] != HEAPTUPLE_DEAD)
 		{
-			bool		totally_frozen;
+			/*
+			 * Deliberately don't set hastup for LP_DEAD items.  We make the
+			 * soft assumption that any LP_DEAD items encountered here will
+			 * become LP_UNUSED later on, before count_nondeletable_pages is
+			 * reached.  If we don't make this assumption then rel truncation
+			 * will only happen every other VACUUM, at most.  Besides, VACUUM
+			 * must treat hastup/nonempty_pages as provisional no matter how
+			 * LP_DEAD items are handled (handled here, or handled later on).
+			 */
+			presult->hastup = true;
 
-			/* Tuple with storage -- consider need to freeze */
-			if ((heap_prepare_freeze_tuple(htup, pagefrz,
-										   &prstate.frozen[presult->nfrozen],
-										   &totally_frozen)))
+			/* Consider freezing any normal tuples which will not be removed */
+			if (pagefrz)
 			{
-				/* Save prepared freeze plan for later */
-				prstate.frozen[presult->nfrozen++].offset = offnum;
-			}
+				bool		totally_frozen;
 
-			/*
-			 * If any tuple isn't either totally frozen already or eligible to
-			 * become totally frozen (according to its freeze plan), then the
-			 * page definitely cannot be set all-frozen in the visibility map
-			 * later on
-			 */
-			if (!totally_frozen)
-				presult->all_frozen = false;
+				/* Tuple with storage -- consider need to freeze */
+				if ((heap_prepare_freeze_tuple(htup, pagefrz,
+											   &prstate.frozen[presult->nfrozen],
+											   &totally_frozen)))
+				{
+					/* Save prepared freeze plan for later */
+					prstate.frozen[presult->nfrozen++].offset = offnum;
+				}
+
+				/*
+				 * If any tuple isn't either totally frozen already or
+				 * eligible to become totally frozen (according to its freeze
+				 * plan), then the page definitely cannot be set all-frozen in
+				 * the visibility map later on
+				 */
+				if (!totally_frozen)
+					presult->all_frozen = false;
+			}
 		}
 	}
 
@@ -1049,7 +1064,7 @@ heap_prune_chain(Buffer buffer, OffsetNumber rootoffnum,
 		if (i >= nchain)
 			heap_prune_record_dead_or_unused(prstate, rootoffnum, presult);
 		else
-			heap_prune_record_redirect(prstate, rootoffnum, chainitems[i]);
+			heap_prune_record_redirect(prstate, rootoffnum, chainitems[i], presult);
 	}
 	else if (nchain < 2 && ItemIdIsRedirected(rootlp))
 	{
@@ -1083,7 +1098,8 @@ heap_prune_record_prunable(PruneState *prstate, TransactionId xid)
 /* Record line pointer to be redirected */
 static void
 heap_prune_record_redirect(PruneState *prstate,
-						   OffsetNumber offnum, OffsetNumber rdoffnum)
+						   OffsetNumber offnum, OffsetNumber rdoffnum,
+						   PruneFreezeResult *presult)
 {
 	Assert(prstate->nredirected < MaxHeapTuplesPerPage);
 	prstate->redirected[prstate->nredirected * 2] = offnum;
@@ -1093,6 +1109,8 @@ heap_prune_record_redirect(PruneState *prstate,
 	prstate->marked[offnum] = true;
 	Assert(!prstate->marked[rdoffnum]);
 	prstate->marked[rdoffnum] = true;
+
+	presult->hastup = true;
 }
 
 /* Record line pointer to be marked dead */
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 9dfb56475cf..370721a619a 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1420,7 +1420,6 @@ lazy_scan_prune(LVRelState *vacrel,
 				live_tuples,
 				recently_dead_tuples;
 	HeapPageFreeze pagefrz;
-	bool		hastup = false;
 	OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
 
 	Assert(BufferGetBlockNumber(buf) == blkno);
@@ -1477,28 +1476,12 @@ lazy_scan_prune(LVRelState *vacrel,
 		vacrel->offnum = offnum;
 		itemid = PageGetItemId(page, offnum);
 
-		if (!ItemIdIsUsed(itemid))
-			continue;
-
 		/* Redirect items mustn't be touched */
-		if (ItemIdIsRedirected(itemid))
-		{
-			/* page makes rel truncation unsafe */
-			hastup = true;
+		if (ItemIdIsRedirected(itemid) || !ItemIdIsUsed(itemid))
 			continue;
-		}
 
 		if (ItemIdIsDead(itemid))
 		{
-			/*
-			 * Deliberately don't set hastup for LP_DEAD items.  We make the
-			 * soft assumption that any LP_DEAD items encountered here will
-			 * become LP_UNUSED later on, before count_nondeletable_pages is
-			 * reached.  If we don't make this assumption then rel truncation
-			 * will only happen every other VACUUM, at most.  Besides, VACUUM
-			 * must treat hastup/nonempty_pages as provisional no matter how
-			 * LP_DEAD items are handled (handled here, or handled later on).
-			 */
 			deadoffsets[lpdead_items++] = offnum;
 			continue;
 		}
@@ -1566,9 +1549,6 @@ lazy_scan_prune(LVRelState *vacrel,
 				elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
 				break;
 		}
-
-		hastup = true;			/* page makes rel truncation unsafe */
-
 	}
 
 	vacrel->offnum = InvalidOffsetNumber;
@@ -1650,7 +1630,7 @@ lazy_scan_prune(LVRelState *vacrel,
 	vacrel->recently_dead_tuples += recently_dead_tuples;
 
 	/* Can't truncate this page */
-	if (hastup)
+	if (presult.hastup)
 		vacrel->nonempty_pages = blkno + 1;
 
 	/* Did we find LP_DEAD items? */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index d5cb8f99cac..25dbae8139e 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -202,6 +202,8 @@ typedef struct PruneFreezeResult
 	int			nnewlpdead;		/* Number of newly LP_DEAD items */
 	bool		all_visible;	/* Whether or not the page is all visible */
 	bool		all_visible_except_removable;
+	bool		hastup;			/* Does page make rel truncation unsafe */
+
 	/* Whether or not the page can be set all frozen in the VM */
 	bool		all_frozen;
 
-- 
2.40.1

