agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedFrom: Nathan Bossart <nathandbossart@gmail.com>
To: Michael Paquier <michael@paquier.xyz>
Cc: pgsql-hackers@postgresql.org
Subject: Re: problems with toast.* reloptions
Date: Mon, 23 Jun 2025 15:59:56 -0500
Message-ID: <aFnATDLLdP0m-fGb@nathan> (raw)
In-Reply-To: <aFTB8bs_u5WyBReK@paquier.xyz>
References: <aFRxC1W_kZU9OjJ9@nathan>
<aFTB8bs_u5WyBReK@paquier.xyz>
On Fri, Jun 20, 2025 at 11:05:37AM +0900, Michael Paquier wrote:
> On Thu, Jun 19, 2025 at 03:20:27PM -0500, Nathan Bossart wrote:
>> I think we need to do something like the following to fix this:
>>
>> * Teach autovacuum to combine the TOAST reloptions with the main relation's
>> when processing TOAST tables (with the toast.* ones winning if both are
>> set).
>>
>> * Teach autovacuum to resolve reloptions for parameters like
>> vacuum_truncate instead of relying on vacuum_rel() to fill it in.
>
> These two points make sense here, yes.
>
>> * Have vacuum_rel() send the main relation's reloptions when recursing to
>> the TOAST table so that we can combine them there, too.
>
> For the case of a manual VACUUM on the main table, where the TOAST
> table is treated as a secondary citizen, that makes sense as well,
> yes.
Here is a very rough proof-of-concept patch set for this. AFAICT there are
a few options we cannot fix on the back-branches because there is no way to
tell whether it is set or has just picked up the default. On v18 and
newer, we could use isset_offset, but that doesn't exist on older versions.
(I haven't looked closely, but I'm assuming that back-patching isset_offset
isn't an option.)
I would like to explore the "option 2" from upthread [0] for v19. I think
that is a better long-term solution, and it may allow us to remove the
table_toast_map in autovacuum.
[0] https://postgr.es/m/aFl598epAdUrrv0y%40nathan
--
nathan
From a1930aa769a96c38621d7caa727c404d553b7ecc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 14:07:30 -0500
Subject: [PATCH v1 1/4] autovac: save all relopts instead of just avopts
---
src/backend/postmaster/autovacuum.c | 119 ++++++++++------------------
1 file changed, 43 insertions(+), 76 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 451fb90a610..f86c9fed853 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -192,8 +192,8 @@ typedef struct av_relation
Oid ar_toastrelid; /* hash key - must be first */
Oid ar_relid;
bool ar_hasrelopts;
- AutoVacOpts ar_reloptions; /* copy of AutoVacOpts from the main table's
- * reloptions, or NULL if none */
+ StdRdOptions ar_reloptions; /* copy of main table's reloptions, or NULL if
+ * none */
} av_relation;
/* struct to keep track of tables to vacuum and/or analyze, after rechecking */
@@ -333,11 +333,11 @@ static void FreeWorkerInfo(int code, Datum arg);
static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
int effective_multixact_freeze_max_age);
-static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts,
+static void recheck_relation_needs_vacanalyze(Oid relid, StdRdOptions *relopts,
Form_pg_class classForm,
int effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
-static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
+static void relation_needs_vacanalyze(Oid relid, StdRdOptions *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
int effective_multixact_freeze_max_age,
@@ -345,8 +345,6 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
static void autovacuum_do_vac_analyze(autovac_table *tab,
BufferAccessStrategy bstrategy);
-static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
- TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
@@ -1995,7 +1993,7 @@ do_autovacuum(void)
{
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
PgStat_StatTabEntry *tabentry;
- AutoVacOpts *relopts;
+ StdRdOptions *relopts;
Oid relid;
bool dovacuum;
bool doanalyze;
@@ -2033,7 +2031,7 @@ do_autovacuum(void)
}
/* Fetch reloptions and the pgstat entry for this table */
- relopts = extract_autovac_opts(tuple, pg_class_desc);
+ relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared,
relid);
@@ -2069,7 +2067,7 @@ do_autovacuum(void)
{
hentry->ar_hasrelopts = true;
memcpy(&hentry->ar_reloptions, relopts,
- sizeof(AutoVacOpts));
+ sizeof(StdRdOptions));
}
}
}
@@ -2095,7 +2093,7 @@ do_autovacuum(void)
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
PgStat_StatTabEntry *tabentry;
Oid relid;
- AutoVacOpts *relopts;
+ StdRdOptions *relopts;
bool free_relopts = false;
bool dovacuum;
bool doanalyze;
@@ -2113,7 +2111,7 @@ do_autovacuum(void)
* fetch reloptions -- if this toast table does not have them, try the
* main rel
*/
- relopts = extract_autovac_opts(tuple, pg_class_desc);
+ relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
if (relopts)
free_relopts = true;
else
@@ -2701,39 +2699,6 @@ deleted2:
pfree(cur_relname);
}
-/*
- * extract_autovac_opts
- *
- * Given a relation's pg_class tuple, return a palloc'd copy of the
- * AutoVacOpts portion of reloptions, if set; otherwise, return NULL.
- *
- * Note: callers do not have a relation lock on the table at this point,
- * so the table could have been dropped, and its catalog rows gone, after
- * we acquired the pg_class row. If pg_class had a TOAST table, this would
- * be a risk; fortunately, it doesn't.
- */
-static AutoVacOpts *
-extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
-{
- bytea *relopts;
- AutoVacOpts *av;
-
- Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
- ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
- ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
-
- relopts = extractRelOptions(tup, pg_class_desc, NULL);
- if (relopts == NULL)
- return NULL;
-
- av = palloc(sizeof(AutoVacOpts));
- memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts));
- pfree(relopts);
-
- return av;
-}
-
-
/*
* table_recheck_autovac
*
@@ -2753,8 +2718,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
bool doanalyze;
autovac_table *tab = NULL;
bool wraparound;
- AutoVacOpts *avopts;
- bool free_avopts = false;
+ StdRdOptions *relopts;
+ bool free_relopts = false;
/* fetch the relation's relcache entry */
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
@@ -2766,9 +2731,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
* Get the applicable reloptions. If it is a TOAST table, try to get the
* main table reloptions if the toast table itself doesn't have.
*/
- avopts = extract_autovac_opts(classTup, pg_class_desc);
- if (avopts)
- free_avopts = true;
+ relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, NULL);
+ if (relopts)
+ free_relopts = true;
else if (classForm->relkind == RELKIND_TOASTVALUE &&
table_toast_map != NULL)
{
@@ -2777,10 +2742,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
if (found && hentry->ar_hasrelopts)
- avopts = &hentry->ar_reloptions;
+ relopts = &hentry->ar_reloptions;
}
- recheck_relation_needs_vacanalyze(relid, avopts, classForm,
+ recheck_relation_needs_vacanalyze(relid, relopts, classForm,
effective_multixact_freeze_max_age,
&dovacuum, &doanalyze, &wraparound);
@@ -2792,6 +2757,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
int multixact_freeze_min_age;
int multixact_freeze_table_age;
int log_min_duration;
+ AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
/*
* Calculate the vacuum cost parameters and the freeze ages. If there
@@ -2879,8 +2845,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
avopts->vacuum_cost_delay >= 0));
}
- if (free_avopts)
- pfree(avopts);
+ if (free_relopts)
+ pfree(relopts);
heap_freetuple(classTup);
return tab;
}
@@ -2895,7 +2861,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
*/
static void
recheck_relation_needs_vacanalyze(Oid relid,
- AutoVacOpts *avopts,
+ StdRdOptions *relopts,
Form_pg_class classForm,
int effective_multixact_freeze_max_age,
bool *dovacuum,
@@ -2908,7 +2874,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared,
relid);
- relation_needs_vacanalyze(relid, avopts, classForm, tabentry,
+ relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
effective_multixact_freeze_max_age,
dovacuum, doanalyze, wraparound);
@@ -2928,7 +2894,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
* "dovacuum" and "doanalyze", respectively. Also return whether the vacuum is
* being forced because of Xid or multixact wraparound.
*
- * relopts is a pointer to the AutoVacOpts options (either for itself in the
+ * relopts is a pointer to the StdRdOptions options (either for itself in the
* case of a plain table, or for either itself or its parent table in the case
* of a TOAST table), NULL if none; tabentry is the pgstats entry, which can be
* NULL.
@@ -2962,7 +2928,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
*/
static void
relation_needs_vacanalyze(Oid relid,
- AutoVacOpts *relopts,
+ StdRdOptions *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
int effective_multixact_freeze_max_age,
@@ -2973,6 +2939,7 @@ relation_needs_vacanalyze(Oid relid,
{
bool force_vacuum;
bool av_enabled;
+ AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
/* constants from reloptions or GUC variables */
int vac_base_thresh,
@@ -3010,45 +2977,45 @@ relation_needs_vacanalyze(Oid relid,
*/
/* -1 in autovac setting means use plain vacuum_scale_factor */
- vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0)
- ? relopts->vacuum_scale_factor
+ vac_scale_factor = (avopts && avopts->vacuum_scale_factor >= 0)
+ ? avopts->vacuum_scale_factor
: autovacuum_vac_scale;
- vac_base_thresh = (relopts && relopts->vacuum_threshold >= 0)
- ? relopts->vacuum_threshold
+ vac_base_thresh = (avopts && avopts->vacuum_threshold >= 0)
+ ? avopts->vacuum_threshold
: autovacuum_vac_thresh;
/* -1 is used to disable max threshold */
- vac_max_thresh = (relopts && relopts->vacuum_max_threshold >= -1)
- ? relopts->vacuum_max_threshold
+ vac_max_thresh = (avopts && avopts->vacuum_max_threshold >= -1)
+ ? avopts->vacuum_max_threshold
: autovacuum_vac_max_thresh;
- vac_ins_scale_factor = (relopts && relopts->vacuum_ins_scale_factor >= 0)
- ? relopts->vacuum_ins_scale_factor
+ vac_ins_scale_factor = (avopts && avopts->vacuum_ins_scale_factor >= 0)
+ ? avopts->vacuum_ins_scale_factor
: autovacuum_vac_ins_scale;
/* -1 is used to disable insert vacuums */
- vac_ins_base_thresh = (relopts && relopts->vacuum_ins_threshold >= -1)
- ? relopts->vacuum_ins_threshold
+ vac_ins_base_thresh = (avopts && avopts->vacuum_ins_threshold >= -1)
+ ? avopts->vacuum_ins_threshold
: autovacuum_vac_ins_thresh;
- anl_scale_factor = (relopts && relopts->analyze_scale_factor >= 0)
- ? relopts->analyze_scale_factor
+ anl_scale_factor = (avopts && avopts->analyze_scale_factor >= 0)
+ ? avopts->analyze_scale_factor
: autovacuum_anl_scale;
- anl_base_thresh = (relopts && relopts->analyze_threshold >= 0)
- ? relopts->analyze_threshold
+ anl_base_thresh = (avopts && avopts->analyze_threshold >= 0)
+ ? avopts->analyze_threshold
: autovacuum_anl_thresh;
- freeze_max_age = (relopts && relopts->freeze_max_age >= 0)
- ? Min(relopts->freeze_max_age, autovacuum_freeze_max_age)
+ freeze_max_age = (avopts && avopts->freeze_max_age >= 0)
+ ? Min(avopts->freeze_max_age, autovacuum_freeze_max_age)
: autovacuum_freeze_max_age;
- multixact_freeze_max_age = (relopts && relopts->multixact_freeze_max_age >= 0)
- ? Min(relopts->multixact_freeze_max_age, effective_multixact_freeze_max_age)
+ multixact_freeze_max_age = (avopts && avopts->multixact_freeze_max_age >= 0)
+ ? Min(avopts->multixact_freeze_max_age, effective_multixact_freeze_max_age)
: effective_multixact_freeze_max_age;
- av_enabled = (relopts ? relopts->enabled : true);
+ av_enabled = (avopts ? avopts->enabled : true);
/* Force vacuum if table is at risk of wraparound */
xidForceLimit = recentXid - freeze_max_age;
--
2.39.5 (Apple Git-154)
From a3bbb1fdcf6a66719af84b763861e187cc5588bd Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 14:42:39 -0500
Subject: [PATCH v1 2/4] autovac: resolve relopts before vacuuming
---
src/backend/commands/vacuum.c | 7 ++--
src/backend/postmaster/autovacuum.c | 52 +++++++++++++++++++++--------
2 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 33a33bf6b1c..0015b9ef4b0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -421,7 +421,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/*
* Later, in vacuum_rel(), we check if a reloption override was specified.
*/
- params.max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
+ params.max_eager_freeze_failure_rate = -1.0;
/*
* Create special memory context for cross-transaction storage.
@@ -2195,10 +2195,13 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
* Check if the vacuum_max_eager_freeze_failure_rate table storage
* parameter was specified. This overrides the GUC value.
*/
- if (rel->rd_options != NULL &&
+ if (params->max_eager_freeze_failure_rate < 0 &&
+ rel->rd_options != NULL &&
((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate >= 0)
params->max_eager_freeze_failure_rate =
((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+ else
+ params->max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
/*
* Set truncate option based on truncate reloption or GUC if it wasn't
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f86c9fed853..3bedca971ff 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2758,6 +2758,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
int multixact_freeze_table_age;
int log_min_duration;
AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
+ VacOptValue index_cleanup;
+ VacOptValue truncate;
+ double max_eager_freeze_failure_rate;
/*
* Calculate the vacuum cost parameters and the freeze ages. If there
@@ -2790,6 +2793,39 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
? avopts->multixact_freeze_table_age
: default_multixact_freeze_table_age;
+ if (relopts)
+ {
+ switch (relopts->vacuum_index_cleanup)
+ {
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
+ index_cleanup = VACOPTVALUE_AUTO;
+ break;
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
+ index_cleanup = VACOPTVALUE_ENABLED;
+ break;
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
+ index_cleanup = VACOPTVALUE_DISABLED;
+ break;
+ }
+ }
+ else
+ index_cleanup = VACOPTVALUE_UNSPECIFIED;
+
+ if (relopts && relopts->vacuum_truncate_set)
+ {
+ if (relopts->vacuum_truncate)
+ truncate = VACOPTVALUE_ENABLED;
+ else
+ truncate = VACOPTVALUE_DISABLED;
+ }
+ else
+ truncate = VACOPTVALUE_UNSPECIFIED;
+
+ if (relopts && relopts->vacuum_max_eager_freeze_failure_rate >= 0)
+ max_eager_freeze_failure_rate = relopts->vacuum_max_eager_freeze_failure_rate;
+ else
+ max_eager_freeze_failure_rate = -1.0;
+
tab = palloc(sizeof(autovac_table));
tab->at_relid = relid;
tab->at_sharedrel = classForm->relisshared;
@@ -2806,13 +2842,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
(doanalyze ? VACOPT_ANALYZE : 0) |
(!wraparound ? VACOPT_SKIP_LOCKED : 0);
- /*
- * index_cleanup and truncate are unspecified at first in autovacuum.
- * They will be filled in with usable values using their reloptions
- * (or reloption defaults) later.
- */
- tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
- tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
+ tab->at_params.index_cleanup = index_cleanup;
+ tab->at_params.truncate = truncate;
/* As of now, we don't support parallel vacuum for autovacuum */
tab->at_params.nworkers = -1;
tab->at_params.freeze_min_age = freeze_min_age;
@@ -2822,12 +2853,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
tab->at_params.toast_parent = InvalidOid;
-
- /*
- * Later, in vacuum_rel(), we check reloptions for any
- * vacuum_max_eager_freeze_failure_rate override.
- */
- tab->at_params.max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
+ tab->at_params.max_eager_freeze_failure_rate = max_eager_freeze_failure_rate;
tab->at_storage_param_vac_cost_limit = avopts ?
avopts->vacuum_cost_limit : 0;
tab->at_storage_param_vac_cost_delay = avopts ?
--
2.39.5 (Apple Git-154)
From 12b6404c906837c9244110095ca980ad3545bc0b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 15:16:38 -0500
Subject: [PATCH v1 3/4] autovac: combine reloptions correctly
---
src/backend/postmaster/autovacuum.c | 85 +++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3bedca971ff..0d1f4e38b58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1873,6 +1873,73 @@ get_database_list(void)
return dblist;
}
+static void
+combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts)
+{
+ AutoVacOpts *toast_avopts = &toast_opts->autovacuum;
+ const AutoVacOpts *main_avopts = &main_opts->autovacuum;
+
+ /* XXX: need isset_offset to combine "enabled" */
+
+ if (toast_avopts->vacuum_threshold == -1)
+ toast_avopts->vacuum_threshold = main_avopts->vacuum_threshold;
+
+ if (toast_avopts->vacuum_max_threshold == -2)
+ toast_avopts->vacuum_max_threshold = main_avopts->vacuum_max_threshold;
+
+ if (toast_avopts->vacuum_ins_threshold == -2)
+ toast_avopts->vacuum_ins_threshold = main_avopts->vacuum_ins_threshold;
+
+ if (toast_avopts->analyze_threshold == -1)
+ toast_avopts->analyze_threshold = main_avopts->analyze_threshold;
+
+ if (toast_avopts->vacuum_cost_limit == -1)
+ toast_avopts->vacuum_cost_limit = main_avopts->vacuum_cost_limit;
+
+ if (toast_avopts->freeze_min_age == -1)
+ toast_avopts->freeze_min_age = main_avopts->freeze_min_age;
+
+ if (toast_avopts->freeze_max_age == -1)
+ toast_avopts->freeze_max_age = main_avopts->freeze_max_age;
+
+ if (toast_avopts->freeze_table_age == -1)
+ toast_avopts->freeze_table_age = main_avopts->freeze_table_age;
+
+ if (toast_avopts->multixact_freeze_min_age == -1)
+ toast_avopts->multixact_freeze_min_age = main_avopts->multixact_freeze_min_age;
+
+ if (toast_avopts->multixact_freeze_max_age == -1)
+ toast_avopts->multixact_freeze_max_age = main_avopts->multixact_freeze_max_age;
+
+ if (toast_avopts->multixact_freeze_table_age == -1)
+ toast_avopts->multixact_freeze_table_age = main_avopts->multixact_freeze_table_age;
+
+ /* XXX: need isset_offset for log_min_duration */
+
+ if (toast_avopts->vacuum_cost_delay == -1)
+ toast_avopts->vacuum_cost_delay = main_avopts->vacuum_cost_delay;
+
+ if (toast_avopts->vacuum_scale_factor == -1)
+ toast_avopts->vacuum_scale_factor = main_avopts->vacuum_scale_factor;
+
+ if (toast_avopts->vacuum_ins_scale_factor == -1)
+ toast_avopts->vacuum_ins_scale_factor = main_avopts->vacuum_ins_scale_factor;
+
+ if (toast_avopts->analyze_scale_factor == -1)
+ toast_avopts->analyze_scale_factor = main_avopts->analyze_scale_factor;
+
+ /* XXX: need isset_offset for vacuum_index_cleanup */
+
+ if (!toast_opts->vacuum_truncate_set && main_opts->vacuum_truncate_set)
+ {
+ toast_opts->vacuum_truncate = main_opts->vacuum_truncate;
+ toast_opts->vacuum_truncate_set = true;
+ }
+
+ if (toast_opts->vacuum_max_eager_freeze_failure_rate == -1)
+ toast_opts->vacuum_max_eager_freeze_failure_rate = main_opts->vacuum_max_eager_freeze_failure_rate;
+}
+
/*
* Process a database table-by-table
*
@@ -2113,7 +2180,16 @@ do_autovacuum(void)
*/
relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
if (relopts)
+ {
+ av_relation *hentry;
+ bool found;
+
free_relopts = true;
+
+ hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
+ if (found && hentry->ar_hasrelopts)
+ combine_relopts(relopts, &hentry->ar_reloptions);
+ }
else
{
av_relation *hentry;
@@ -2733,7 +2809,16 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
*/
relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, NULL);
if (relopts)
+ {
+ av_relation *hentry;
+ bool found;
+
free_relopts = true;
+
+ hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
+ if (found && hentry->ar_hasrelopts)
+ combine_relopts(relopts, &hentry->ar_reloptions);
+ }
else if (classForm->relkind == RELKIND_TOASTVALUE &&
table_toast_map != NULL)
{
--
2.39.5 (Apple Git-154)
From 724f90f4d19a2c92026adffcde8bf47967378f1e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 15:40:28 -0500
Subject: [PATCH v1 4/4] combine relopts correctly for VACUUM commands
---
src/backend/commands/vacuum.c | 30 +++++++++++++++++++----------
src/backend/postmaster/autovacuum.c | 2 +-
src/include/commands/vacuum.h | 2 ++
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0015b9ef4b0..a74c3a9e2a2 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -124,7 +124,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy, StdRdOptions *main_opts);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -634,7 +634,7 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy, NULL))
continue;
}
@@ -1998,7 +1998,7 @@ vac_truncate_clog(TransactionId frozenXID,
*/
static bool
vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- BufferAccessStrategy bstrategy)
+ BufferAccessStrategy bstrategy, StdRdOptions *main_opts)
{
LOCKMODE lmode;
Relation rel;
@@ -2008,6 +2008,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
Oid save_userid;
int save_sec_context;
int save_nestlevel;
+ StdRdOptions saved_opts;
+ StdRdOptions combined_opts;
Assert(params != NULL);
@@ -2165,6 +2167,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
lockrelid = rel->rd_lockInfo.lockRelId;
LockRelationIdForSession(&lockrelid, lmode);
+ if (rel->rd_options)
+ {
+ memcpy(&saved_opts, rel->rd_options, sizeof(StdRdOptions));
+ memcpy(&combined_opts, rel->rd_options, sizeof(StdRdOptions));
+
+ if (main_opts)
+ combine_relopts(&combined_opts, main_opts);
+ }
+
/*
* Set index_cleanup option based on index_cleanup reloption if it wasn't
* specified in VACUUM command, or when running in an autovacuum worker
@@ -2176,8 +2187,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
if (rel->rd_options == NULL)
vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
else
- vacuum_index_cleanup =
- ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
+ vacuum_index_cleanup = combined_opts.vacuum_index_cleanup;
if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
params->index_cleanup = VACOPTVALUE_AUTO;
@@ -2197,9 +2207,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
*/
if (params->max_eager_freeze_failure_rate < 0 &&
rel->rd_options != NULL &&
- ((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate >= 0)
+ combined_opts.vacuum_max_eager_freeze_failure_rate >= 0)
params->max_eager_freeze_failure_rate =
- ((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+ combined_opts.vacuum_max_eager_freeze_failure_rate;
else
params->max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
@@ -2211,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && combined_opts.vacuum_truncate_set)
{
- if (opts->vacuum_truncate)
+ if (combined_opts.vacuum_truncate)
params->truncate = VACOPTVALUE_ENABLED;
else
params->truncate = VACOPTVALUE_DISABLED;
@@ -2314,7 +2324,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
toast_vacuum_params.toast_parent = relid;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy, &saved_opts);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0d1f4e38b58..ca399b6a92e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1873,7 +1873,7 @@ get_database_list(void)
return dblist;
}
-static void
+void
combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts)
{
AutoVacOpts *toast_avopts = &toast_opts->autovacuum;
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc74..928f864581b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -24,6 +24,7 @@
#include "parser/parse_node.h"
#include "storage/buf.h"
#include "storage/lock.h"
+#include "utils/rel.h"
#include "utils/relcache.h"
/*
@@ -377,6 +378,7 @@ extern IndexBulkDeleteResult *vac_cleanup_one_index(IndexVacuumInfo *ivinfo,
/* In postmaster/autovacuum.c */
extern void AutoVacuumUpdateCostLimit(void);
extern void VacuumUpdateCosts(void);
+extern void combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts);
/* in commands/vacuumparallel.c */
extern ParallelVacuumState *parallel_vacuum_init(Relation rel, Relation *indrels,
--
2.39.5 (Apple Git-154)
Attachments:
[text/plain] v1-0001-autovac-save-all-relopts-instead-of-just-avopts.patch (11.1K, ../aFnATDLLdP0m-fGb@nathan/2-v1-0001-autovac-save-all-relopts-instead-of-just-avopts.patch)
download | inline diff:
From a1930aa769a96c38621d7caa727c404d553b7ecc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 14:07:30 -0500
Subject: [PATCH v1 1/4] autovac: save all relopts instead of just avopts
---
src/backend/postmaster/autovacuum.c | 119 ++++++++++------------------
1 file changed, 43 insertions(+), 76 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 451fb90a610..f86c9fed853 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -192,8 +192,8 @@ typedef struct av_relation
Oid ar_toastrelid; /* hash key - must be first */
Oid ar_relid;
bool ar_hasrelopts;
- AutoVacOpts ar_reloptions; /* copy of AutoVacOpts from the main table's
- * reloptions, or NULL if none */
+ StdRdOptions ar_reloptions; /* copy of main table's reloptions, or NULL if
+ * none */
} av_relation;
/* struct to keep track of tables to vacuum and/or analyze, after rechecking */
@@ -333,11 +333,11 @@ static void FreeWorkerInfo(int code, Datum arg);
static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
TupleDesc pg_class_desc,
int effective_multixact_freeze_max_age);
-static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts,
+static void recheck_relation_needs_vacanalyze(Oid relid, StdRdOptions *relopts,
Form_pg_class classForm,
int effective_multixact_freeze_max_age,
bool *dovacuum, bool *doanalyze, bool *wraparound);
-static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
+static void relation_needs_vacanalyze(Oid relid, StdRdOptions *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
int effective_multixact_freeze_max_age,
@@ -345,8 +345,6 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
static void autovacuum_do_vac_analyze(autovac_table *tab,
BufferAccessStrategy bstrategy);
-static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
- TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
static void autovac_report_activity(autovac_table *tab);
static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
@@ -1995,7 +1993,7 @@ do_autovacuum(void)
{
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
PgStat_StatTabEntry *tabentry;
- AutoVacOpts *relopts;
+ StdRdOptions *relopts;
Oid relid;
bool dovacuum;
bool doanalyze;
@@ -2033,7 +2031,7 @@ do_autovacuum(void)
}
/* Fetch reloptions and the pgstat entry for this table */
- relopts = extract_autovac_opts(tuple, pg_class_desc);
+ relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared,
relid);
@@ -2069,7 +2067,7 @@ do_autovacuum(void)
{
hentry->ar_hasrelopts = true;
memcpy(&hentry->ar_reloptions, relopts,
- sizeof(AutoVacOpts));
+ sizeof(StdRdOptions));
}
}
}
@@ -2095,7 +2093,7 @@ do_autovacuum(void)
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
PgStat_StatTabEntry *tabentry;
Oid relid;
- AutoVacOpts *relopts;
+ StdRdOptions *relopts;
bool free_relopts = false;
bool dovacuum;
bool doanalyze;
@@ -2113,7 +2111,7 @@ do_autovacuum(void)
* fetch reloptions -- if this toast table does not have them, try the
* main rel
*/
- relopts = extract_autovac_opts(tuple, pg_class_desc);
+ relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
if (relopts)
free_relopts = true;
else
@@ -2701,39 +2699,6 @@ deleted2:
pfree(cur_relname);
}
-/*
- * extract_autovac_opts
- *
- * Given a relation's pg_class tuple, return a palloc'd copy of the
- * AutoVacOpts portion of reloptions, if set; otherwise, return NULL.
- *
- * Note: callers do not have a relation lock on the table at this point,
- * so the table could have been dropped, and its catalog rows gone, after
- * we acquired the pg_class row. If pg_class had a TOAST table, this would
- * be a risk; fortunately, it doesn't.
- */
-static AutoVacOpts *
-extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
-{
- bytea *relopts;
- AutoVacOpts *av;
-
- Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
- ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
- ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
-
- relopts = extractRelOptions(tup, pg_class_desc, NULL);
- if (relopts == NULL)
- return NULL;
-
- av = palloc(sizeof(AutoVacOpts));
- memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts));
- pfree(relopts);
-
- return av;
-}
-
-
/*
* table_recheck_autovac
*
@@ -2753,8 +2718,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
bool doanalyze;
autovac_table *tab = NULL;
bool wraparound;
- AutoVacOpts *avopts;
- bool free_avopts = false;
+ StdRdOptions *relopts;
+ bool free_relopts = false;
/* fetch the relation's relcache entry */
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
@@ -2766,9 +2731,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
* Get the applicable reloptions. If it is a TOAST table, try to get the
* main table reloptions if the toast table itself doesn't have.
*/
- avopts = extract_autovac_opts(classTup, pg_class_desc);
- if (avopts)
- free_avopts = true;
+ relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, NULL);
+ if (relopts)
+ free_relopts = true;
else if (classForm->relkind == RELKIND_TOASTVALUE &&
table_toast_map != NULL)
{
@@ -2777,10 +2742,10 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
if (found && hentry->ar_hasrelopts)
- avopts = &hentry->ar_reloptions;
+ relopts = &hentry->ar_reloptions;
}
- recheck_relation_needs_vacanalyze(relid, avopts, classForm,
+ recheck_relation_needs_vacanalyze(relid, relopts, classForm,
effective_multixact_freeze_max_age,
&dovacuum, &doanalyze, &wraparound);
@@ -2792,6 +2757,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
int multixact_freeze_min_age;
int multixact_freeze_table_age;
int log_min_duration;
+ AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
/*
* Calculate the vacuum cost parameters and the freeze ages. If there
@@ -2879,8 +2845,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
avopts->vacuum_cost_delay >= 0));
}
- if (free_avopts)
- pfree(avopts);
+ if (free_relopts)
+ pfree(relopts);
heap_freetuple(classTup);
return tab;
}
@@ -2895,7 +2861,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
*/
static void
recheck_relation_needs_vacanalyze(Oid relid,
- AutoVacOpts *avopts,
+ StdRdOptions *relopts,
Form_pg_class classForm,
int effective_multixact_freeze_max_age,
bool *dovacuum,
@@ -2908,7 +2874,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared,
relid);
- relation_needs_vacanalyze(relid, avopts, classForm, tabentry,
+ relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
effective_multixact_freeze_max_age,
dovacuum, doanalyze, wraparound);
@@ -2928,7 +2894,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
* "dovacuum" and "doanalyze", respectively. Also return whether the vacuum is
* being forced because of Xid or multixact wraparound.
*
- * relopts is a pointer to the AutoVacOpts options (either for itself in the
+ * relopts is a pointer to the StdRdOptions options (either for itself in the
* case of a plain table, or for either itself or its parent table in the case
* of a TOAST table), NULL if none; tabentry is the pgstats entry, which can be
* NULL.
@@ -2962,7 +2928,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
*/
static void
relation_needs_vacanalyze(Oid relid,
- AutoVacOpts *relopts,
+ StdRdOptions *relopts,
Form_pg_class classForm,
PgStat_StatTabEntry *tabentry,
int effective_multixact_freeze_max_age,
@@ -2973,6 +2939,7 @@ relation_needs_vacanalyze(Oid relid,
{
bool force_vacuum;
bool av_enabled;
+ AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
/* constants from reloptions or GUC variables */
int vac_base_thresh,
@@ -3010,45 +2977,45 @@ relation_needs_vacanalyze(Oid relid,
*/
/* -1 in autovac setting means use plain vacuum_scale_factor */
- vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0)
- ? relopts->vacuum_scale_factor
+ vac_scale_factor = (avopts && avopts->vacuum_scale_factor >= 0)
+ ? avopts->vacuum_scale_factor
: autovacuum_vac_scale;
- vac_base_thresh = (relopts && relopts->vacuum_threshold >= 0)
- ? relopts->vacuum_threshold
+ vac_base_thresh = (avopts && avopts->vacuum_threshold >= 0)
+ ? avopts->vacuum_threshold
: autovacuum_vac_thresh;
/* -1 is used to disable max threshold */
- vac_max_thresh = (relopts && relopts->vacuum_max_threshold >= -1)
- ? relopts->vacuum_max_threshold
+ vac_max_thresh = (avopts && avopts->vacuum_max_threshold >= -1)
+ ? avopts->vacuum_max_threshold
: autovacuum_vac_max_thresh;
- vac_ins_scale_factor = (relopts && relopts->vacuum_ins_scale_factor >= 0)
- ? relopts->vacuum_ins_scale_factor
+ vac_ins_scale_factor = (avopts && avopts->vacuum_ins_scale_factor >= 0)
+ ? avopts->vacuum_ins_scale_factor
: autovacuum_vac_ins_scale;
/* -1 is used to disable insert vacuums */
- vac_ins_base_thresh = (relopts && relopts->vacuum_ins_threshold >= -1)
- ? relopts->vacuum_ins_threshold
+ vac_ins_base_thresh = (avopts && avopts->vacuum_ins_threshold >= -1)
+ ? avopts->vacuum_ins_threshold
: autovacuum_vac_ins_thresh;
- anl_scale_factor = (relopts && relopts->analyze_scale_factor >= 0)
- ? relopts->analyze_scale_factor
+ anl_scale_factor = (avopts && avopts->analyze_scale_factor >= 0)
+ ? avopts->analyze_scale_factor
: autovacuum_anl_scale;
- anl_base_thresh = (relopts && relopts->analyze_threshold >= 0)
- ? relopts->analyze_threshold
+ anl_base_thresh = (avopts && avopts->analyze_threshold >= 0)
+ ? avopts->analyze_threshold
: autovacuum_anl_thresh;
- freeze_max_age = (relopts && relopts->freeze_max_age >= 0)
- ? Min(relopts->freeze_max_age, autovacuum_freeze_max_age)
+ freeze_max_age = (avopts && avopts->freeze_max_age >= 0)
+ ? Min(avopts->freeze_max_age, autovacuum_freeze_max_age)
: autovacuum_freeze_max_age;
- multixact_freeze_max_age = (relopts && relopts->multixact_freeze_max_age >= 0)
- ? Min(relopts->multixact_freeze_max_age, effective_multixact_freeze_max_age)
+ multixact_freeze_max_age = (avopts && avopts->multixact_freeze_max_age >= 0)
+ ? Min(avopts->multixact_freeze_max_age, effective_multixact_freeze_max_age)
: effective_multixact_freeze_max_age;
- av_enabled = (relopts ? relopts->enabled : true);
+ av_enabled = (avopts ? avopts->enabled : true);
/* Force vacuum if table is at risk of wraparound */
xidForceLimit = recentXid - freeze_max_age;
--
2.39.5 (Apple Git-154)
[text/plain] v1-0002-autovac-resolve-relopts-before-vacuuming.patch (4.7K, ../aFnATDLLdP0m-fGb@nathan/3-v1-0002-autovac-resolve-relopts-before-vacuuming.patch)
download | inline diff:
From a3bbb1fdcf6a66719af84b763861e187cc5588bd Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 14:42:39 -0500
Subject: [PATCH v1 2/4] autovac: resolve relopts before vacuuming
---
src/backend/commands/vacuum.c | 7 ++--
src/backend/postmaster/autovacuum.c | 52 +++++++++++++++++++++--------
2 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 33a33bf6b1c..0015b9ef4b0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -421,7 +421,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/*
* Later, in vacuum_rel(), we check if a reloption override was specified.
*/
- params.max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
+ params.max_eager_freeze_failure_rate = -1.0;
/*
* Create special memory context for cross-transaction storage.
@@ -2195,10 +2195,13 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
* Check if the vacuum_max_eager_freeze_failure_rate table storage
* parameter was specified. This overrides the GUC value.
*/
- if (rel->rd_options != NULL &&
+ if (params->max_eager_freeze_failure_rate < 0 &&
+ rel->rd_options != NULL &&
((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate >= 0)
params->max_eager_freeze_failure_rate =
((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+ else
+ params->max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
/*
* Set truncate option based on truncate reloption or GUC if it wasn't
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index f86c9fed853..3bedca971ff 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2758,6 +2758,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
int multixact_freeze_table_age;
int log_min_duration;
AutoVacOpts *avopts = (relopts ? &relopts->autovacuum : NULL);
+ VacOptValue index_cleanup;
+ VacOptValue truncate;
+ double max_eager_freeze_failure_rate;
/*
* Calculate the vacuum cost parameters and the freeze ages. If there
@@ -2790,6 +2793,39 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
? avopts->multixact_freeze_table_age
: default_multixact_freeze_table_age;
+ if (relopts)
+ {
+ switch (relopts->vacuum_index_cleanup)
+ {
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
+ index_cleanup = VACOPTVALUE_AUTO;
+ break;
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
+ index_cleanup = VACOPTVALUE_ENABLED;
+ break;
+ case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
+ index_cleanup = VACOPTVALUE_DISABLED;
+ break;
+ }
+ }
+ else
+ index_cleanup = VACOPTVALUE_UNSPECIFIED;
+
+ if (relopts && relopts->vacuum_truncate_set)
+ {
+ if (relopts->vacuum_truncate)
+ truncate = VACOPTVALUE_ENABLED;
+ else
+ truncate = VACOPTVALUE_DISABLED;
+ }
+ else
+ truncate = VACOPTVALUE_UNSPECIFIED;
+
+ if (relopts && relopts->vacuum_max_eager_freeze_failure_rate >= 0)
+ max_eager_freeze_failure_rate = relopts->vacuum_max_eager_freeze_failure_rate;
+ else
+ max_eager_freeze_failure_rate = -1.0;
+
tab = palloc(sizeof(autovac_table));
tab->at_relid = relid;
tab->at_sharedrel = classForm->relisshared;
@@ -2806,13 +2842,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
(doanalyze ? VACOPT_ANALYZE : 0) |
(!wraparound ? VACOPT_SKIP_LOCKED : 0);
- /*
- * index_cleanup and truncate are unspecified at first in autovacuum.
- * They will be filled in with usable values using their reloptions
- * (or reloption defaults) later.
- */
- tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
- tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
+ tab->at_params.index_cleanup = index_cleanup;
+ tab->at_params.truncate = truncate;
/* As of now, we don't support parallel vacuum for autovacuum */
tab->at_params.nworkers = -1;
tab->at_params.freeze_min_age = freeze_min_age;
@@ -2822,12 +2853,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
tab->at_params.toast_parent = InvalidOid;
-
- /*
- * Later, in vacuum_rel(), we check reloptions for any
- * vacuum_max_eager_freeze_failure_rate override.
- */
- tab->at_params.max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
+ tab->at_params.max_eager_freeze_failure_rate = max_eager_freeze_failure_rate;
tab->at_storage_param_vac_cost_limit = avopts ?
avopts->vacuum_cost_limit : 0;
tab->at_storage_param_vac_cost_delay = avopts ?
--
2.39.5 (Apple Git-154)
[text/plain] v1-0003-autovac-combine-reloptions-correctly.patch (4.1K, ../aFnATDLLdP0m-fGb@nathan/4-v1-0003-autovac-combine-reloptions-correctly.patch)
download | inline diff:
From 12b6404c906837c9244110095ca980ad3545bc0b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 15:16:38 -0500
Subject: [PATCH v1 3/4] autovac: combine reloptions correctly
---
src/backend/postmaster/autovacuum.c | 85 +++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3bedca971ff..0d1f4e38b58 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1873,6 +1873,73 @@ get_database_list(void)
return dblist;
}
+static void
+combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts)
+{
+ AutoVacOpts *toast_avopts = &toast_opts->autovacuum;
+ const AutoVacOpts *main_avopts = &main_opts->autovacuum;
+
+ /* XXX: need isset_offset to combine "enabled" */
+
+ if (toast_avopts->vacuum_threshold == -1)
+ toast_avopts->vacuum_threshold = main_avopts->vacuum_threshold;
+
+ if (toast_avopts->vacuum_max_threshold == -2)
+ toast_avopts->vacuum_max_threshold = main_avopts->vacuum_max_threshold;
+
+ if (toast_avopts->vacuum_ins_threshold == -2)
+ toast_avopts->vacuum_ins_threshold = main_avopts->vacuum_ins_threshold;
+
+ if (toast_avopts->analyze_threshold == -1)
+ toast_avopts->analyze_threshold = main_avopts->analyze_threshold;
+
+ if (toast_avopts->vacuum_cost_limit == -1)
+ toast_avopts->vacuum_cost_limit = main_avopts->vacuum_cost_limit;
+
+ if (toast_avopts->freeze_min_age == -1)
+ toast_avopts->freeze_min_age = main_avopts->freeze_min_age;
+
+ if (toast_avopts->freeze_max_age == -1)
+ toast_avopts->freeze_max_age = main_avopts->freeze_max_age;
+
+ if (toast_avopts->freeze_table_age == -1)
+ toast_avopts->freeze_table_age = main_avopts->freeze_table_age;
+
+ if (toast_avopts->multixact_freeze_min_age == -1)
+ toast_avopts->multixact_freeze_min_age = main_avopts->multixact_freeze_min_age;
+
+ if (toast_avopts->multixact_freeze_max_age == -1)
+ toast_avopts->multixact_freeze_max_age = main_avopts->multixact_freeze_max_age;
+
+ if (toast_avopts->multixact_freeze_table_age == -1)
+ toast_avopts->multixact_freeze_table_age = main_avopts->multixact_freeze_table_age;
+
+ /* XXX: need isset_offset for log_min_duration */
+
+ if (toast_avopts->vacuum_cost_delay == -1)
+ toast_avopts->vacuum_cost_delay = main_avopts->vacuum_cost_delay;
+
+ if (toast_avopts->vacuum_scale_factor == -1)
+ toast_avopts->vacuum_scale_factor = main_avopts->vacuum_scale_factor;
+
+ if (toast_avopts->vacuum_ins_scale_factor == -1)
+ toast_avopts->vacuum_ins_scale_factor = main_avopts->vacuum_ins_scale_factor;
+
+ if (toast_avopts->analyze_scale_factor == -1)
+ toast_avopts->analyze_scale_factor = main_avopts->analyze_scale_factor;
+
+ /* XXX: need isset_offset for vacuum_index_cleanup */
+
+ if (!toast_opts->vacuum_truncate_set && main_opts->vacuum_truncate_set)
+ {
+ toast_opts->vacuum_truncate = main_opts->vacuum_truncate;
+ toast_opts->vacuum_truncate_set = true;
+ }
+
+ if (toast_opts->vacuum_max_eager_freeze_failure_rate == -1)
+ toast_opts->vacuum_max_eager_freeze_failure_rate = main_opts->vacuum_max_eager_freeze_failure_rate;
+}
+
/*
* Process a database table-by-table
*
@@ -2113,7 +2180,16 @@ do_autovacuum(void)
*/
relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
if (relopts)
+ {
+ av_relation *hentry;
+ bool found;
+
free_relopts = true;
+
+ hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
+ if (found && hentry->ar_hasrelopts)
+ combine_relopts(relopts, &hentry->ar_reloptions);
+ }
else
{
av_relation *hentry;
@@ -2733,7 +2809,16 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
*/
relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, NULL);
if (relopts)
+ {
+ av_relation *hentry;
+ bool found;
+
free_relopts = true;
+
+ hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
+ if (found && hentry->ar_hasrelopts)
+ combine_relopts(relopts, &hentry->ar_reloptions);
+ }
else if (classForm->relkind == RELKIND_TOASTVALUE &&
table_toast_map != NULL)
{
--
2.39.5 (Apple Git-154)
[text/plain] v1-0004-combine-relopts-correctly-for-VACUUM-commands.patch (5.5K, ../aFnATDLLdP0m-fGb@nathan/5-v1-0004-combine-relopts-correctly-for-VACUUM-commands.patch)
download | inline diff:
From 724f90f4d19a2c92026adffcde8bf47967378f1e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 23 Jun 2025 15:40:28 -0500
Subject: [PATCH v1 4/4] combine relopts correctly for VACUUM commands
---
src/backend/commands/vacuum.c | 30 +++++++++++++++++++----------
src/backend/postmaster/autovacuum.c | 2 +-
src/include/commands/vacuum.h | 2 ++
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0015b9ef4b0..a74c3a9e2a2 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -124,7 +124,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy, StdRdOptions *main_opts);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -634,7 +634,7 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy, NULL))
continue;
}
@@ -1998,7 +1998,7 @@ vac_truncate_clog(TransactionId frozenXID,
*/
static bool
vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- BufferAccessStrategy bstrategy)
+ BufferAccessStrategy bstrategy, StdRdOptions *main_opts)
{
LOCKMODE lmode;
Relation rel;
@@ -2008,6 +2008,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
Oid save_userid;
int save_sec_context;
int save_nestlevel;
+ StdRdOptions saved_opts;
+ StdRdOptions combined_opts;
Assert(params != NULL);
@@ -2165,6 +2167,15 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
lockrelid = rel->rd_lockInfo.lockRelId;
LockRelationIdForSession(&lockrelid, lmode);
+ if (rel->rd_options)
+ {
+ memcpy(&saved_opts, rel->rd_options, sizeof(StdRdOptions));
+ memcpy(&combined_opts, rel->rd_options, sizeof(StdRdOptions));
+
+ if (main_opts)
+ combine_relopts(&combined_opts, main_opts);
+ }
+
/*
* Set index_cleanup option based on index_cleanup reloption if it wasn't
* specified in VACUUM command, or when running in an autovacuum worker
@@ -2176,8 +2187,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
if (rel->rd_options == NULL)
vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
else
- vacuum_index_cleanup =
- ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
+ vacuum_index_cleanup = combined_opts.vacuum_index_cleanup;
if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
params->index_cleanup = VACOPTVALUE_AUTO;
@@ -2197,9 +2207,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
*/
if (params->max_eager_freeze_failure_rate < 0 &&
rel->rd_options != NULL &&
- ((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate >= 0)
+ combined_opts.vacuum_max_eager_freeze_failure_rate >= 0)
params->max_eager_freeze_failure_rate =
- ((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+ combined_opts.vacuum_max_eager_freeze_failure_rate;
else
params->max_eager_freeze_failure_rate = vacuum_max_eager_freeze_failure_rate;
@@ -2211,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && combined_opts.vacuum_truncate_set)
{
- if (opts->vacuum_truncate)
+ if (combined_opts.vacuum_truncate)
params->truncate = VACOPTVALUE_ENABLED;
else
params->truncate = VACOPTVALUE_DISABLED;
@@ -2314,7 +2324,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
toast_vacuum_params.toast_parent = relid;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, bstrategy, &saved_opts);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0d1f4e38b58..ca399b6a92e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1873,7 +1873,7 @@ get_database_list(void)
return dblist;
}
-static void
+void
combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts)
{
AutoVacOpts *toast_avopts = &toast_opts->autovacuum;
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bc37a80dc74..928f864581b 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -24,6 +24,7 @@
#include "parser/parse_node.h"
#include "storage/buf.h"
#include "storage/lock.h"
+#include "utils/rel.h"
#include "utils/relcache.h"
/*
@@ -377,6 +378,7 @@ extern IndexBulkDeleteResult *vac_cleanup_one_index(IndexVacuumInfo *ivinfo,
/* In postmaster/autovacuum.c */
extern void AutoVacuumUpdateCostLimit(void);
extern void VacuumUpdateCosts(void);
+extern void combine_relopts(StdRdOptions *toast_opts, const StdRdOptions *main_opts);
/* in commands/vacuumparallel.c */
extern ParallelVacuumState *parallel_vacuum_init(Relation rel, Relation *indrels,
--
2.39.5 (Apple Git-154)
view thread (44+ messages) latest in thread
Message-ID: <aFnATDLLdP0m-fGb@nathan>
Permalink: ../aFnATDLLdP0m-fGb@nathan/
Also on: postgresql.org/message-id/aFnATDLLdP0m-fGb@nathan
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: nathandbossart@gmail.com, michael@paquier.xyz
Subject: Re: problems with toast.* reloptions
In-Reply-To: <aFnATDLLdP0m-fGb@nathan>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox