diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 97a2868..1a1ce70 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -18,6 +18,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/htup_details.h"
+#include "access/visibilitymap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
@@ -40,6 +41,13 @@ typedef struct
 	OffsetNumber nowunused[MaxHeapTuplesPerPage];
 	/* marked[i] is TRUE if item i is entered in one of the above arrays */
 	bool		marked[MaxHeapTuplesPerPage + 1];
+	/* 
+	 * all_visible is TRUE if all tuples in the page are visible to all
+	 * transactions and there are no DEAD line pointers in the page.
+	 */
+	bool		all_visible;
+	/* minimum xmin of all-visible tuples */
+	TransactionId	visibility_cutoff_xid;
 } PruneState;
 
 /* Local functions */
@@ -80,6 +88,12 @@ heap_page_prune_opt(Relation relation, Buffer buffer, TransactionId OldestXmin)
 	 *
 	 * Forget it if page is not hinted to contain something prunable that's
 	 * older than OldestXmin.
+	 *
+	 * We now also set visibility information about the page at the end of
+	 * pruning. Its possible that a page may not have anything to prune but 
+	 * marked as not-all-visible. Running it through page prune algorithm can
+	 * give us an opportunity to mark the page all-visible. But its not clear
+	 * if thats worth the cost of scanning the page.
 	 */
 	if (!PageIsPrunable(page, OldestXmin))
 		return;
@@ -176,6 +190,8 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
 	prstate.latestRemovedXid = InvalidTransactionId;
 	prstate.nredirected = prstate.ndead = prstate.nunused = 0;
 	memset(prstate.marked, 0, sizeof(prstate.marked));
+	prstate.all_visible = true;
+	prstate.visibility_cutoff_xid = InvalidTransactionId;
 
 	/* Scan the page */
 	maxoff = PageGetMaxOffsetNumber(page);
@@ -191,8 +207,13 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
 
 		/* Nothing to do if slot is empty or already dead */
 		itemid = PageGetItemId(page, offnum);
-		if (!ItemIdIsUsed(itemid) || ItemIdIsDead(itemid))
+		if (!ItemIdIsUsed(itemid))
+		   continue;
+		if (ItemIdIsDead(itemid))
+		{
+			prstate.all_visible = false;
 			continue;
+		}
 
 		/* Process this item or chain of items */
 		ndeleted += heap_prune_chain(relation, buffer, offnum,
@@ -267,6 +288,25 @@ heap_page_prune(Relation relation, Buffer buffer, TransactionId OldestXmin,
 		}
 	}
 
+	/* Update the all-visible flag on the page */
+	if (!PageIsAllVisible(page) && prstate.all_visible)
+	{
+		Buffer vmbuffer = InvalidBuffer;
+
+		elog(DEBUG2, "heap_page_prune - set page %d all visible",
+				BufferGetBlockNumber(buffer));
+
+		PageSetAllVisible(page);
+
+		visibilitymap_pin(relation, BufferGetBlockNumber(buffer), &vmbuffer);
+		visibilitymap_set(relation, BufferGetBlockNumber(buffer),
+				InvalidXLogRecPtr, vmbuffer, prstate.visibility_cutoff_xid);
+		if (BufferIsValid(vmbuffer))
+			ReleaseBuffer(vmbuffer);
+
+		MarkBufferDirty(buffer);
+	}
+
 	END_CRIT_SECTION();
 
 	/*
@@ -427,7 +467,10 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		 * function.)
 		 */
 		if (ItemIdIsDead(lp))
+		{
+			prstate->all_visible = false;
 			break;
+		}
 
 		Assert(ItemIdIsNormal(lp));
 		htup = (HeapTupleHeader) PageGetItem(dp, lp);
