diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e0aeaa6909..9d86b8dbe7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -6161,6 +6161,8 @@ get_actual_variable_endpoint(Relation heapRel,
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	MemoryContext oldcontext;
+	uint32 		visited_heap_pages = 0;
+	BlockNumber	last_heap_block = InvalidBlockNumber;
 
 	/*
 	 * We use the index-only-scan machinery for this.  With mostly-static
@@ -6213,13 +6215,41 @@ get_actual_variable_endpoint(Relation heapRel,
 	/* Fetch first/next tuple in specified direction */
 	while ((tid = index_getnext_tid(index_scan, indexscandir)) != NULL)
 	{
+		BlockNumber block = ItemPointerGetBlockNumber(tid);
+
 		if (!VM_ALL_VISIBLE(heapRel,
-							ItemPointerGetBlockNumber(tid),
+							block,
 							&vmbuffer))
 		{
 			/* Rats, we have to visit the heap to check visibility */
 			if (!index_fetch_heap(index_scan, tableslot))
-				continue;		/* no visible tuple, try next index entry */
+			{
+				CHECK_FOR_INTERRUPTS();
+
+				/*
+				 * Use a simple test to see if we read a new block.
+				 * This clearly isn't accurate for all access patterns
+				 * but the only user of this information is the
+				 * simple heuristic below.
+				 */
+				if (block != last_heap_block)
+					visited_heap_pages++;
+
+				/*
+				 * Apply a simple heuristic to avoid time-consuming
+				 * scans through dead entries, if they exist.
+				 * If we exceed the limit, simply break and return,
+				 * which causes us to use the default selectivity.
+				 */
+#define VISITED_PAGES_LIMIT 100
+				if (visited_heap_pages > VISITED_PAGES_LIMIT)
+					break;
+				else
+				{
+					last_heap_block = block;
+					continue; /* no visible tuple, try next index entry */
+				}
+			}
 
 			/* We don't actually need the heap tuple for anything */
 			ExecClearTuple(tableslot);
