From 9e792516465f204c98d566e21c8aee64b2849c73 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Mon, 28 Dec 2020 20:57:43 -0800
Subject: [PATCH v1] Fix latestRemovedXid index deletion calculation.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-Wz=Eib393+HHcERK_9MtgNS7Ew1HY=RDC_g6GL46zM5C6Q@mail.gmail.com
Backpatch: 12-
---
 src/backend/access/heap/heapam.c | 76 ++++++++++++++++++--------------
 1 file changed, 44 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index a9583f3103..14621c99b3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -6997,6 +6997,7 @@ heap_compute_xid_horizon_for_tuples(Relation rel,
 									ItemPointerData *tids,
 									int nitems)
 {
+	/* Start with the assumption that no recovery conflict is needed */
 	TransactionId latestRemovedXid = InvalidTransactionId;
 	BlockNumber hblkno;
 	Buffer		buf = InvalidBuffer;
@@ -7045,7 +7046,6 @@ heap_compute_xid_horizon_for_tuples(Relation rel,
 	for (int i = 0; i < nitems; i++)
 	{
 		ItemPointer htid = &tids[i];
-		ItemId		hitemid;
 		OffsetNumber hoffnum;
 
 		/*
@@ -7080,44 +7080,56 @@ heap_compute_xid_horizon_for_tuples(Relation rel,
 			LockBuffer(buf, BUFFER_LOCK_SHARE);
 		}
 
+		/*
+		 * Maintain latestRemovedXid value for deletion operation as a whole
+		 * by trying to advance current value using the tuple headers of items
+		 * from the htid HOT chain
+		 */
 		hoffnum = ItemPointerGetOffsetNumber(htid);
-		hitemid = PageGetItemId(hpage, hoffnum);
-
-		/*
-		 * Follow any redirections until we find something useful.
-		 */
-		while (ItemIdIsRedirected(hitemid))
+		for (;;)
 		{
-			hoffnum = ItemIdGetRedirect(hitemid);
-			hitemid = PageGetItemId(hpage, hoffnum);
-		}
+			ItemId		hitemid = PageGetItemId(hpage, hoffnum);
+			HeapTupleHeader htup;
 
-		/*
-		 * If the heap item has storage, then read the header and use that to
-		 * set latestRemovedXid.
-		 *
-		 * Some LP_DEAD items may not be accessible, so we ignore them.
-		 */
-		if (ItemIdHasStorage(hitemid))
-		{
-			HeapTupleHeader htuphdr;
+			if (ItemIdIsRedirected(hitemid))
+			{
+				hoffnum = ItemIdGetRedirect(hitemid);
+				continue;
+			}
 
-			htuphdr = (HeapTupleHeader) PageGetItem(hpage, hitemid);
-
-			HeapTupleHeaderAdvanceLatestRemovedXid(htuphdr, &latestRemovedXid);
-		}
-		else if (ItemIdIsDead(hitemid))
-		{
 			/*
-			 * Conjecture: if hitemid is dead then it had xids before the xids
-			 * marked on LP_NORMAL items. So we just ignore this item and move
-			 * onto the next, for the purposes of calculating
-			 * latestRemovedXid.
+			 * It's quite possible that we'll encounter an LP_DEAD line
+			 * pointer.  No need to do anything more with htid when that
+			 * happens.
+			 *
+			 * This is okay because the earlier heap pruning operation that
+			 * made the line pointer LP_DEAD in the first place must have
+			 * considered the heap tuple header as part of generating its own
+			 * latestRemovedXid value.  Relying on XLOG_HEAP2_CLEANUP_INFO
+			 * records like this is the same strategy that conventional index
+			 * vacuuming (not to be confused with index deletion) uses to
+			 * totally avoid any need to explicitly log latestRemovedXid
+			 * values.
 			 */
-		}
-		else
-			Assert(!ItemIdIsUsed(hitemid));
+			if (!ItemIdIsNormal(hitemid))
+				break;
 
+			htup = (HeapTupleHeader) PageGetItem(hpage, hitemid);
+			HeapTupleHeaderAdvanceLatestRemovedXid(htup, &latestRemovedXid);
+
+			/*
+			 * If the tuple is not HOT-updated, then we are at the end of this
+			 * HOT-chain.  No need to visit later tuples from the same update
+			 * chain (they get their own index entries) -- just move on to
+			 * next htid from index AM caller.
+			 */
+			if (!HeapTupleHeaderIsHotUpdated(htup))
+				break;
+
+			/* Advance to next HOT chain member from htid's HOT chain */
+			Assert(ItemPointerGetBlockNumber(&htup->t_ctid) == hblkno);
+			hoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
+		}
 	}
 
 	if (BufferIsValid(buf))
-- 
2.27.0

