From 85e99681cc7207b8e62a0ba8e604fde36bd5f6e4 Mon Sep 17 00:00:00 2001
From: Sami Imseih <samimseih@gmail.com>
Date: Fri, 27 Mar 2026 20:38:26 +0000
Subject: [PATCH v1 1/2] Add force_scores option to relation_needs_vacanalyze

A future commit will need the ability for relation_needs_vacanalyze()
to forcefully calculate an autovacuum priority score even if autovacuum
would otherwise abandon the computation of the score. This is needed for
tools that wish to introspect the score outside of the normal path of
do_autovacuum().
---
 src/backend/postmaster/autovacuum.c | 81 +++++++++++++++++------------
 1 file changed, 48 insertions(+), 33 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d695f1de4bd..f6b213852e3 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -378,6 +378,7 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
 									  Form_pg_class classForm,
 									  PgStat_StatTabEntry *tabentry,
 									  int effective_multixact_freeze_max_age,
+									  bool force_scores,
 									  bool *dovacuum, bool *doanalyze, bool *wraparound,
 									  AutoVacuumScores *scores);
 
@@ -2075,6 +2076,7 @@ do_autovacuum(void)
 		/* Check if it needs vacuum or analyze */
 		relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
 								  effective_multixact_freeze_max_age,
+								  false,
 								  &dovacuum, &doanalyze, &wraparound,
 								  &scores);
 
@@ -2175,6 +2177,7 @@ do_autovacuum(void)
 
 		relation_needs_vacanalyze(relid, relopts, classForm, tabentry,
 								  effective_multixact_freeze_max_age,
+								  false,
 								  &dovacuum, &doanalyze, &wraparound,
 								  &scores);
 
