diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index b0a911e..205b3e1 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1491,8 +1491,14 @@ heap_fetch(Relation relation,
  * On entry, *tid is the TID of a tuple (either a simple tuple, or the root
  * of a HOT chain), and buffer is the buffer holding this tuple.  We search
  * for the first chain member satisfying the given snapshot.  If one is
- * found, we update *tid to reference that tuple's offset number, and
- * return TRUE.  If no match, return FALSE without modifying *tid.
+ * found, we update *tid and *heapTuple to reference that tuple, and
+ * return TRUE.  If no match, return FALSE without modifying *tid (*heapTuple
+ * is undefined in that case).
+ *
+ * To fetch the next matching tuple in the chain, call again with 'firstCall'
+ * set to FALSE and *tid still pointing to the tuple returned by previous call.
+ * (normally only one tuple in a chain can be visible at a time, but that does
+ * not hold for special snapshots like SnapshotAny)
  *
  * If all_dead is not NULL, we check non-visible tuples to see if they are
  * globally dead; *all_dead is set TRUE if all members of the HOT chain
@@ -1504,27 +1510,29 @@ heap_fetch(Relation relation,
  */
 bool
 heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
-					   bool *all_dead)
+					   HeapTuple heapTuple, bool *all_dead, bool firstCall)
 {
 	Page		dp = (Page) BufferGetPage(buffer);
 	TransactionId prev_xmax = InvalidTransactionId;
 	OffsetNumber offnum;
 	bool		at_chain_start;
+	bool		skip;
 
-	if (all_dead)
+	if (all_dead && firstCall)
 		*all_dead = true;
 
 	Assert(TransactionIdIsValid(RecentGlobalXmin));
 
 	Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
 	offnum = ItemPointerGetOffsetNumber(tid);
-	at_chain_start = true;
+
+	at_chain_start = firstCall;
+	skip = !firstCall;
 
 	/* Scan through possible multiple members of HOT-chain */
 	for (;;)
 	{
 		ItemId		lp;
-		HeapTupleData heapTuple;
 
 		/* check for bogus TID */
 		if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp))
@@ -1547,13 +1555,13 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
 			break;
 		}
 
-		heapTuple.t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-		heapTuple.t_len = ItemIdGetLength(lp);
+		heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
+		heapTuple->t_len = ItemIdGetLength(lp);
 
 		/*
 		 * Shouldn't see a HEAP_ONLY tuple at chain start.
 		 */
-		if (at_chain_start && HeapTupleIsHeapOnly(&heapTuple))
+		if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
 			break;
 
 		/*
@@ -1562,17 +1570,18 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
 		 */
 		if (TransactionIdIsValid(prev_xmax) &&
 			!TransactionIdEquals(prev_xmax,
-								 HeapTupleHeaderGetXmin(heapTuple.t_data)))
+								 HeapTupleHeaderGetXmin(heapTuple->t_data)))
 			break;
 
 		/* If it's visible per the snapshot, we must return it */
-		if (HeapTupleSatisfiesVisibility(&heapTuple, snapshot, buffer))
+		if (!skip && HeapTupleSatisfiesVisibility(heapTuple, snapshot, buffer))
 		{
 			ItemPointerSetOffsetNumber(tid, offnum);
 			if (all_dead)
 				*all_dead = false;
 			return true;
 		}
+		skip = false;
 
 		/*
 		 * If we can't see it, maybe no one else can either.  At caller
@@ -1580,7 +1589,7 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
 		 * transactions.
 		 */
 		if (all_dead && *all_dead &&
-			HeapTupleSatisfiesVacuum(heapTuple.t_data, RecentGlobalXmin,
+			HeapTupleSatisfiesVacuum(heapTuple->t_data, RecentGlobalXmin,
 									 buffer) != HEAPTUPLE_DEAD)
 			*all_dead = false;
 
@@ -1588,13 +1597,13 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
 		 * Check to see if HOT chain continues past this tuple; if so fetch
 		 * the next offnum and loop around.
 		 */
-		if (HeapTupleIsHotUpdated(&heapTuple))
+		if (HeapTupleIsHotUpdated(heapTuple))
 		{
-			Assert(ItemPointerGetBlockNumber(&heapTuple.t_data->t_ctid) ==
+			Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
 				   ItemPointerGetBlockNumber(tid));
-			offnum = ItemPointerGetOffsetNumber(&heapTuple.t_data->t_ctid);
+			offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
 			at_chain_start = false;
-			prev_xmax = HeapTupleHeaderGetXmax(heapTuple.t_data);
+			prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data);
 		}
 		else
 			break;				/* end of chain */
