From 9a81ca1da2792a797bed71c1b1336b792fef7d0d Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Sun, 7 Jan 2024 17:55:31 -0500 Subject: [PATCH v5 20/26] Merge prune and freeze records When both pruning and freezing is done, this means a single, combined WAL record is emitted for both operations. This will reduce the number of WAL records emitted. When there are only tuples to freeze present, we can avoid taking a full cleanup lock when replaying the record. --- src/backend/access/heap/pruneheap.c | 230 +++++++++++++++------------- 1 file changed, 121 insertions(+), 109 deletions(-) diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 029d792ed49..04e4a2fdeb4 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -242,9 +242,9 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer, HeapTupleData tup; bool do_freeze; bool do_prune; + bool do_hint; bool whole_page_freezable; bool hint_bit_fpi; - bool prune_fpi = false; int64 fpi_before = pgWalUsage.wal_fpi; /* @@ -444,10 +444,9 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer, /* * If checksums are enabled, heap_prune_satisfies_vacuum() may have caused - * an FPI to be emitted. Then reset fpi_before for no prune case. + * an FPI to be emitted. */ hint_bit_fpi = fpi_before != pgWalUsage.wal_fpi; - fpi_before = pgWalUsage.wal_fpi; /* * For vacuum, if the whole page will become frozen, we consider @@ -497,14 +496,18 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer, prstate.ndead > 0 || prstate.nunused > 0; + /* Record number of newly-set-LP_DEAD items for caller */ + presult->nnewlpdead = prstate.ndead; + /* - * Only incur overhead of checking if we will do an FPI if we might use - * the information. + * Even if we don't prune anything, if we found a new value for the + * pd_prune_xid field or the page was marked full, we will update the hint + * bit. */ - if (do_prune && pagefrz) - prune_fpi = XLogCheckBufferNeedsBackup(buffer); + do_hint = ((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid || + PageIsFull(page); - /* Is the whole page freezable? And is there something to freeze */ + /* Is the whole page freezable? And is there something to freeze? */ whole_page_freezable = presult->all_visible_except_removable && presult->all_frozen; @@ -519,43 +522,51 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer, * opportunistic freeze heuristic must be improved; however, for now, try * to approximate it. */ - do_freeze = pagefrz && - (pagefrz->freeze_required || - (whole_page_freezable && presult->nfrozen > 0 && (prune_fpi || hint_bit_fpi))); + do_freeze = false; + if (pagefrz) + { + if (pagefrz->freeze_required) + do_freeze = true; + else if (whole_page_freezable && presult->nfrozen > 0) + { + /* + * Freezing would make the page all-frozen. In this case, we will + * freeze if we have already emitted an FPI or will do so anyway. + * Be sure only to incur the overhead of checking if we will do an + * FPI if we may use that information. + */ + if (hint_bit_fpi || + ((do_prune || do_hint) && XLogCheckBufferNeedsBackup(buffer))) + { + do_freeze = true; + } + } + } + /* + * Validate the tuples we are considering freezing. We do this even if + * pruning and hint bit setting have not emitted an FPI so far because we + * still may emit an FPI while setting the page hint bit later. But we + * want to avoid doing the pre-freeze checks in a critical section. + */ if (do_freeze) - { heap_pre_freeze_checks(buffer, prstate.frozen, presult->nfrozen); + if (!do_freeze && (!pagefrz || !presult->all_frozen || presult->nfrozen > 0)) + { /* - * We can use frz_conflict_horizon as our cutoff for conflicts when - * the whole page is eligible to become all-frozen in the VM once - * we're done with it. Otherwise we generate a conservative cutoff by - * stepping back from OldestXmin. + * If we will neither freeze tuples on the page nor set the page all + * frozen in the visibility map, the page is not all-frozen and there + * will be no newly frozen tuples. */ - if (!(presult->all_visible_except_removable && presult->all_frozen)) - { - /* Avoids false conflicts when hot_standby_feedback in use */ - presult->frz_conflict_horizon = pagefrz->cutoffs->OldestXmin; - TransactionIdRetreat(presult->frz_conflict_horizon); - } + presult->all_frozen = false; + presult->nfrozen = 0; /* avoid miscounts in instrumentation */ } - /* Any error while applying the changes is critical */ START_CRIT_SECTION(); - /* Have we found any prunable items? */ - if (do_prune) + if (do_hint) { - /* - * Apply the planned item changes, then repair page fragmentation, and - * update the page's hint bit about whether it has free line pointers. - */ - heap_page_prune_execute(buffer, false, - prstate.redirected, prstate.nredirected, - prstate.nowdead, prstate.ndead, - prstate.nowunused, prstate.nunused); - /* * Update the page's pd_prune_xid field to either zero, or the lowest * XID of any soon-prunable tuple. @@ -563,108 +574,109 @@ heap_page_prune_and_freeze(Relation relation, Buffer buffer, ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid; /* - * Also clear the "page is full" flag, since there's no point in - * repeating the prune/defrag process until something else happens to - * the page. + * Clear the "page is full" flag if it is set since there's no point + * in repeating the prune/defrag process until something else happens + * to the page. */ PageClearFull(page); - MarkBufferDirty(buffer); - /* - * Emit a WAL XLOG_HEAP2_PRUNE_FREEZE record showing what we did + * We only needed to update pd_prune_xid and clear the page-is-full + * hint bit, this is a non-WAL-logged hint. If we will also freeze or + * prune the page, we will mark the buffer dirty below. */ - if (RelationNeedsWAL(relation)) - { - log_heap_prune_and_freeze(relation, buffer, - prstate.snapshotConflictHorizon, - false, - NULL, 0, - prstate.redirected, prstate.nredirected, - prstate.nowdead, prstate.ndead, - prstate.nowunused, prstate.nunused); - } + if (!do_freeze && !do_prune) + MarkBufferDirtyHint(buffer, true); } - else + + if (do_prune || do_freeze) { /* - * If we didn't prune anything, but have found a new value for the - * pd_prune_xid field, update it and mark the buffer dirty. This is - * treated as a non-WAL-logged hint. - * - * Also clear the "page is full" flag if it is set, since there's no - * point in repeating the prune/defrag process until something else - * happens to the page. + * Apply the planned item changes, then repair page fragmentation, and + * update the page's hint bit about whether it has free line pointers. */ - if (((PageHeader) page)->pd_prune_xid != prstate.new_prune_xid || - PageIsFull(page)) + if (do_prune) { - ((PageHeader) page)->pd_prune_xid = prstate.new_prune_xid; - PageClearFull(page); - MarkBufferDirtyHint(buffer, true); + heap_page_prune_execute(buffer, false, + prstate.redirected, prstate.nredirected, + prstate.nowdead, prstate.ndead, + prstate.nowunused, prstate.nunused); } - } - - END_CRIT_SECTION(); - /* Record number of newly-set-LP_DEAD items for caller */ - presult->nnewlpdead = prstate.ndead; - - if (do_freeze) - { - START_CRIT_SECTION(); - - Assert(presult->nfrozen > 0); - - heap_freeze_prepared_tuples(buffer, prstate.frozen, presult->nfrozen); + if (do_freeze) + { + /* + * We can use frz_conflict_horizon as our cutoff for conflicts + * when the whole page is eligible to become all-frozen in the VM + * once we're done with it. Otherwise we generate a conservative + * cutoff by stepping back from OldestXmin. This avoids false + * conflicts when hot_standby_feedback is in use. + */ + if (!(presult->all_visible_except_removable && presult->all_frozen)) + { + presult->frz_conflict_horizon = pagefrz->cutoffs->OldestXmin; + TransactionIdRetreat(presult->frz_conflict_horizon); + } + heap_freeze_prepared_tuples(buffer, prstate.frozen, presult->nfrozen); + } MarkBufferDirty(buffer); - /* Now WAL-log freezing if necessary */ + /* + * Emit a WAL XLOG_HEAP2_PRUNE_FREEZE record showing what we did + */ if (RelationNeedsWAL(relation)) + { + /* + * The snapshotConflictHorizon for the whole record should be the most + * conservative of all the horizons calculated for any of the possible + * modifications. If this record will prune tuples, any transactions on + * the standby older than the youngest xmax of the most recently removed + * tuple this record will prune will conflict. If this record will freeze + * tuples, any transactions on the standby with xids older than the + * youngest tuple this record will freeze will conflict. + */ + TransactionId conflict_xid; + + if (do_freeze) + conflict_xid = Max(prstate.snapshotConflictHorizon, + presult->frz_conflict_horizon); + else + conflict_xid = prstate.snapshotConflictHorizon; + log_heap_prune_and_freeze(relation, buffer, - presult->frz_conflict_horizon, false, + conflict_xid, + false, prstate.frozen, presult->nfrozen, - NULL, 0, /* redirected */ - NULL, 0, /* dead */ - NULL, 0); /* unused */ - - END_CRIT_SECTION(); - } - else if (!pagefrz || !presult->all_frozen || presult->nfrozen > 0) - { - /* - * If we will neither freeze tuples on the page nor set the page all - * frozen in the visibility map, the page is not all frozen and there - * will be no newly frozen tuples. - */ - presult->all_frozen = false; - presult->nfrozen = 0; /* avoid miscounts in instrumentation */ + prstate.redirected, prstate.nredirected, + prstate.nowdead, prstate.ndead, + prstate.nowunused, prstate.nunused); + } } - /* Caller won't update new_relfrozenxid and new_relminmxid */ - if (!pagefrz) - return; + END_CRIT_SECTION(); /* - * If we will freeze tuples on the page or, even if we don't freeze tuples - * on the page, if we will set the page all-frozen in the visibility map, - * we can advance relfrozenxid and relminmxid to the values in - * pagefrz->FreezePageRelfrozenXid and pagefrz->FreezePageRelminMxid. + * If we froze tuples on the page, the caller can advance relfrozenxid and + * relminmxid to the values in pagefrz->FreezePageRelfrozenXid and + * pagefrz->FreezePageRelminMxid. Otherwise, it is only safe to advance to + * the values in pagefrz->NoFreezePage[RelfrozenXid|RelminMxid] */ - if (presult->all_frozen || presult->nfrozen > 0) - { - presult->new_relfrozenxid = pagefrz->FreezePageRelfrozenXid; - presult->new_relminmxid = pagefrz->FreezePageRelminMxid; - } - else + if (pagefrz) { - presult->new_relfrozenxid = pagefrz->NoFreezePageRelfrozenXid; - presult->new_relminmxid = pagefrz->NoFreezePageRelminMxid; + if (presult->nfrozen > 0) + { + presult->new_relfrozenxid = pagefrz->FreezePageRelfrozenXid; + presult->new_relminmxid = pagefrz->FreezePageRelminMxid; + } + else + { + presult->new_relfrozenxid = pagefrz->NoFreezePageRelfrozenXid; + presult->new_relminmxid = pagefrz->NoFreezePageRelminMxid; + } } } - /* * Perform visibility checks for heap pruning. */ -- 2.39.2