From 616e02c0e9a2cc5c098387d120751ac8adfa20b1 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Tue, 31 Mar 2026 18:25:30 +0000 Subject: [PATCH v6 1/4] Always compute autovacuum priority scores Previously, XID/MXID age scores were only computed when a table was at wraparound risk, and threshold-based scores were only computed when autovacuum was globally active. This meant the scores were unavailable for tables not yet at risk or with autovacuum disabled. A future patch to monitor the scores and the need for vacuum/analyze must be able to compute these values regardless of autovacuum state on the table. Therefore, separate the need for vacuum and analyze and track them in AutoVacuumScores so they can be used for this purpose rather than the output dovacuum, doanalyze and wraparound parameters that are acted upon by autovacuum. Discussion: https://postgr.es/m/CAA5RZ0s4xjMrB-VAnLccC7kY8d0-4806-Lsac-czJsdA1LXtAw%40mail.gmail.com --- src/backend/postmaster/autovacuum.c | 159 +++++++++++++++------------- 1 file changed, 87 insertions(+), 72 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6694f485216..f409a5d9028 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -318,8 +318,9 @@ static MemoryContext DatabaseListCxt = NULL; /* * This struct is used by relation_needs_vacanalyze() to return the table's - * score (i.e., the maximum of the component scores) as well as the component - * scores themselves. + * autovacuum priority, including the overall score (i.e., the maximum of the + * component scores), the component scores themselves, and whether the table + * needs vacuum, analyze, or is at risk of wraparound. */ typedef struct { @@ -329,6 +330,9 @@ typedef struct double vac; /* vacuum component */ double vac_ins; /* vacuum insert component */ double anl; /* analyze component */ + bool needs_vacuum; /* threshold exceeded for vacuum */ + bool needs_analyze; /* threshold exceeded for analyze */ + bool is_wraparound; /* at risk of XID/MXID wraparound */ } AutoVacuumScores; /* @@ -3080,6 +3084,10 @@ recheck_relation_needs_vacanalyze(Oid relid, * The autovacuum table score is returned in scores->max. The component scores * are also returned in the "scores" argument via the other members of the * AutoVacuumScores struct. + * + * All fields in AutoVacuumScores are always computed regardless of autovacuum + * settings. The dovacuum and doanalyze output parameters are only set when + * autovacuum is globally active and enabled for the relation. */ static void relation_needs_vacanalyze(Oid relid, @@ -3122,6 +3130,10 @@ relation_needs_vacanalyze(Oid relid, TransactionId relfrozenxid; MultiXactId relminmxid; MultiXactId multiForceLimit; + uint32 xid_age; + uint32 mxid_age; + int effective_xid_failsafe_age; + int effective_mxid_failsafe_age; Assert(classForm != NULL); Assert(OidIsValid(relid)); @@ -3196,74 +3208,64 @@ relation_needs_vacanalyze(Oid relid, } *wraparound = force_vacuum; - /* Update the score. */ - if (force_vacuum) - { - uint32 xid_age; - uint32 mxid_age; - int effective_xid_failsafe_age; - int effective_mxid_failsafe_age; + /* + * To calculate the (M)XID age portion of the score, divide the age by its + * respective *_freeze_max_age parameter. + */ + xid_age = TransactionIdIsNormal(relfrozenxid) ? recentXid - relfrozenxid : 0; + mxid_age = MultiXactIdIsValid(relminmxid) ? recentMulti - relminmxid : 0; - /* - * To calculate the (M)XID age portion of the score, divide the age by - * its respective *_freeze_max_age parameter. - */ - xid_age = TransactionIdIsNormal(relfrozenxid) ? recentXid - relfrozenxid : 0; - mxid_age = MultiXactIdIsValid(relminmxid) ? recentMulti - relminmxid : 0; + scores->xid = (double) xid_age / freeze_max_age; + scores->mxid = (double) mxid_age / multixact_freeze_max_age; - scores->xid = (double) xid_age / freeze_max_age; - scores->mxid = (double) mxid_age / multixact_freeze_max_age; + /* + * To ensure tables are given increased priority once they begin + * approaching wraparound, we scale the score aggressively if the ages + * surpass vacuum_failsafe_age or vacuum_multixact_failsafe_age. + * + * As in vacuum_xid_failsafe_check(), the effective failsafe age is no + * less than 105% the value of the respective *_freeze_max_age parameter. + * Note that per-table settings could result in a low score even if the + * table surpasses the failsafe settings. However, this is a strange + * enough corner case that we don't bother trying to handle it. + * + * We further adjust the effective failsafe ages with the weight + * parameters so that increasing them lowers the ages at which we begin + * scaling aggressively. + */ + effective_xid_failsafe_age = Max(vacuum_failsafe_age, + autovacuum_freeze_max_age * 1.05); + effective_mxid_failsafe_age = Max(vacuum_multixact_failsafe_age, + autovacuum_multixact_freeze_max_age * 1.05); - /* - * To ensure tables are given increased priority once they begin - * approaching wraparound, we scale the score aggressively if the ages - * surpass vacuum_failsafe_age or vacuum_multixact_failsafe_age. - * - * As in vacuum_xid_failsafe_check(), the effective failsafe age is no - * less than 105% the value of the respective *_freeze_max_age - * parameter. Note that per-table settings could result in a low - * score even if the table surpasses the failsafe settings. However, - * this is a strange enough corner case that we don't bother trying to - * handle it. - * - * We further adjust the effective failsafe ages with the weight - * parameters so that increasing them lowers the ages at which we - * begin scaling aggressively. - */ - effective_xid_failsafe_age = Max(vacuum_failsafe_age, - autovacuum_freeze_max_age * 1.05); - effective_mxid_failsafe_age = Max(vacuum_multixact_failsafe_age, - autovacuum_multixact_freeze_max_age * 1.05); + if (autovacuum_freeze_score_weight > 1.0) + effective_xid_failsafe_age /= autovacuum_freeze_score_weight; + if (autovacuum_multixact_freeze_score_weight > 1.0) + effective_mxid_failsafe_age /= autovacuum_multixact_freeze_score_weight; - if (autovacuum_freeze_score_weight > 1.0) - effective_xid_failsafe_age /= autovacuum_freeze_score_weight; - if (autovacuum_multixact_freeze_score_weight > 1.0) - effective_mxid_failsafe_age /= autovacuum_multixact_freeze_score_weight; + if (xid_age >= effective_xid_failsafe_age) + scores->xid = pow(scores->xid, Max(1.0, (double) xid_age / 100000000)); + if (mxid_age >= effective_mxid_failsafe_age) + scores->mxid = pow(scores->mxid, Max(1.0, (double) mxid_age / 100000000)); - if (xid_age >= effective_xid_failsafe_age) - scores->xid = pow(scores->xid, Max(1.0, (double) xid_age / 100000000)); - if (mxid_age >= effective_mxid_failsafe_age) - scores->mxid = pow(scores->mxid, Max(1.0, (double) mxid_age / 100000000)); + scores->xid *= autovacuum_freeze_score_weight; + scores->mxid *= autovacuum_multixact_freeze_score_weight; - scores->xid *= autovacuum_freeze_score_weight; - scores->mxid *= autovacuum_multixact_freeze_score_weight; + scores->max = Max(scores->xid, scores->mxid); - scores->max = Max(scores->xid, scores->mxid); + if (force_vacuum) + { *dovacuum = true; + scores->is_wraparound = scores->needs_vacuum = true; } - /* User disabled it in pg_class.reloptions? (But ignore if at risk) */ - if (!av_enabled && !force_vacuum) - return; - /* - * If we found stats for the table, and autovacuum is currently enabled, - * make a threshold-based decision whether to vacuum and/or analyze. If - * autovacuum is currently disabled, we must be here for anti-wraparound - * vacuuming only, so don't vacuum (or analyze) anything that's not being - * forced. + * If we found stats for the table, make a threshold-based decision + * whether to vacuum and/or analyze, and compute the corresponding scores. + * dovacuum/doanalyze are only set when autovacuum is active and enabled + * for this table. */ - if (tabentry && AutoVacuumingActive()) + if (tabentry) { float4 pcnt_unfrozen = 1; float4 reltuples = classForm->reltuples; @@ -3304,37 +3306,50 @@ relation_needs_vacanalyze(Oid relid, anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; /* - * Determine if this table needs vacuum, and update the score if it - * does. + * Update the vacuum score and determine if this table requires + * vacuuming. */ + scores->vac = (double) vactuples / Max(vacthresh, 1); + scores->vac *= autovacuum_vacuum_score_weight; + scores->max = Max(scores->max, scores->vac); if (vactuples > vacthresh) { - scores->vac = (double) vactuples / Max(vacthresh, 1); - scores->vac *= autovacuum_vacuum_score_weight; - scores->max = Max(scores->max, scores->vac); - *dovacuum = true; + scores->needs_vacuum = true; + if (av_enabled && AutoVacuumingActive()) + *dovacuum = true; } - if (vac_ins_base_thresh >= 0 && instuples > vacinsthresh) + /* + * Ditto for vacuum insert score when it is applicable. + */ + if (vac_ins_base_thresh >= 0) { scores->vac_ins = (double) instuples / Max(vacinsthresh, 1); scores->vac_ins *= autovacuum_vacuum_insert_score_weight; scores->max = Max(scores->max, scores->vac_ins); - *dovacuum = true; + + if (instuples > vacinsthresh) + { + scores->needs_vacuum = true; + if (av_enabled && AutoVacuumingActive()) + *dovacuum = true; + } } /* - * Determine if this table needs analyze, and update the score if it - * does. Note that we don't analyze TOAST tables and pg_statistic. + * Update the analyze scores and determine if this table requires + * analyze. Note that we don't analyze TOAST tables and pg_statistic. */ + scores->anl = (double) anltuples / Max(anlthresh, 1); + scores->anl *= autovacuum_analyze_score_weight; + scores->max = Max(scores->max, scores->anl); if (anltuples > anlthresh && relid != StatisticRelationId && classForm->relkind != RELKIND_TOASTVALUE) { - scores->anl = (double) anltuples / Max(anlthresh, 1); - scores->anl *= autovacuum_analyze_score_weight; - scores->max = Max(scores->max, scores->anl); - *doanalyze = true; + scores->needs_analyze = true; + if (av_enabled && AutoVacuumingActive()) + *doanalyze = true; } if (vac_ins_base_thresh >= 0) -- 2.47.3