@@ -1616,10 +1625,12 @@ heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot,
 {
 	bool		result;
 	Buffer		buffer;
+	HeapTupleData heapTuple;
 
 	buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
 	LockBuffer(buffer, BUFFER_LOCK_SHARE);
-	result = heap_hot_search_buffer(tid, buffer, snapshot, all_dead);
+	result = heap_hot_search_buffer(tid, buffer, snapshot, &heapTuple,
+									all_dead, true);
 	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 	ReleaseBuffer(buffer);
 	return result;
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 1c1cd34..4398e8c 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -99,9 +99,6 @@ RelationGetIndexScan(Relation indexRelation,
 	ItemPointerSetInvalid(&scan->xs_ctup.t_self);
 	scan->xs_ctup.t_data = NULL;
 	scan->xs_cbuf = InvalidBuffer;
-	scan->xs_hot_dead = false;
-	scan->xs_next_hot = InvalidOffsetNumber;
-	scan->xs_prev_xmax = InvalidTransactionId;
 
 	/*
 	 * Let the AM fill in the key and any opaque data it wants.
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index c86cd52..616edae 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -20,7 +20,8 @@
  *		index_insert	- insert an index tuple into a relation
  *		index_markpos	- mark a scan position
  *		index_restrpos	- restore a scan position
- *		index_getnext	- get the next tuple from a scan
+ *		index_next		- advance index scan to next match
+ *		index_fetch		- fetch heap tuple for current index scan position
  *		index_getbitmap - get all tuples from a scan
  *		index_bulk_delete	- bulk deletion of index tuples
  *		index_vacuum_cleanup	- post-deletion cleanup of an index
@@ -310,8 +311,6 @@ index_rescan(IndexScanDesc scan, ScanKey key)
 		scan->xs_cbuf = InvalidBuffer;
 	}
 
-	scan->xs_next_hot = InvalidOffsetNumber;
-
 	scan->kill_prior_tuple = false;		/* for safety */
 
 	FunctionCall2(procedure,
@@ -389,32 +388,28 @@ index_restrpos(IndexScanDesc scan)
 	SCAN_CHECKS;
 	GET_SCAN_PROCEDURE(amrestrpos);
 
-	scan->xs_next_hot = InvalidOffsetNumber;
-
 	scan->kill_prior_tuple = false;		/* for safety */
 
 	FunctionCall1(procedure, PointerGetDatum(scan));
 }
 
 /* ----------------
- *		index_getnext - get the next heap tuple from a scan
+ *		index_next - get the next index tuple from a scan
  *
- * The result is the next heap tuple satisfying the scan keys and the
- * snapshot, or NULL if no more matching tuples exist.	On success,
- * the buffer containing the heap tuple is pinned (the pin will be dropped
- * at the next index_getnext or index_endscan).
+ * Advances to the next index tuple satisfying the scan keys, returning TRUE
+ * if there was one, FALSE otherwise. The heap TID pointer of the next match
+ * is stored in scan->xs_ctup.self.
  *
  * Note: caller must check scan->xs_recheck, and perform rechecking of the
  * scan keys if required.  We do not do that here because we don't have
  * enough information to do it efficiently in the general case.
  * ----------------
  */
-HeapTuple
-index_getnext(IndexScanDesc scan, ScanDirection direction)
+bool
+index_next(IndexScanDesc scan, ScanDirection direction)
 {
-	HeapTuple	heapTuple = &scan->xs_ctup;
-	ItemPointer tid = &heapTuple->t_self;
 	FmgrInfo   *procedure;
+	bool 		found;
 
 	SCAN_CHECKS;
 	GET_SCAN_PROCEDURE(amgettuple);
@@ -422,220 +417,125 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
 	Assert(TransactionIdIsValid(RecentGlobalXmin));
 
 	/*
-	 * We always reset xs_hot_dead; if we are here then either we are just
-	 * starting the scan, or we previously returned a visible tuple, and in
-	 * either case it's inappropriate to kill the prior index entry.
+	 * The AM's gettuple proc finds the next index entry matching the scan
+	 * keys, and puts the TID in xs_ctup.t_self. It should also set
+	 * scan->xs_recheck, though we pay no attention to that here.
 	 */
-	scan->xs_hot_dead = false;
+	found = DatumGetBool(FunctionCall2(procedure,
+									   PointerGetDatum(scan),
+									   Int32GetDatum(direction)));
 
-	for (;;)
-	{
-		OffsetNumber offnum;
-		bool		at_chain_start;
-		Page		dp;
+	/* Reset kill flag immediately for safety */
+	scan->kill_prior_tuple = false;
 
-		if (scan->xs_next_hot != InvalidOffsetNumber)
-		{
-			/*
-			 * We are resuming scan of a HOT chain after having returned an
-			 * earlier member.	Must still hold pin on current heap page.
-			 */
-			Assert(BufferIsValid(scan->xs_cbuf));
-			Assert(ItemPointerGetBlockNumber(tid) ==
-				   BufferGetBlockNumber(scan->xs_cbuf));
-			Assert(TransactionIdIsValid(scan->xs_prev_xmax));
-			offnum = scan->xs_next_hot;
-			at_chain_start = false;
-			scan->xs_next_hot = InvalidOffsetNumber;
-		}
-		else
+	/* If we're out of index entries, release any held pin on a heap page */
+	if (!found)
+	{
+		if (BufferIsValid(scan->xs_cbuf))
 		{
-			bool		found;
-			Buffer		prev_buf;
-
-			/*
-			 * If we scanned a whole HOT chain and found only dead tuples,
-			 * tell index AM to kill its entry for that TID.
-			 */
-			scan->kill_prior_tuple = scan->xs_hot_dead;
-
-			/*
-			 * The AM's gettuple proc finds the next index entry matching the
-			 * scan keys, and puts the TID in xs_ctup.t_self (ie, *tid). It
-			 * should also set scan->xs_recheck, though we pay no attention to
-			 * that here.
-			 */
-			found = DatumGetBool(FunctionCall2(procedure,
-											   PointerGetDatum(scan),
-											   Int32GetDatum(direction)));
-
-			/* Reset kill flag immediately for safety */
-			scan->kill_prior_tuple = false;
-
-			/* If we're out of index entries, break out of outer loop */
-			if (!found)
-				break;
-
-			pgstat_count_index_tuples(scan->indexRelation, 1);
-
-			/* Switch to correct buffer if we don't have it already */
-			prev_buf = scan->xs_cbuf;
-			scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf,
-												 scan->heapRelation,
-											 ItemPointerGetBlockNumber(tid));
-
-			/*
-			 * Prune page, but only if we weren't already on this page
-			 */
-			if (prev_buf != scan->xs_cbuf)
-				heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
-									RecentGlobalXmin);
-
-			/* Prepare to scan HOT chain starting at index-referenced offnum */
-			offnum = ItemPointerGetOffsetNumber(tid);
-			at_chain_start = true;
-
-			/* We don't know what the first tuple's xmin should be */
-			scan->xs_prev_xmax = InvalidTransactionId;
-
-			/* Initialize flag to detect if all entries are dead */
-			scan->xs_hot_dead = true;
+			ReleaseBuffer(scan->xs_cbuf);
+			scan->xs_cbuf = InvalidBuffer;
 		}
+		return false;
+	}
 
-		/* Obtain share-lock on the buffer so we can examine visibility */
-		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+	pgstat_count_index_tuples(scan->indexRelation, 1);
 
-		dp = (Page) BufferGetPage(scan->xs_cbuf);
+	return true;
+}
 
-		/* Scan through possible multiple members of HOT-chain */
-		for (;;)
-		{
-			ItemId		lp;
-			ItemPointer ctid;
-
-			/* check for bogus TID */
-			if (offnum < FirstOffsetNumber ||
-				offnum > PageGetMaxOffsetNumber(dp))
-				break;
-
-			lp = PageGetItemId(dp, offnum);
-
-			/* check for unused, dead, or redirected items */
-			if (!ItemIdIsNormal(lp))
-			{
-				/* We should only see a redirect at start of chain */
-				if (ItemIdIsRedirected(lp) && at_chain_start)
-				{
-					/* Follow the redirect */
-					offnum = ItemIdGetRedirect(lp);
-					at_chain_start = false;
-					continue;
-				}
-				/* else must be end of chain */
-				break;
-			}
-
-			/*
-			 * We must initialize all of *heapTuple (ie, scan->xs_ctup) since
-			 * it is returned to the executor on success.
-			 */
-			heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp);
-			heapTuple->t_len = ItemIdGetLength(lp);
-			ItemPointerSetOffsetNumber(tid, offnum);
-			heapTuple->t_tableOid = RelationGetRelid(scan->heapRelation);
-			ctid = &heapTuple->t_data->t_ctid;
-
-			/*
-			 * Shouldn't see a HEAP_ONLY tuple at chain start.  (This test
-			 * should be unnecessary, since the chain root can't be removed
-			 * while we have pin on the index entry, but let's make it
-			 * anyway.)
-			 */
-			if (at_chain_start && HeapTupleIsHeapOnly(heapTuple))
-				break;
-
-			/*
-			 * The xmin should match the previous xmax value, else chain is
-			 * broken.	(Note: this test is not optional because it protects
-			 * us against the case where the prior chain member's xmax aborted
-			 * since we looked at it.)
-			 */
-			if (TransactionIdIsValid(scan->xs_prev_xmax) &&
-				!TransactionIdEquals(scan->xs_prev_xmax,
-								  HeapTupleHeaderGetXmin(heapTuple->t_data)))
-				break;
-
-			/* If it's visible per the snapshot, we must return it */
-			if (HeapTupleSatisfiesVisibility(heapTuple, scan->xs_snapshot,
-											 scan->xs_cbuf))
-			{
-				/*
-				 * If the snapshot is MVCC, we know that it could accept at
-				 * most one member of the HOT chain, so we can skip examining
-				 * any more members.  Otherwise, check for continuation of the
-				 * HOT-chain, and set state for next time.
-				 */
-				if (IsMVCCSnapshot(scan->xs_snapshot))
-					scan->xs_next_hot = InvalidOffsetNumber;
-				else if (HeapTupleIsHotUpdated(heapTuple))
-				{
-					Assert(ItemPointerGetBlockNumber(ctid) ==
-						   ItemPointerGetBlockNumber(tid));
-					scan->xs_next_hot = ItemPointerGetOffsetNumber(ctid);
-					scan->xs_prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data);
-				}
-				else
-					scan->xs_next_hot = InvalidOffsetNumber;
-
-				LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
-
-				pgstat_count_heap_fetch(scan->indexRelation);
-
-				return heapTuple;
-			}
-
-			/*
-			 * If we can't see it, maybe no one else can either.  Check to see
-			 * if the tuple is dead to all transactions.  If we find that all
-			 * the tuples in the HOT chain are dead, we'll signal the index AM
-			 * to not return that TID on future indexscans.
-			 */
-			if (scan->xs_hot_dead &&
-				HeapTupleSatisfiesVacuum(heapTuple->t_data, RecentGlobalXmin,
-										 scan->xs_cbuf) != HEAPTUPLE_DEAD)
-				scan->xs_hot_dead = false;
-
-			/*
-			 * Check to see if HOT chain continues past this tuple; if so
-			 * fetch the next offnum (we don't bother storing it into
-			 * xs_next_hot, but must store xs_prev_xmax), and loop around.
-			 */
-			if (HeapTupleIsHotUpdated(heapTuple))
-			{
-				Assert(ItemPointerGetBlockNumber(ctid) ==
-					   ItemPointerGetBlockNumber(tid));
-				offnum = ItemPointerGetOffsetNumber(ctid);
-				at_chain_start = false;
-				scan->xs_prev_xmax = HeapTupleHeaderGetXmax(heapTuple->t_data);
-			}
-			else
-				break;			/* end of chain */
-		}						/* loop over a single HOT chain */
-
-		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
-
-		/* Loop around to ask index AM for another TID */
-		scan->xs_next_hot = InvalidOffsetNumber;
-	}
 
