Hi, On 2023-01-24 20:59:04 -0800, Andres Freund wrote: > I find this to be awkward code. The booleans are kinda pointless, and the > tableagevac case is hard to follow because trigger is set elsewhere. > > I can give reformulating it a go. Need to make some food first.
Here's a draft of what I am thinking of. Not perfect yet, but I think it looks better. The pg_stat_activity output looks like this right now: autovacuum due to table XID age: VACUUM public.pgbench_accounts (to prevent wraparound) Why don't we drop the "(to prevent wraparound)" now? And I still think removing the "table " bit would be an improvement. Greetings, Andres Freund
diff --git i/src/include/commands/vacuum.h w/src/include/commands/vacuum.h index 4450003b4a8..13f70a1f654 100644 --- i/src/include/commands/vacuum.h +++ w/src/include/commands/vacuum.h @@ -237,7 +237,8 @@ typedef struct VacuumParams * use default */ int multixact_freeze_table_age; /* multixact age at which to scan * whole table */ - bool is_wraparound; /* force a for-wraparound vacuum */ + bool is_wraparound; /* antiwraparound autovacuum? */ + AutoVacType trigger; /* autovacuum trigger condition, if any */ int log_min_duration; /* minimum execution threshold in ms at * which autovacuum is logged, -1 to use * default */ @@ -328,6 +329,7 @@ extern void vacuum(List *relations, VacuumParams *params, extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, int *nindexes, Relation **Irel); extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode); +extern const char *vac_autovacuum_trigger_msg(AutoVacType trigger); extern double vac_estimate_reltuples(Relation relation, BlockNumber total_pages, BlockNumber scanned_pages, diff --git i/src/backend/access/heap/vacuumlazy.c w/src/backend/access/heap/vacuumlazy.c index 8f14cf85f38..8a64dce6180 100644 --- i/src/backend/access/heap/vacuumlazy.c +++ w/src/backend/access/heap/vacuumlazy.c @@ -639,6 +639,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * implies aggressive. Produce distinct output for the corner * case all the same, just in case. */ + Assert(params->trigger == AUTOVACUUM_TABLE_XID_AGE || + params->trigger == AUTOVACUUM_TABLE_MXID_AGE); if (vacrel->aggressive) msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); else @@ -656,6 +658,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->relnamespace, vacrel->relname, vacrel->num_index_scans); + if (!verbose) + appendStringInfo(&buf, _("triggered by: %s\n"), + vac_autovacuum_trigger_msg(params->trigger)); appendStringInfo(&buf, _("pages: %u removed, %u remain, %u scanned (%.2f%% of total)\n"), vacrel->removed_pages, new_rel_pages, diff --git i/src/backend/commands/vacuum.c w/src/backend/commands/vacuum.c index 7b1a4b127eb..18278acb557 100644 --- i/src/backend/commands/vacuum.c +++ w/src/backend/commands/vacuum.c @@ -273,8 +273,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) params.multixact_freeze_table_age = -1; } - /* user-invoked vacuum is never "for wraparound" */ + /* user-invoked vacuum never uses these autovacuum-only flags */ params.is_wraparound = false; + params.trigger = AUTOVACUUM_NONE; /* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */ params.log_min_duration = -1; @@ -1874,7 +1875,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs) LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); MyProc->statusFlags |= PROC_IN_VACUUM; if (params->is_wraparound) + { + Assert(params->trigger == AUTOVACUUM_TABLE_XID_AGE || + params->trigger == AUTOVACUUM_TABLE_MXID_AGE); MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND; + } ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; LWLockRelease(ProcArrayLock); } @@ -2176,6 +2181,30 @@ vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode) pfree(Irel); } +/* + * Return translatable string describing autovacuum trigger threshold type + */ +const char * +vac_autovacuum_trigger_msg(AutoVacType trigger) +{ + switch (trigger) + { + case AUTOVACUUM_NONE: + return _("none"); + case AUTOVACUUM_TABLE_XID_AGE: + return _("table XID age"); + case AUTOVACUUM_TABLE_MXID_AGE: + return _("table MXID age"); + case AUTOVACUUM_DEAD_TUPLES: + return _("dead tuples"); + case AUTOVACUUM_INSERTED_TUPLES: + return _("inserted tuples"); + } + + Assert(false); /* cannot be reached */ + return NULL; +} + /* * vacuum_delay_point --- check for interrupts and cost-based delay. * diff --git i/src/backend/postmaster/autovacuum.c w/src/backend/postmaster/autovacuum.c index f5ea381c53e..18d150c975b 100644 --- i/src/backend/postmaster/autovacuum.c +++ w/src/backend/postmaster/autovacuum.c @@ -330,12 +330,14 @@ static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map, static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts, Form_pg_class classForm, int effective_multixact_freeze_max_age, - bool *dovacuum, bool *doanalyze, bool *wraparound); + AutoVacType *dovacuum, bool *doanalyze, + bool *wraparound); static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, int effective_multixact_freeze_max_age, - bool *dovacuum, bool *doanalyze, bool *wraparound); + AutoVacType *dovacuum, bool *doanalyze, + bool *wraparound); static void autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy); @@ -2064,7 +2066,7 @@ do_autovacuum(void) PgStat_StatTabEntry *tabentry; AutoVacOpts *relopts; Oid relid; - bool dovacuum; + AutoVacType dovacuum; bool doanalyze; bool wraparound; @@ -2110,7 +2112,7 @@ do_autovacuum(void) &dovacuum, &doanalyze, &wraparound); /* Relations that need work are added to table_oids */ - if (dovacuum || doanalyze) + if (dovacuum != AUTOVACUUM_NONE || doanalyze) table_oids = lappend_oid(table_oids, relid); /* @@ -2157,7 +2159,7 @@ do_autovacuum(void) PgStat_StatTabEntry *tabentry; Oid relid; AutoVacOpts *relopts = NULL; - bool dovacuum; + AutoVacType dovacuum; bool doanalyze; bool wraparound; @@ -2193,7 +2195,7 @@ do_autovacuum(void) &dovacuum, &doanalyze, &wraparound); /* ignore analyze for toast tables */ - if (dovacuum) + if (dovacuum != AUTOVACUUM_NONE) table_oids = lappend_oid(table_oids, relid); } @@ -2762,7 +2764,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, { Form_pg_class classForm; HeapTuple classTup; - bool dovacuum; + AutoVacType dovacuum; bool doanalyze; autovac_table *tab = NULL; bool wraparound; @@ -2792,10 +2794,11 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, recheck_relation_needs_vacanalyze(relid, avopts, classForm, effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); + &dovacuum, &doanalyze, + &wraparound); /* OK, it needs something done */ - if (doanalyze || dovacuum) + if (doanalyze || dovacuum != AUTOVACUUM_NONE) { int freeze_min_age; int freeze_table_age; @@ -2860,7 +2863,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * skip vac_update_datfrozenxid(); we'll do that separately. */ tab->at_params.options = - (dovacuum ? (VACOPT_VACUUM | VACOPT_SKIP_DATABASE_STATS) : 0) | + (dovacuum != AUTOVACUUM_NONE ? (VACOPT_VACUUM | VACOPT_SKIP_DATABASE_STATS) : 0) | (doanalyze ? VACOPT_ANALYZE : 0) | (!wraparound ? VACOPT_SKIP_LOCKED : 0); @@ -2878,6 +2881,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age; tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age; tab->at_params.is_wraparound = wraparound; + tab->at_params.trigger = dovacuum; tab->at_params.log_min_duration = log_min_duration; tab->at_vacuum_cost_limit = vac_cost_limit; tab->at_vacuum_cost_delay = vac_cost_delay; @@ -2911,7 +2915,7 @@ recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts, Form_pg_class classForm, int effective_multixact_freeze_max_age, - bool *dovacuum, + AutoVacType *dovacuum, bool *doanalyze, bool *wraparound) { @@ -2933,9 +2937,9 @@ recheck_relation_needs_vacanalyze(Oid relid, /* * relation_needs_vacanalyze * - * Check whether a relation needs to be vacuumed or analyzed; return each into - * "dovacuum" and "doanalyze", respectively. Also return whether the vacuum is - * being forced because of Xid or multixact wraparound. + * Check whether a relation needs to be vacuumed or analyzed; return whether + * and why vacuum needs to be performed in *dovacuum, whether the table needs + * to be analyzed in *doanalyze. * * relopts is a pointer to the AutoVacOpts options (either for itself in the * case of a plain table, or for either itself or its parent table in the case @@ -2966,6 +2970,15 @@ recheck_relation_needs_vacanalyze(Oid relid, * autovacuum_vacuum_threshold GUC variable. Similarly, a vac_scale_factor * value < 0 is substituted with the value of * autovacuum_vacuum_scale_factor GUC variable. Ditto for analyze. + * + * + * There can only be exactly one triggering condition for vacuum, even when + * multiple thresholds happened to be crossed at the same time. We prefer to + * return "table XID age" in the event of such a conflict, after which we + * prefer "table MXID age" as the criteria, then "dead tuples", with "inserted + * tuples" placed last. These predecence rules are largely arbitrary. We + * must at least ensure that all antiwraparound autovacuums are advertised as + * triggered by table XID/MXID age criteria. */ static void relation_needs_vacanalyze(Oid relid, @@ -2974,11 +2987,10 @@ relation_needs_vacanalyze(Oid relid, PgStat_StatTabEntry *tabentry, int effective_multixact_freeze_max_age, /* output params below */ - bool *dovacuum, + AutoVacType *dovacuum, bool *doanalyze, bool *wraparound) { - bool force_vacuum; bool av_enabled; float4 reltuples; /* pg_class.reltuples */ @@ -3055,24 +3067,27 @@ relation_needs_vacanalyze(Oid relid, xidForceLimit = recentXid - freeze_max_age; if (xidForceLimit < FirstNormalTransactionId) xidForceLimit -= FirstNormalTransactionId; - force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) && - TransactionIdPrecedes(classForm->relfrozenxid, - xidForceLimit)); - if (!force_vacuum) - { - multiForceLimit = recentMulti - multixact_freeze_max_age; - if (multiForceLimit < FirstMultiXactId) - multiForceLimit -= FirstMultiXactId; - force_vacuum = MultiXactIdIsValid(classForm->relminmxid) && - MultiXactIdPrecedes(classForm->relminmxid, multiForceLimit); - } - *wraparound = force_vacuum; + multiForceLimit = recentMulti - multixact_freeze_max_age; + if (multiForceLimit < FirstMultiXactId) + multiForceLimit -= FirstMultiXactId; + + /* See header comments about trigger precedence */ + if (TransactionIdIsNormal(classForm->relfrozenxid) && + TransactionIdPrecedes(classForm->relfrozenxid, xidForceLimit)) + *dovacuum = AUTOVACUUM_TABLE_XID_AGE; + else if (MultiXactIdIsValid(classForm->relminmxid) && + MultiXactIdPrecedes(classForm->relminmxid, multiForceLimit)) + *dovacuum = AUTOVACUUM_TABLE_MXID_AGE; + else + *dovacuum = AUTOVACUUM_NONE; + + /* A table age autovacuum always gets antiwraparound protections */ + *wraparound = *dovacuum != AUTOVACUUM_NONE; /* User disabled it in pg_class.reloptions? (But ignore if at risk) */ - if (!av_enabled && !force_vacuum) + if (!av_enabled && !*wraparound) { *doanalyze = false; - *dovacuum = false; return; } @@ -3112,9 +3127,18 @@ relation_needs_vacanalyze(Oid relid, NameStr(classForm->relname), vactuples, vacthresh, anltuples, anlthresh); - /* Determine if this table needs vacuum or analyze. */ - *dovacuum = force_vacuum || (vactuples > vacthresh) || - (vac_ins_base_thresh >= 0 && instuples > vacinsthresh); + /* + * Determine if this table needs vacuum or analyze. + * + * See header comments about trigger precedence. + */ + if (*dovacuum == AUTOVACUUM_NONE) + { + if (vactuples > vacthresh) + *dovacuum = AUTOVACUUM_DEAD_TUPLES; + else if (vac_ins_base_thresh >= 0 && instuples > vacinsthresh) + *dovacuum = AUTOVACUUM_INSERTED_TUPLES; + } *doanalyze = (anltuples > anlthresh); } else @@ -3124,7 +3148,6 @@ relation_needs_vacanalyze(Oid relid, * for anti-wrap purposes. If it's not acted upon, there's no need to * vacuum it. */ - *dovacuum = force_vacuum; *doanalyze = false; } @@ -3169,14 +3192,15 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy) static void autovac_report_activity(autovac_table *tab) { -#define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 56) +#define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 100) char activity[MAX_AUTOVAC_ACTIV_LEN]; int len; /* Report the command and possible options */ if (tab->at_params.options & VACOPT_VACUUM) snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, - "autovacuum: VACUUM%s", + "autovacuum due to %s: VACUUM%s", + vac_autovacuum_trigger_msg(tab->at_params.trigger), tab->at_params.options & VACOPT_ANALYZE ? " ANALYZE" : ""); else snprintf(activity, MAX_AUTOVAC_ACTIV_LEN,