@@ -453,6 +496,16 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 		{
 			case HEAPTUPLE_DEAD:
 				tupdead = true;
+				/*
+				 * If this DEAD tuple was HOT-updated, its removal won't cause
+				 * any impact on the all-visibility of the page. For such
+				 * tuples, it is guaranteed to have a newer version (dead or
+				 * alive) in this same HOT chain and that version will decide
+				 * whether the page is all-visible or not. OTOH if this is the
+				 * last tuple in the HOT chain making the entire chain dead,
+				 * this case is handled by setting prstate.all_visible to false
+				 * in heap_prune_record_dead
+				 */
 				break;
 
 			case HEAPTUPLE_RECENTLY_DEAD:
@@ -464,6 +517,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 				 */
 				heap_prune_record_prunable(prstate,
 										   HeapTupleHeaderGetXmax(htup));
+				prstate->all_visible = false;
 				break;
 
 			case HEAPTUPLE_DELETE_IN_PROGRESS:
@@ -474,9 +528,44 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 				 */
 				heap_prune_record_prunable(prstate,
 										   HeapTupleHeaderGetXmax(htup));
+				prstate->all_visible = false;
 				break;
 
 			case HEAPTUPLE_LIVE:
+				/*
+				 * Is the tuple definitely visible to all transactions?
+				 *
+				 * NB: Like with per-tuple hint bits, we can't set the
+				 * PD_ALL_VISIBLE flag if the inserter committed
+				 * asynchronously. See SetHintBits for more info. Check
+				 * that the HEAP_XMIN_COMMITTED hint bit is set because of
+				 * that.
+				 */
+				if (prstate->all_visible)
+				{
+					TransactionId xmin;
+
+					if (!(htup->t_infomask & HEAP_XMIN_COMMITTED))
+					{
+						prstate->all_visible = false;
+						break;
+					}
+					/*
+					 * The inserter definitely committed. But is it
+					 * old enough that everyone sees it as committed?
+					 */
+					xmin = HeapTupleHeaderGetXmin(htup);
+					if (!TransactionIdPrecedes(xmin, OldestXmin))
+					{
+						prstate->all_visible = false;
+						break;
+					}
+
+					/* Track newest xmin on page. */
+					if (TransactionIdFollows(xmin, prstate->visibility_cutoff_xid))
+						prstate->visibility_cutoff_xid = xmin;
+				}
+				break;
 			case HEAPTUPLE_INSERT_IN_PROGRESS:
 
 				/*
@@ -484,7 +573,10 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
 				 * marking the page prunable when we see INSERT_IN_PROGRESS.
 				 * But we don't.  See related decisions about when to mark the
 				 * page prunable in heapam.c.
+				 *
+				 * The tuple can't be visible to all
 				 */
+				prstate->all_visible = false;
 				break;
 
 			default:
@@ -615,6 +707,8 @@ heap_prune_record_dead(PruneState *prstate, OffsetNumber offnum)
 	prstate->ndead++;
 	Assert(!prstate->marked[offnum]);
 	prstate->marked[offnum] = true;
+	/* DEAD line pointers can only be removed by VACUUM */
+	prstate->all_visible = false;
 }
 
 /* Record item pointer to be marked unused */
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index fc18b27..a7f6125 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -679,6 +679,19 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
 										 &vacrelstats->latestRemovedXid);
 
 		/*
+		 * XXX If heap_page_prune marked the page as all-visible and we are
+		 * skipping skipping_all_visible_blocks, then we can just skip the
+		 * line pointer array scan below. We probably still want to record the
+		 * free space in the page because heap_page_prune does not do that
+		 * today. We may also want to see if we can freeze old tuples on the
+		 * page. This is quite similar to what we are doing with
+		 * SKIP_PAGES_THRESHOLD mechanism anyway where we read a page even if
+		 * we know its all-visible just to give kernel a chance to do seq IO.
+		 * Not just that, we also read the page content to see if there are
+		 * tuples that can be freeze
+		 */ 
+
+		/*
 		 * Now scan the page to collect vacuumable items and check for tuples
 		 * requiring freezing.
 		 */