-	/* Release any held pin on a heap page */
-	if (BufferIsValid(scan->xs_cbuf))
+/* ----------------
+ *		index_fetch - fetch the heap tuple the current index pointer points to
+ *
+ * The result is the heap tuple the current index tuple points to if it
+ * matches the snapshot, or NULL if it doesn't. On success, the buffer
+ * containing the heap tuple is pinned (the pin will be dropped at the next
+ * index_fetch or index_endscan).
+ * ----------------
+ */
+HeapTuple
+index_fetch(IndexScanDesc scan)
+{
+	ItemPointer tid = &scan->xs_ctup.t_self;
+	bool		found;
+	Buffer		prev_buf;
+
+	/*
+	 * We only fetch the first matching tuple in a chain, so we can't support
+	 * SnapshotAny. All other snapshots only consider one tuple in a HOT chain
+	 * as visible at any given moment.
+	 */
+	Assert(scan->xs_snapshot != SnapshotAny);
+
+	/* Switch to correct buffer if we don't have it already */
+	prev_buf = scan->xs_cbuf;
+	scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf,
+										 scan->heapRelation,
+										 ItemPointerGetBlockNumber(tid));
+
+	/*
+	 * Prune page, but only if we weren't already on this page
+	 */
+	if (prev_buf != scan->xs_cbuf)
+		heap_page_prune_opt(scan->heapRelation, scan->xs_cbuf,
+							RecentGlobalXmin);
+
+	/* Obtain share-lock on the buffer so we can examine visibility */
+	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+
+	found = heap_hot_search_buffer(tid, scan->xs_cbuf,
+								   scan->xs_snapshot, &scan->xs_ctup,
+								   &scan->kill_prior_tuple, true);
+	LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+
+	if (found)
 	{
-		ReleaseBuffer(scan->xs_cbuf);
-		scan->xs_cbuf = InvalidBuffer;
+		Assert(!scan->kill_prior_tuple);
+
+		/*
+		 * We must initialize all of *heapTuple (ie, scan->xs_ctup) since
+		 * it is returned to the executor on success.
+		 */
+		scan->xs_ctup.t_tableOid = RelationGetRelid(scan->heapRelation);
+
+		pgstat_count_heap_fetch(scan->indexRelation);
+
+		return &scan->xs_ctup;
 	}
