@@ -142,7 +142,7 @@ static void lazy_cleanup_index(Relation indrel,
142142 IndexBulkDeleteResult * stats ,
143143 LVRelStats * vacrelstats );
144144static int lazy_vacuum_page (Relation onerel , BlockNumber blkno , Buffer buffer ,
145- int tupindex , LVRelStats * vacrelstats );
145+ int tupindex , LVRelStats * vacrelstats , Buffer * vmbuffer );
146146static void lazy_truncate_heap (Relation onerel , LVRelStats * vacrelstats );
147147static BlockNumber count_nondeletable_pages (Relation onerel ,
148148 LVRelStats * vacrelstats );
@@ -151,6 +151,8 @@ static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
151151 ItemPointer itemptr );
152152static bool lazy_tid_reaped (ItemPointer itemptr , void * state );
153153static int vac_cmp_itemptr (const void * left , const void * right );
154+ static bool heap_page_is_all_visible (Buffer buf ,
155+ TransactionId * visibility_cutoff_xid );
154156
155157
156158/*
@@ -697,6 +699,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
697699 hastup = false;
698700 prev_dead_count = vacrelstats -> num_dead_tuples ;
699701 maxoff = PageGetMaxOffsetNumber (page );
702+
703+ /*
704+ * Note: If you change anything in the loop below, also look at
705+ * heap_page_is_all_visible to see if that needs to be changed.
706+ */
700707 for (offnum = FirstOffsetNumber ;
701708 offnum <= maxoff ;
702709 offnum = OffsetNumberNext (offnum ))
@@ -881,7 +888,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
881888 vacrelstats -> num_dead_tuples > 0 )
882889 {
883890 /* Remove tuples from heap */
884- lazy_vacuum_page (onerel , blkno , buf , 0 , vacrelstats );
891+ lazy_vacuum_page (onerel , blkno , buf , 0 , vacrelstats , & vmbuffer );
885892
886893 /*
887894 * Forget the now-vacuumed tuples, and press on, but be careful
@@ -1051,6 +1058,7 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
10511058 int tupindex ;
10521059 int npages ;
10531060 PGRUsage ru0 ;
1061+ Buffer vmbuffer = InvalidBuffer ;
10541062
10551063 pg_rusage_init (& ru0 );
10561064 npages = 0 ;
@@ -1074,7 +1082,8 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
10741082 ++ tupindex ;
10751083 continue ;
10761084 }
1077- tupindex = lazy_vacuum_page (onerel , tblk , buf , tupindex , vacrelstats );
1085+ tupindex = lazy_vacuum_page (onerel , tblk , buf , tupindex , vacrelstats ,
1086+ & vmbuffer );
10781087
10791088 /* Now that we've compacted the page, record its available space */
10801089 page = BufferGetPage (buf );
@@ -1085,6 +1094,12 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
10851094 npages ++ ;
10861095 }
10871096
1097+ if (BufferIsValid (vmbuffer ))
1098+ {
1099+ ReleaseBuffer (vmbuffer );
1100+ vmbuffer = InvalidBuffer ;
1101+ }
1102+
10881103 ereport (elevel ,
10891104 (errmsg ("\"%s\": removed %d row versions in %d pages" ,
10901105 RelationGetRelationName (onerel ),
@@ -1105,11 +1120,12 @@ lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats)
11051120 */
11061121static int
11071122lazy_vacuum_page (Relation onerel , BlockNumber blkno , Buffer buffer ,
1108- int tupindex , LVRelStats * vacrelstats )
1123+ int tupindex , LVRelStats * vacrelstats , Buffer * vmbuffer )
11091124{
11101125 Page page = BufferGetPage (buffer );
11111126 OffsetNumber unused [MaxOffsetNumber ];
11121127 int uncnt = 0 ;
1128+ TransactionId visibility_cutoff_xid ;
11131129
11141130 START_CRIT_SECTION ();
11151131
@@ -1130,6 +1146,19 @@ lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
11301146
11311147 PageRepairFragmentation (page );
11321148
1149+ /*
1150+ * Now that we have removed the dead tuples from the page, once again check
1151+ * if the page has become all-visible.
1152+ */
1153+ if (!visibilitymap_test (onerel , blkno , vmbuffer ) &&
1154+ heap_page_is_all_visible (buffer , & visibility_cutoff_xid ))
1155+ {
1156+ Assert (BufferIsValid (* vmbuffer ));
1157+ PageSetAllVisible (page );
1158+ visibilitymap_set (onerel , blkno , InvalidXLogRecPtr , * vmbuffer ,
1159+ visibility_cutoff_xid );
1160+ }
1161+
11331162 MarkBufferDirty (buffer );
11341163
11351164 /* XLOG stuff */
@@ -1633,3 +1662,99 @@ vac_cmp_itemptr(const void *left, const void *right)
16331662
16341663 return 0 ;
16351664}
1665+
1666+ /*
1667+ * Check if every tuple in the given page is visible to all current and future
1668+ * transactions. Also return the visibility_cutoff_xid which is the highest
1669+ * xmin amongst the visible tuples.
1670+ */
1671+ static bool
1672+ heap_page_is_all_visible (Buffer buf , TransactionId * visibility_cutoff_xid )
1673+ {
1674+ Page page = BufferGetPage (buf );
1675+ OffsetNumber offnum ,
1676+ maxoff ;
1677+ bool all_visible = true;
1678+
1679+ * visibility_cutoff_xid = InvalidTransactionId ;
1680+
1681+ /*
1682+ * This is a stripped down version of the line pointer scan in
1683+ * lazy_scan_heap(). So if you change anything here, also check that
1684+ * code.
1685+ */
1686+ maxoff = PageGetMaxOffsetNumber (page );
1687+ for (offnum = FirstOffsetNumber ;
1688+ offnum <= maxoff && all_visible ;
1689+ offnum = OffsetNumberNext (offnum ))
1690+ {
1691+ ItemId itemid ;
1692+ HeapTupleData tuple ;
1693+
1694+ itemid = PageGetItemId (page , offnum );
1695+
1696+ /* Unused or redirect line pointers are of no interest */
1697+ if (!ItemIdIsUsed (itemid ) || ItemIdIsRedirected (itemid ))
1698+ continue ;
1699+
1700+ ItemPointerSet (& (tuple .t_self ), BufferGetBlockNumber (buf ), offnum );
1701+
1702+ /*
1703+ * Dead line pointers can have index pointers pointing to them. So they
1704+ * can't be treated as visible
1705+ */
1706+ if (ItemIdIsDead (itemid ))
1707+ {
1708+ all_visible = false;
1709+ break ;
1710+ }
1711+
1712+ Assert (ItemIdIsNormal (itemid ));
1713+
1714+ tuple .t_data = (HeapTupleHeader ) PageGetItem (page , itemid );
1715+
1716+ switch (HeapTupleSatisfiesVacuum (tuple .t_data , OldestXmin , buf ))
1717+ {
1718+ case HEAPTUPLE_LIVE :
1719+ {
1720+ TransactionId xmin ;
1721+
1722+ /* Check comments in lazy_scan_heap. */
1723+ if (!(tuple .t_data -> t_infomask & HEAP_XMIN_COMMITTED ))
1724+ {
1725+ all_visible = false;
1726+ break ;
1727+ }
1728+
1729+ /*
1730+ * The inserter definitely committed. But is it old
1731+ * enough that everyone sees it as committed?
1732+ */
1733+ xmin = HeapTupleHeaderGetXmin (tuple .t_data );
1734+ if (!TransactionIdPrecedes (xmin , OldestXmin ))
1735+ {
1736+ all_visible = false;
1737+ break ;
1738+ }
1739+
1740+ /* Track newest xmin on page. */
1741+ if (TransactionIdFollows (xmin , * visibility_cutoff_xid ))
1742+ * visibility_cutoff_xid = xmin ;
1743+ }
1744+ break ;
1745+
1746+ case HEAPTUPLE_DEAD :
1747+ case HEAPTUPLE_RECENTLY_DEAD :
1748+ case HEAPTUPLE_INSERT_IN_PROGRESS :
1749+ case HEAPTUPLE_DELETE_IN_PROGRESS :
1750+ all_visible = false;
1751+ break ;
1752+
1753+ default :
1754+ elog (ERROR , "unexpected HeapTupleSatisfiesVacuum result" );
1755+ break ;
1756+ }
1757+ } /* scan along page */
1758+
1759+ return all_visible ;
1760+ }
0 commit comments