From 8968994830ed2fe511e938e6d12fba9a3ce4aa65 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 10 Dec 2021 20:00:55 -0800 Subject: [PATCH v4 4/6] wip: vacuum: Perform horizon determination just before starting scan. Author: Reviewed-By: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/access/heap/vacuumlazy.c | 157 ++++++++++++++++----------- 1 file changed, 96 insertions(+), 61 deletions(-) diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 282b44f87bf..5145b9eee4b 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -288,6 +288,8 @@ typedef struct LVRelState bool failsafe_active; /* Consider index vacuuming bypass optimization? */ bool consider_bypass_optimization; + /* Should we scan all unfrozen pages? */ + bool aggressive; /* Doing index vacuuming, index cleanup, rel truncation? */ bool do_index_vacuuming; @@ -308,6 +310,8 @@ typedef struct LVRelState /* VACUUM operation's cutoff for freezing XIDs and MultiXactIds */ TransactionId FreezeLimit; MultiXactId MultiXactCutoff; + /* visibility state for pruning */ + GlobalVisState *vistest; /* Error reporting state */ char *relnamespace; @@ -377,11 +381,10 @@ static int elevel = -1; /* non-export function prototypes */ -static void lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, - bool aggressive); +static void lazy_scan_heap(LVRelState *vacrel, VacuumParams *params); +static void lazy_scan_heap_limits(LVRelState *vacrel, VacuumParams *params); static void lazy_scan_prune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, - GlobalVisState *vistest, LVPagePruneState *prunestate); static void lazy_vacuum(LVRelState *vacrel); static bool lazy_vacuum_all_indexes(LVRelState *vacrel); @@ -417,7 +420,7 @@ static bool should_attempt_truncation(LVRelState *vacrel); static void lazy_truncate_heap(LVRelState *vacrel); static BlockNumber count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected); -static int dead_items_max_items(LVRelState *vacrel); +static int dead_items_max_items(LVRelState *vacrel); static inline Size max_items_to_alloc_size(int max_items); static void dead_items_alloc(LVRelState *vacrel, int nworkers); static void dead_items_cleanup(LVRelState *vacrel); @@ -465,11 +468,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, int usecs; double read_rate, write_rate; - bool aggressive; /* should we scan all unfrozen pages? */ bool scanned_all_unfrozen; /* actually scanned all such pages? */ char **indnames = NULL; - TransactionId xidFullScanLimit; - MultiXactId mxactFullScanLimit; BlockNumber new_rel_pages; BlockNumber new_rel_allvisible; double new_live_tuples; @@ -478,9 +478,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, ErrorContextCallback errcallback; PgStat_Counter startreadtime = 0; PgStat_Counter startwritetime = 0; - TransactionId OldestXmin; - TransactionId FreezeLimit; - MultiXactId MultiXactCutoff; /* measure elapsed time iff autovacuum logging requires it */ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0) @@ -502,27 +499,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM, RelationGetRelid(rel)); - vacuum_set_xid_limits(rel, - params->freeze_min_age, - params->freeze_table_age, - params->multixact_freeze_min_age, - params->multixact_freeze_table_age, - &OldestXmin, &FreezeLimit, &xidFullScanLimit, - &MultiXactCutoff, &mxactFullScanLimit); - - /* - * We request an aggressive scan if the table's frozen Xid is now older - * than or equal to the requested Xid full-table scan limit; or if the - * table's minimum MultiXactId is older than or equal to the requested - * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified. - */ - aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid, - xidFullScanLimit); - aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid, - mxactFullScanLimit); - if (params->options & VACOPT_DISABLE_PAGE_SKIPPING) - aggressive = true; - vacrel = (LVRelState *) palloc0(sizeof(LVRelState)); /* Set up high level stuff about rel */ @@ -569,11 +545,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->relminmxid = rel->rd_rel->relminmxid; vacrel->old_live_tuples = rel->rd_rel->reltuples; - /* Set cutoffs for entire VACUUM */ - vacrel->OldestXmin = OldestXmin; - vacrel->FreezeLimit = FreezeLimit; - vacrel->MultiXactCutoff = MultiXactCutoff; - vacrel->relnamespace = get_namespace_name(RelationGetNamespace(rel)); vacrel->relname = pstrdup(RelationGetRelationName(rel)); vacrel->indname = NULL; @@ -609,7 +580,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * Call lazy_scan_heap to perform all required heap pruning, index * vacuuming, and heap vacuuming (plus related processing) */ - lazy_scan_heap(vacrel, params, aggressive); + lazy_scan_heap(vacrel, params); /* Done with indexes */ vac_close_indexes(vacrel->nindexes, vacrel->indrels, NoLock); @@ -624,7 +595,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, if ((vacrel->scanned_pages + vacrel->frozenskipped_pages) < vacrel->rel_pages) { - Assert(!aggressive); + Assert(!vacrel->aggressive); scanned_all_unfrozen = false; } else @@ -674,8 +645,16 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, if (new_rel_allvisible > new_rel_pages) new_rel_allvisible = new_rel_pages; - new_frozen_xid = scanned_all_unfrozen ? FreezeLimit : InvalidTransactionId; - new_min_multi = scanned_all_unfrozen ? MultiXactCutoff : InvalidMultiXactId; + if (scanned_all_unfrozen) + { + new_frozen_xid = vacrel->FreezeLimit; + new_min_multi = vacrel->MultiXactCutoff; + } + else + { + new_frozen_xid = InvalidTransactionId; + new_min_multi = InvalidMultiXactId; + } vac_update_relstats(rel, new_rel_pages, @@ -743,14 +722,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * implies aggressive. Produce distinct output for the corner * case all the same, just in case. */ - if (aggressive) + if (vacrel->aggressive) msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); else msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); } else { - if (aggressive) + if (vacrel->aggressive) msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n"); else msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n"); @@ -770,7 +749,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, (long long) vacrel->tuples_deleted, (long long) vacrel->new_rel_tuples, (long long) vacrel->new_dead_tuples, - OldestXmin); + vacrel->OldestXmin); orig_rel_pages = vacrel->rel_pages + vacrel->pages_removed; if (orig_rel_pages > 0) { @@ -888,7 +867,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * supply. */ static void -lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) +lazy_scan_heap(LVRelState *vacrel, VacuumParams *params) { LVDeadItems *dead_items; BlockNumber nblocks, @@ -906,21 +885,10 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) PROGRESS_VACUUM_MAX_DEAD_TUPLES }; int64 initprog_val[3]; - GlobalVisState *vistest; + bool aggressive; pg_rusage_init(&ru0); - if (aggressive) - ereport(elevel, - (errmsg("aggressively vacuuming \"%s.%s\"", - vacrel->relnamespace, - vacrel->relname))); - else - ereport(elevel, - (errmsg("vacuuming \"%s.%s\"", - vacrel->relnamespace, - vacrel->relname))); - nblocks = RelationGetNumberOfBlocks(vacrel->rel); next_unskippable_block = 0; next_failsafe_block = 0; @@ -942,8 +910,6 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) vacrel->num_tuples = 0; vacrel->live_tuples = 0; - vistest = GlobalVisTestFor(vacrel->rel); - vacrel->indstats = (IndexBulkDeleteResult **) palloc0(vacrel->nindexes * sizeof(IndexBulkDeleteResult *)); @@ -968,6 +934,28 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) initprog_val[2] = dead_items->max_items; pgstat_progress_update_multi_param(3, initprog_index, initprog_val); + /* + * Compute vacuuming XID cutoffs. + * + * We do this as late as possible, as the limits determine how much work + * can be done now / how much has to be deferred till later. + * vac_open_indexes() may block, RelationGetNumberOfBlocks() can take a + * while on large relations, dead_items_alloc() isn't cheap, ... + */ + lazy_scan_heap_limits(vacrel, params); + aggressive = vacrel->aggressive; + + if (aggressive) + ereport(elevel, + (errmsg("aggressively vacuuming \"%s.%s\"", + vacrel->relnamespace, + vacrel->relname))); + else + ereport(elevel, + (errmsg("vacuuming \"%s.%s\"", + vacrel->relnamespace, + vacrel->relname))); + /* * Except when aggressive is set, we want to skip pages that are * all-visible according to the visibility map, but only when we can skip @@ -1364,7 +1352,7 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) * were pruned some time earlier. Also considers freezing XIDs in the * tuple headers of remaining items with storage. */ - lazy_scan_prune(vacrel, buf, blkno, page, vistest, &prunestate); + lazy_scan_prune(vacrel, buf, blkno, page, &prunestate); Assert(!prunestate.all_visible || !prunestate.has_lpdead_items); @@ -1683,7 +1671,6 @@ lazy_scan_prune(LVRelState *vacrel, Buffer buf, BlockNumber blkno, Page page, - GlobalVisState *vistest, LVPagePruneState *prunestate) { Relation rel = vacrel->rel; @@ -1722,7 +1709,7 @@ retry: * lpdead_items's final value can be thought of as the number of tuples * that were deleted from indexes. */ - tuples_deleted = heap_page_prune(rel, buf, vistest, + tuples_deleted = heap_page_prune(rel, buf, vacrel->vistest, InvalidTransactionId, 0, &nnewlpdead, &vacrel->offnum); @@ -2184,6 +2171,54 @@ lazy_vacuum(LVRelState *vacrel) vacrel->dead_items->num_items = 0; } +/* + * Helper for lazy_scan_heap(), determining xid limits (vacrel->OldestXmin, + * vacrel->MultiXactCutoff) and whether the full relation should be scanned + * (vacrel->aggressive). + */ +static void +lazy_scan_heap_limits(LVRelState *vacrel, VacuumParams *params) +{ + TransactionId xidFullScanLimit; + MultiXactId mxactFullScanLimit; + bool aggressive; + + vacuum_set_xid_limits(vacrel->rel, + params->freeze_min_age, + params->freeze_table_age, + params->multixact_freeze_min_age, + params->multixact_freeze_table_age, + &vacrel->OldestXmin, &vacrel->FreezeLimit, + &xidFullScanLimit, + &vacrel->MultiXactCutoff, + &mxactFullScanLimit); + + /* + * We request an aggressive scan if the table's frozen Xid is now older + * than or equal to the requested Xid full-table scan limit; or if the + * table's minimum MultiXactId is older than or equal to the requested + * mxid full-table scan limit; or if DISABLE_PAGE_SKIPPING was specified. + */ + aggressive = TransactionIdPrecedesOrEquals(vacrel->rel->rd_rel->relfrozenxid, + xidFullScanLimit); + aggressive |= MultiXactIdPrecedesOrEquals(vacrel->rel->rd_rel->relminmxid, + mxactFullScanLimit); + if (params->options & VACOPT_DISABLE_PAGE_SKIPPING) + aggressive = true; + vacrel->aggressive = aggressive; + + /* + * heap_prune_page() uses vacrel->vistest for visibility (primarily for + * compatibility with on-access pruning, but also because it allows to + * prune more). vacrel->vistest is always at least as aggressive as the + * limits vacuum_set_xid_limits() computes because ComputeXidHorizons() + * (via vacuum_set_xid_limits() ->GetOldestNonRemovableTransactionId()) + * ensures the approximate horizons are always at least as aggressive as + * the precise horizons. + */ + vacrel->vistest = GlobalVisTestFor(vacrel->rel); +} + /* * lazy_vacuum_all_indexes() -- Main entry for index vacuuming * -- 2.34.0