+	else
+		return NULL;
+}
 
-	return NULL;				/* failure exit */
+/* ----------------
+ *		index_getnext - get the next heap tuple from a scan
+ *
+ * This is a shorthand for index_next() + index_fetch().
+ *
+ * The result is the next heap tuple satisfying the scan keys and the
+ * snapshot, or NULL if no more matching tuples exist.	On success,
+ * the buffer containing the heap tuple is pinned (the pin will be dropped
+ * at the next index_getnext or index_endscan).
+ *
+ * Note: caller must check scan->xs_recheck, and perform rechecking of the
+ * scan keys if required.  We do not do that here because we don't have
+ * enough information to do it efficiently in the general case.
+ * ----------------
+ */
+HeapTuple
+index_getnext(IndexScanDesc scan, ScanDirection direction)
+{
+	for(;;)
+	{
+		HeapTuple tup;
+
+		if (!index_next(scan, direction))
+			return NULL;
+
+		tup = index_fetch(scan);
+		if (tup != NULL)
+			return tup;
+	}
 }
 
 /* ----------------
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a6ba2ec..6fb810e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -772,6 +772,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
 	TransactionId OldestXmin;
 	TransactionId FreezeXid;
 	RewriteState rwstate;
+	ItemPointerData tid;
 
 	/*
 	 * Open the relations we need.
@@ -829,19 +830,56 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
 	scan = index_beginscan(OldHeap, OldIndex,
 						   SnapshotAny, 0, (ScanKey) NULL);
 
-	while ((tuple = index_getnext(scan, ForwardScanDirection)) != NULL)
+	tuple = NULL;
+	for(;;)
 	{
 		HeapTuple	copiedTuple;
 		bool		isdead;
 		int			i;
+		bool		found;
 
 		CHECK_FOR_INTERRUPTS();
 
-		/* Since we used no scan keys, should never need to recheck */
-		if (scan->xs_recheck)
-			elog(ERROR, "CLUSTER does not support lossy index conditions");
+		/* Advance to next index tuple if we're done with current HOT chain */
+		if (tuple == NULL)
+		{
+			if (!index_next(scan, ForwardScanDirection))
+				break;
+
+			/* Since we used no scan keys, should never need to recheck */
+			if (scan->xs_recheck)
+				elog(ERROR, "CLUSTER does not support lossy index conditions");
+
+			tid = scan->xs_ctup.t_self;
 
-		LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+			scan->xs_cbuf = ReleaseAndReadBuffer(scan->xs_cbuf, OldHeap,
+												 ItemPointerGetBlockNumber(&tid));
+			/* Obtain share-lock on the buffer so we can examine visibility */
+			LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+			found = heap_hot_search_buffer(&tid, scan->xs_cbuf,
+										   scan->xs_snapshot, &scan->xs_ctup,
+										   &scan->kill_prior_tuple, true);
+			if (!found)
+			{
+				LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+				break;
+			}
+		}
+		else
+		{
+			/* Fetch next tuple from current HOT chain */
+			LockBuffer(scan->xs_cbuf, BUFFER_LOCK_SHARE);
+			found = heap_hot_search_buffer(&tid, scan->xs_cbuf,
+										   scan->xs_snapshot, &scan->xs_ctup,
+										   &scan->kill_prior_tuple, false);
+			if (!found)
+			{
+				LockBuffer(scan->xs_cbuf, BUFFER_LOCK_UNLOCK);
+				tuple = NULL;
+				continue;
+			}
+		}
+		tuple = &scan->xs_ctup;
 
 		switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin,
 										 scan->xs_cbuf))
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index eba7063..e511abd 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -382,9 +382,11 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
 		{
 			OffsetNumber offnum = tbmres->offsets[curslot];
 			ItemPointerData tid;
+			HeapTupleData heapTuple;
 
 			ItemPointerSet(&tid, page, offnum);
-			if (heap_hot_search_buffer(&tid, buffer, snapshot, NULL))
+			if (heap_hot_search_buffer(&tid, buffer, snapshot, &heapTuple,
+									   NULL, true))
 				scan->rs_vistuples[ntup++] = ItemPointerGetOffsetNumber(&tid);
 		}
 	}
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index ae23a19..cfe2b60 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -145,6 +145,9 @@ extern void index_endscan(IndexScanDesc scan);
 extern void index_markpos(IndexScanDesc scan);
 extern void index_restrpos(IndexScanDesc scan);
 extern HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction);
