From 083690b946e19ab5e536a9f2689772e7b91d2a70 Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Fri, 29 Mar 2024 21:22:14 -0400 Subject: [PATCH v11 4/7] Prepare freeze tuples in heap_page_prune() In order to combine the freeze and prune records, we must determine which tuples are freezable before actually executing pruning. All of the page modifications should be made in the same critical section along with emitting the combined WAL. Determine whether or not tuples should or must be frozen and whether or not the page will be all frozen as a consequence during pruning. --- src/backend/access/heap/heapam.c | 6 +-- src/backend/access/heap/pruneheap.c | 64 +++++++++++++++++++++----- src/backend/access/heap/vacuumlazy.c | 67 ++++++++++------------------ src/include/access/heapam.h | 25 ++++++++++- 4 files changed, 103 insertions(+), 59 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index b661d9811eb..c5b52978380 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -6477,10 +6477,10 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, */ bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, - const struct VacuumCutoffs *cutoffs, HeapPageFreeze *pagefrz, HeapTupleFreeze *frz, bool *totally_frozen) { + const struct VacuumCutoffs *cutoffs = pagefrz->cutoffs; bool xmin_already_frozen = false, xmax_already_frozen = false; bool freeze_xmin = false, @@ -6891,9 +6891,9 @@ heap_freeze_tuple(HeapTupleHeader tuple, pagefrz.FreezePageRelminMxid = MultiXactCutoff; pagefrz.NoFreezePageRelfrozenXid = FreezeLimit; pagefrz.NoFreezePageRelminMxid = MultiXactCutoff; + pagefrz.cutoffs = &cutoffs; - do_freeze = heap_prepare_freeze_tuple(tuple, &cutoffs, - &pagefrz, &frz, &totally_frozen); + do_freeze = heap_prepare_freeze_tuple(tuple, &pagefrz, &frz, &totally_frozen); /* * Note that because this is not a WAL-logged operation, we don't need to diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 30965c3c5a1..8bdd6389b25 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -17,6 +17,7 @@ #include "access/heapam.h" #include "access/heapam_xlog.h" #include "access/htup_details.h" +#include "access/multixact.h" #include "access/transam.h" #include "access/xlog.h" #include "access/xloginsert.h" @@ -72,7 +73,7 @@ static HTSV_Result heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer); static void heap_prune_chain(Page page, BlockNumber blockno, OffsetNumber maxoff, - OffsetNumber rootoffnum, int8 *htsv, PruneState *prstate); + OffsetNumber rootoffnum, int8 *htsv, PruneState *prstate, PruneResult *presult); static void heap_prune_record_prunable(PruneState *prstate, TransactionId xid); static void heap_prune_record_redirect(PruneState *prstate, OffsetNumber offnum, OffsetNumber rdoffnum, bool was_normal); @@ -81,7 +82,7 @@ static void heap_prune_record_dead_or_unused(PruneState *prstate, OffsetNumber o static void heap_prune_record_unused(PruneState *prstate, OffsetNumber offnum, bool was_normal); static void heap_prune_record_unchanged_lp_unused(Page page, PruneState *prstate, OffsetNumber offnum); -static void heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate, OffsetNumber offnum); +static void heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate, PruneResult *presult, OffsetNumber offnum); static void heap_prune_record_unchanged_lp_dead(Page page, PruneState *prstate, OffsetNumber offnum); static void heap_prune_record_unchanged_lp_redirect(PruneState *prstate, OffsetNumber offnum); @@ -166,6 +167,13 @@ heap_page_prune_opt(Relation relation, Buffer buffer) OffsetNumber dummy_off_loc; PruneResult presult; + presult.pagefrz.freeze_required = false; + presult.pagefrz.FreezePageRelfrozenXid = InvalidTransactionId; + presult.pagefrz.FreezePageRelminMxid = InvalidMultiXactId; + presult.pagefrz.NoFreezePageRelfrozenXid = InvalidTransactionId; + presult.pagefrz.NoFreezePageRelminMxid = InvalidMultiXactId; + presult.pagefrz.cutoffs = NULL; + /* * For now, do not set PRUNE_DO_MARK_UNUSED_NOW regardless of * whether or not the relation has indexes, since we cannot safely @@ -264,6 +272,16 @@ heap_page_prune(Relation relation, Buffer buffer, prstate.nroot_items = 0; prstate.nheaponly_items = 0; + /* + * If we will prepare to freeze tuples, consider that it might be possible + * to set the page all-frozen in the visibility map. + */ + if (prstate.actions & PRUNE_DO_TRY_FREEZE) + presult->all_frozen = true; + else + presult->all_frozen = false; + + /* * presult->htsv is not initialized here because all ntuple spots in the * array will be set either to a valid HTSV_Result value or -1. @@ -271,6 +289,8 @@ heap_page_prune(Relation relation, Buffer buffer, presult->ndeleted = 0; presult->nnewlpdead = 0; + presult->nfrozen = 0; + maxoff = PageGetMaxOffsetNumber(page); tup.t_tableOid = RelationGetRelid(relation); @@ -371,7 +391,7 @@ heap_page_prune(Relation relation, Buffer buffer, /* Process this item or chain of items */ heap_prune_chain(page, blockno, maxoff, - offnum, presult->htsv, &prstate); + offnum, presult->htsv, &prstate, presult); } /* @@ -421,7 +441,7 @@ heap_page_prune(Relation relation, Buffer buffer, * HOT-updated member of a chain, it should have already been * processed by heap_prune_chain(). */ - heap_prune_record_unchanged_lp_normal(page, presult->htsv, &prstate, offnum); + heap_prune_record_unchanged_lp_normal(page, presult->htsv, &prstate, presult, offnum); } /* We should now have processed every tuple exactly once */ @@ -559,7 +579,7 @@ heap_prune_satisfies_vacuum(PruneState *prstate, HeapTuple tup, Buffer buffer) */ static void heap_prune_chain(Page page, BlockNumber blockno, OffsetNumber maxoff, - OffsetNumber rootoffnum, int8 *htsv, PruneState *prstate) + OffsetNumber rootoffnum, int8 *htsv, PruneState *prstate, PruneResult *presult) { TransactionId priorXmax = InvalidTransactionId; ItemId rootlp; @@ -728,7 +748,7 @@ process_chain: i++; } for (; i < nchain; i++) - heap_prune_record_unchanged_lp_normal(page, htsv, prstate, chainitems[i]); + heap_prune_record_unchanged_lp_normal(page, htsv, prstate, presult, chainitems[i]); } else if (ndeadchain == nchain) { @@ -754,7 +774,7 @@ process_chain: /* the rest of tuples in the chain are normal, unchanged tuples */ for (int i = ndeadchain; i < nchain; i++) - heap_prune_record_unchanged_lp_normal(page, htsv, prstate, chainitems[i]); + heap_prune_record_unchanged_lp_normal(page, htsv, prstate, presult, chainitems[i]); } } @@ -878,9 +898,10 @@ heap_prune_record_unchanged_lp_unused(Page page, PruneState *prstate, OffsetNumb * Record LP_NORMAL line pointer that is left unchanged. */ static void -heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate, OffsetNumber offnum) +heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate, + PruneResult *presult, OffsetNumber offnum) { - HeapTupleHeader htup; + HeapTupleHeader htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum)); Assert(!prstate->processed[offnum]); prstate->processed[offnum] = true; @@ -901,8 +922,6 @@ heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate case HEAPTUPLE_RECENTLY_DEAD: case HEAPTUPLE_DELETE_IN_PROGRESS: - htup = (HeapTupleHeader) PageGetItem(page, PageGetItemId(page, offnum)); - /* * This tuple may soon become DEAD. Update the hint field so that * the page is reconsidered for pruning in future. @@ -921,6 +940,29 @@ heap_prune_record_unchanged_lp_normal(Page page, int8 *htsv, PruneState *prstate elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result %d", htsv[offnum]); break; } + + /* Consider freezing any normal tuples which will not be removed */ + if (prstate->actions & PRUNE_DO_TRY_FREEZE) + { + /* Tuple with storage -- consider need to freeze */ + bool totally_frozen; + + if ((heap_prepare_freeze_tuple(htup, &presult->pagefrz, + &presult->frozen[presult->nfrozen], + &totally_frozen))) + { + /* Save prepared freeze plan for later */ + presult->frozen[presult->nfrozen++].offset = offnum; + } + + /* + * If any tuple isn't either totally frozen already or eligible to + * become totally frozen (according to its freeze plan), then the page + * definitely cannot be set all-frozen in the visibility map later on + */ + if (!totally_frozen) + presult->all_frozen = false; + } } diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 880a218cb4d..679c6a866ea 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -1416,19 +1416,15 @@ lazy_scan_prune(LVRelState *vacrel, maxoff; ItemId itemid; PruneResult presult; - int tuples_frozen, - lpdead_items, + int lpdead_items, live_tuples, recently_dead_tuples; - HeapPageFreeze pagefrz; bool hastup = false; - bool all_visible, - all_frozen; + bool all_visible; TransactionId visibility_cutoff_xid; uint8 actions = 0; int64 fpi_before = pgWalUsage.wal_fpi; OffsetNumber deadoffsets[MaxHeapTuplesPerPage]; - HeapTupleFreeze frozen[MaxHeapTuplesPerPage]; Assert(BufferGetBlockNumber(buf) == blkno); @@ -1440,12 +1436,12 @@ lazy_scan_prune(LVRelState *vacrel, maxoff = PageGetMaxOffsetNumber(page); /* Initialize (or reset) page-level state */ - pagefrz.freeze_required = false; - pagefrz.FreezePageRelfrozenXid = vacrel->NewRelfrozenXid; - pagefrz.FreezePageRelminMxid = vacrel->NewRelminMxid; - pagefrz.NoFreezePageRelfrozenXid = vacrel->NewRelfrozenXid; - pagefrz.NoFreezePageRelminMxid = vacrel->NewRelminMxid; - tuples_frozen = 0; + presult.pagefrz.freeze_required = false; + presult.pagefrz.FreezePageRelfrozenXid = vacrel->NewRelfrozenXid; + presult.pagefrz.FreezePageRelminMxid = vacrel->NewRelminMxid; + presult.pagefrz.NoFreezePageRelfrozenXid = vacrel->NewRelfrozenXid; + presult.pagefrz.NoFreezePageRelminMxid = vacrel->NewRelminMxid; + presult.pagefrz.cutoffs = &vacrel->cutoffs; lpdead_items = 0; live_tuples = 0; recently_dead_tuples = 0; @@ -1462,6 +1458,7 @@ lazy_scan_prune(LVRelState *vacrel, * items LP_UNUSED, so PRUNE_DO_MARK_UNUSED_NOW should be set if no * indexes and unset otherwise. */ + actions |= PRUNE_DO_TRY_FREEZE; if (vacrel->nindexes == 0) actions |= PRUNE_DO_MARK_UNUSED_NOW; @@ -1479,7 +1476,6 @@ lazy_scan_prune(LVRelState *vacrel, * Also keep track of the visibility cutoff xid for recovery conflicts. */ all_visible = true; - all_frozen = true; visibility_cutoff_xid = InvalidTransactionId; /* @@ -1491,7 +1487,6 @@ lazy_scan_prune(LVRelState *vacrel, offnum = OffsetNumberNext(offnum)) { HeapTupleHeader htup; - bool totally_frozen; /* * Set the offset number so that we can display it along with any @@ -1638,22 +1633,6 @@ lazy_scan_prune(LVRelState *vacrel, } hastup = true; /* page makes rel truncation unsafe */ - - /* Tuple with storage -- consider need to freeze */ - if (heap_prepare_freeze_tuple(htup, &vacrel->cutoffs, &pagefrz, - &frozen[tuples_frozen], &totally_frozen)) - { - /* Save prepared freeze plan for later */ - frozen[tuples_frozen++].offset = offnum; - } - - /* - * If any tuple isn't either totally frozen already or eligible to - * become totally frozen (according to its freeze plan), then the page - * definitely cannot be set all-frozen in the visibility map later on - */ - if (!totally_frozen) - all_frozen = false; } /* @@ -1670,18 +1649,18 @@ lazy_scan_prune(LVRelState *vacrel, * freeze when pruning generated an FPI, if doing so means that we set the * page all-frozen afterwards (might not happen until final heap pass). */ - if (pagefrz.freeze_required || tuples_frozen == 0 || - (all_visible && all_frozen && + if (presult.pagefrz.freeze_required || presult.nfrozen == 0 || + (all_visible && presult.all_frozen && fpi_before != pgWalUsage.wal_fpi)) { /* * We're freezing the page. Our final NewRelfrozenXid doesn't need to * be affected by the XIDs that are just about to be frozen anyway. */ - vacrel->NewRelfrozenXid = pagefrz.FreezePageRelfrozenXid; - vacrel->NewRelminMxid = pagefrz.FreezePageRelminMxid; + vacrel->NewRelfrozenXid = presult.pagefrz.FreezePageRelfrozenXid; + vacrel->NewRelminMxid = presult.pagefrz.FreezePageRelminMxid; - if (tuples_frozen == 0) + if (presult.nfrozen == 0) { /* * We have no freeze plans to execute, so there's no added cost @@ -1709,7 +1688,7 @@ lazy_scan_prune(LVRelState *vacrel, * once we're done with it. Otherwise we generate a conservative * cutoff by stepping back from OldestXmin. */ - if (all_visible && all_frozen) + if (all_visible && presult.all_frozen) { /* Using same cutoff when setting VM is now unnecessary */ snapshotConflictHorizon = visibility_cutoff_xid; @@ -1725,7 +1704,7 @@ lazy_scan_prune(LVRelState *vacrel, /* Execute all freeze plans for page as a single atomic action */ heap_freeze_execute_prepared(vacrel->rel, buf, snapshotConflictHorizon, - frozen, tuples_frozen); + presult.frozen, presult.nfrozen); } } else @@ -1734,10 +1713,10 @@ lazy_scan_prune(LVRelState *vacrel, * Page requires "no freeze" processing. It might be set all-visible * in the visibility map, but it can never be set all-frozen. */ - vacrel->NewRelfrozenXid = pagefrz.NoFreezePageRelfrozenXid; - vacrel->NewRelminMxid = pagefrz.NoFreezePageRelminMxid; - all_frozen = false; - tuples_frozen = 0; /* avoid miscounts in instrumentation */ + vacrel->NewRelfrozenXid = presult.pagefrz.NoFreezePageRelfrozenXid; + vacrel->NewRelminMxid = presult.pagefrz.NoFreezePageRelminMxid; + presult.all_frozen = false; + presult.nfrozen = 0; /* avoid miscounts in instrumentation */ } /* @@ -1801,7 +1780,7 @@ lazy_scan_prune(LVRelState *vacrel, /* Finally, add page-local counts to whole-VACUUM counts */ vacrel->tuples_deleted += presult.ndeleted; - vacrel->tuples_frozen += tuples_frozen; + vacrel->tuples_frozen += presult.nfrozen; vacrel->lpdead_items += lpdead_items; vacrel->live_tuples += live_tuples; vacrel->recently_dead_tuples += recently_dead_tuples; @@ -1824,7 +1803,7 @@ lazy_scan_prune(LVRelState *vacrel, { uint8 flags = VISIBILITYMAP_ALL_VISIBLE; - if (all_frozen) + if (presult.all_frozen) { Assert(!TransactionIdIsValid(visibility_cutoff_xid)); flags |= VISIBILITYMAP_ALL_FROZEN; @@ -1895,7 +1874,7 @@ lazy_scan_prune(LVRelState *vacrel, * true, so we must check both all_visible and all_frozen. */ else if (all_visible_according_to_vm && all_visible && - all_frozen && !VM_ALL_FROZEN(vacrel->rel, blkno, &vmbuffer)) + presult.all_frozen && !VM_ALL_FROZEN(vacrel->rel, blkno, &vmbuffer)) { /* * Avoid relying on all_visible_according_to_vm as a proxy for the diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 35b8486c34a..ac129692c13 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -189,6 +189,7 @@ typedef struct HeapPageFreeze TransactionId NoFreezePageRelfrozenXid; MultiXactId NoFreezePageRelminMxid; + struct VacuumCutoffs *cutoffs; } HeapPageFreeze; /* @@ -202,6 +203,15 @@ typedef struct HeapPageFreeze */ #define PRUNE_DO_MARK_UNUSED_NOW (1 << 1) +/* + * Prepare to freeze if advantageous or required and try to advance + * relfrozenxid and relminmxid. To attempt freezing, we will need to determine + * if the page is all frozen. So, if this action is set, we will also inform + * the caller if the page is all-visible and/or all-frozen and calculate a + * snapshot conflict horizon for updating the visibility map. + */ +#define PRUNE_DO_TRY_FREEZE (1 << 2) + /* * Per-page state returned from pruning */ @@ -220,6 +230,20 @@ typedef struct PruneResult * 1. Otherwise every access would need to subtract 1. */ int8 htsv[MaxHeapTuplesPerPage + 1]; + + /* + * Prepare to freeze in heap_page_prune(). lazy_scan_prune() will use the + * returned freeze plans to execute freezing. + */ + HeapPageFreeze pagefrz; + + /* + * Whether or not the page can be set all-frozen in the visibility map. + * This is only set if the PRUNE_DO_TRY_FREEZE action flag is set. + */ + bool all_frozen; + int nfrozen; + HeapTupleFreeze frozen[MaxHeapTuplesPerPage]; } PruneResult; /* 'reason' codes for heap_page_prune() */ @@ -314,7 +338,6 @@ extern TM_Result heap_lock_tuple(Relation relation, ItemPointer tid, extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, - const struct VacuumCutoffs *cutoffs, HeapPageFreeze *pagefrz, HeapTupleFreeze *frz, bool *totally_frozen); extern void heap_freeze_execute_prepared(Relation rel, Buffer buffer, -- 2.39.2