From 46dc299c2bc878799e2c56ed4c240d5c5284b986 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 22 Jan 2024 14:53:36 -0500
Subject: [PATCH v3 13/17] 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  | 33 ++++++++++++++++++++++++----
 src/backend/access/heap/vacuumlazy.c | 25 ++-------------------
 src/include/access/heapam.h          |  3 +++
 3 files changed, 34 insertions(+), 27 deletions(-)

diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 994cf75c54e..2fee9aa509c 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -70,7 +70,8 @@ static void prune_prepare_freeze_tuple(Page page, OffsetNumber offnum,
 									   PruneFreezeResult *presult);
 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,
@@ -294,6 +295,8 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
 	presult->nnewlpdead = 0;
 	presult->nfrozen = 0;
 
+	presult->hastup = false;
+
 	/*
 	 * Keep track of whether or not the page is all_visible in case the caller
 	 * wants to use this information to update the VM.
@@ -474,18 +477,37 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer,
 			prune_prepare_freeze_tuple(page, offnum,
 									   pagefrz, frozen, presult);
 
+		itemid = PageGetItemId(page, offnum);
+
+		if (ItemIdIsNormal(itemid) &&
+			presult->htsv[offnum] != HEAPTUPLE_DEAD)
+		{
+			Assert(presult->htsv[offnum] != -1);
+
+			/*
+			 * 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;
+		}
+
 		/* Ignore items already processed as part of an earlier chain */
 		if (prstate.marked[offnum])
 			continue;
 
 		/* Nothing to do if slot is empty */
-		itemid = PageGetItemId(page, offnum);
 		if (!ItemIdIsUsed(itemid))
 			continue;
 
 		/* Process this item or chain of items */
 		presult->ndeleted += heap_prune_chain(buffer, offnum,
 											  &prstate, presult);
+
 	}
 
 	/* Clear the offset information once we have processed the given page. */
@@ -1040,7 +1062,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))
 	{
@@ -1122,7 +1144,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;
@@ -1132,6 +1155,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 6dd8d457c9c..aac38f54c0a 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1447,7 +1447,6 @@ lazy_scan_prune(LVRelState *vacrel,
 				live_tuples,
 				recently_dead_tuples;
 	HeapPageFreeze pagefrz;
-	bool		hastup = false;
 	OffsetNumber deadoffsets[MaxHeapTuplesPerPage];
 
 	Assert(BufferGetBlockNumber(buf) == blkno);
@@ -1491,7 +1490,6 @@ lazy_scan_prune(LVRelState *vacrel,
 	 * the VM after collecting LP_DEAD items and freezing tuples. Pruning will
 	 * have determined whether or not the page is all_visible and able to
 	 * become all_frozen.
-	 *
 	 */
 	for (offnum = FirstOffsetNumber;
 		 offnum <= maxoff;
@@ -1504,28 +1502,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;
 		}
@@ -1593,9 +1575,6 @@ lazy_scan_prune(LVRelState *vacrel,
 				elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
 				break;
 		}
-
-		hastup = true;			/* page makes rel truncation unsafe */
-
 	}
 
 	vacrel->offnum = InvalidOffsetNumber;
@@ -1677,7 +1656,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 8a6bc071345..7ad46696d66 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -202,11 +202,14 @@ 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;
 
 	/* Number of newly frozen tuples */
 	int			nfrozen;
+
 	TransactionId frz_conflict_horizon; /* Newest xmin on the page */
 
 	/*
-- 
2.40.1