+extern bool index_next(IndexScanDesc scan, ScanDirection direction);
+extern HeapTuple index_fetch(IndexScanDesc scan);
+
 extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap);
 
 extern IndexBulkDeleteResult *index_bulk_delete(IndexVacuumInfo *info,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index f8395fe..a5781db 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -83,7 +83,8 @@ extern bool heap_fetch(Relation relation, Snapshot snapshot,
 		   HeapTuple tuple, Buffer *userbuf, bool keep_buf,
 		   Relation stats_relation);
 extern bool heap_hot_search_buffer(ItemPointer tid, Buffer buffer,
-					   Snapshot snapshot, bool *all_dead);
+					   Snapshot snapshot, HeapTuple tuple,
+					   bool *all_dead, bool firstCall);
 extern bool heap_hot_search(ItemPointer tid, Relation relation,
 				Snapshot snapshot, bool *all_dead);
 
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 47b95c2..3bac6d3 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -72,16 +72,11 @@ typedef struct IndexScanDescData
 	/* index access method's private state */
 	void	   *opaque;			/* access-method-specific info */
 
-	/* xs_ctup/xs_cbuf/xs_recheck are valid after a successful index_getnext */
+	/* xs_ctup/xs_cbuf/xs_recheck are valid after a successful index_next */
 	HeapTupleData xs_ctup;		/* current heap tuple, if any */
 	Buffer		xs_cbuf;		/* current heap buffer in scan, if any */
 	/* NB: if xs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
 	bool		xs_recheck;		/* T means scan keys must be rechecked */
-
-	/* state data for traversing HOT chains in index_getnext */
-	bool		xs_hot_dead;	/* T if all members of HOT chain are dead */
-	OffsetNumber xs_next_hot;	/* next member of HOT chain, if any */
-	TransactionId xs_prev_xmax; /* previous HOT chain member's XMAX, if any */
 } IndexScanDescData;
 
 /* Struct for heap-or-index scans of system tables */