@@ -2993,6 +2996,7 @@ recheck_relation_needs_vacanalyze(Oid relid,
 
 	relation_needs_vacanalyze(relid, avopts, classForm, tabentry,
 							  effective_multixact_freeze_max_age,
+							  false,
 							  dovacuum, doanalyze, wraparound,
 							  &scores);
 
@@ -3080,6 +3084,9 @@ recheck_relation_needs_vacanalyze(Oid relid,
  * The autovacuum table score is returned in scores->max.  The component scores
  * are also returned in the "scores" argument via the other members of the
  * AutoVacuumScores struct.
+ *
+ * force_scores set to true forces the computation of a score. This is useful for
+ * tools that wish to inspect scores outside of the do_vacuum() path.
  */
 static void
 relation_needs_vacanalyze(Oid relid,
@@ -3087,6 +3094,7 @@ relation_needs_vacanalyze(Oid relid,
 						  Form_pg_class classForm,
 						  PgStat_StatTabEntry *tabentry,
 						  int effective_multixact_freeze_max_age,
+						  bool force_scores,
  /* output params below */
 						  bool *dovacuum,
 						  bool *doanalyze,
@@ -3252,18 +3260,21 @@ relation_needs_vacanalyze(Oid relid,
 		*dovacuum = true;
 	}
 
-	/* User disabled it in pg_class.reloptions?  (But ignore if at risk) */
-	if (!av_enabled && !force_vacuum)
+	/*
+	 * User disabled it in pg_class.reloptions?  (But ignore if at risk or
+	 * forced)
+	 */
+	if (!force_scores && !av_enabled && !force_vacuum)
 		return;
 
 	/*
-	 * If we found stats for the table, and autovacuum is currently enabled,
-	 * make a threshold-based decision whether to vacuum and/or analyze.  If
-	 * autovacuum is currently disabled, we must be here for anti-wraparound
-	 * vacuuming only, so don't vacuum (or analyze) anything that's not being
-	 * forced.
+	 * If we found stats for the table, and autovacuum is currently enabled
+	 * (or scores are forced), make a threshold-based decision whether to
+	 * vacuum and/or analyze.  If autovacuum is currently disabled, we must be
+	 * here for anti-wraparound vacuuming only, so don't vacuum (or analyze)
+	 * anything that's not being forced.
 	 */
-	if (tabentry && AutoVacuumingActive())
+	if (tabentry && (AutoVacuumingActive() || force_scores))
 	{
 		float4		pcnt_unfrozen = 1;
 		float4		reltuples = classForm->reltuples;
@@ -3304,32 +3315,33 @@ relation_needs_vacanalyze(Oid relid,
 		anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples;
 
 		/*
-		 * Determine if this table needs vacuum, and update the score if it
-		 * does.
+		 * Update the vacuum score and determine if this table needs vacuum
 		 */
+		scores->vac = (double) vactuples / Max(vacthresh, 1);
+		scores->vac *= autovacuum_vacuum_score_weight;
+		scores->max = Max(scores->max, scores->vac);
 		if (vactuples > vacthresh)
-		{
-			scores->vac = (double) vactuples / Max(vacthresh, 1);
-			scores->vac *= autovacuum_vacuum_score_weight;
-			scores->max = Max(scores->max, scores->vac);
 			*dovacuum = true;
-		}
 
-		if (vac_ins_base_thresh >= 0 && instuples > vacinsthresh)
+		/*
+		 * Ditto for insert vacuum
+		 */
+		if (vac_ins_base_thresh >= 0)
 		{
 			scores->vac_ins = (double) instuples / Max(vacinsthresh, 1);
 			scores->vac_ins *= autovacuum_vacuum_insert_score_weight;
 			scores->max = Max(scores->max, scores->vac_ins);
-			*dovacuum = true;
 		}
+		if (vac_ins_base_thresh >= 0 && instuples > vacinsthresh)
+			*dovacuum = true;
 
 		/*
 		 * Determine if this table needs analyze, and update the score if it
 		 * does.  Note that we don't analyze TOAST tables and pg_statistic.
 		 */
-		if (anltuples > anlthresh &&
-			relid != StatisticRelationId &&
-			classForm->relkind != RELKIND_TOASTVALUE)
+		if ((anltuples > anlthresh &&
+			 relid != StatisticRelationId &&
+			 classForm->relkind != RELKIND_TOASTVALUE))
 		{
 			scores->anl = (double) anltuples / Max(anlthresh, 1);
 			scores->anl *= autovacuum_analyze_score_weight;
@@ -3337,19 +3349,22 @@ relation_needs_vacanalyze(Oid relid,
 			*doanalyze = true;
 		}
 
-		if (vac_ins_base_thresh >= 0)
-			elog(DEBUG3, "%s: vac: %.0f (thresh %.0f, score %.2f), ins: %.0f (thresh %.0f, score %.2f), anl: %.0f (thresh %.0f, score %.2f), xid score: %.2f, mxid score: %.2f",
-				 NameStr(classForm->relname),
-				 vactuples, vacthresh, scores->vac,
-				 instuples, vacinsthresh, scores->vac_ins,
-				 anltuples, anlthresh, scores->anl,
-				 scores->xid, scores->mxid);
-		else
-			elog(DEBUG3, "%s: vac: %.0f (thresh %.0f, score %.2f), ins: (disabled), anl: %.0f (thresh %.0f, score %.2f), xid score: %.2f, mxid score: %.2f",
-				 NameStr(classForm->relname),
-				 vactuples, vacthresh, scores->vac,
-				 anltuples, anlthresh, scores->anl,
-				 scores->xid, scores->mxid);
+		if (!force_scores)
+		{
+			if (vac_ins_base_thresh >= 0)
+				elog(DEBUG3, "%s: vac: %.0f (thresh %.0f, score %.2f), ins: %.0f (thresh %.0f, score %.2f), anl: %.0f (thresh %.0f, score %.2f), xid score: %.2f, mxid score: %.2f",
+					 NameStr(classForm->relname),
+					 vactuples, vacthresh, scores->vac,
+					 instuples, vacinsthresh, scores->vac_ins,
+					 anltuples, anlthresh, scores->anl,
+					 scores->xid, scores->mxid);
+			else
+				elog(DEBUG3, "%s: vac: %.0f (thresh %.0f, score %.2f), ins: (disabled), anl: %.0f (thresh %.0f, score %.2f), xid score: %.2f, mxid score: %.2f",
+					 NameStr(classForm->relname),
+					 vactuples, vacthresh, scores->vac,
+					 anltuples, anlthresh, scores->anl,
+					 scores->xid, scores->mxid);
+		}
 	}
 }
 
-- 
2.47.3

