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 <nat...@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 <nat...@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 <nat...@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 <nat...@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)

Reply via email to