hot-prune-set-all-visible-v2.patch
application/octet-stream
Filename: hot-prune-set-all-visible-v2.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| src/backend/access/heap/pruneheap.c | 95 | 1 |
| src/backend/commands/vacuumlazy.c | 13 | 0 |
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.
*/