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. */