Subject: [PATCH v1 1/4] autovac: save all relopts instead of just avopts

From: Nathan Bossart <[email-masked]>
Date: Mon, 23 Jun 2025 14:07:30 -0500

---
 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)

--QWBYbd5Ar5k8NvSv
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename=v1-0002-autovac-resolve-relopts-before-vacuuming.patch