diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 58eb72e2339..94e9152cbab 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -348,7 +348,7 @@ static relopt_int intRelOpts[] =
 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
 			ShareUpdateExclusiveLock
 		},
-		-1, -1, INT_MAX
+		-2, -1, INT_MAX
 	},
 	{
 		{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 52116c02b59..6a8df38ca13 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -187,6 +187,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 
 	/* Will be set later if we recurse to a TOAST table. */
 	params.toast_parent = InvalidOid;
+	params.main_index_cleanup = VACOPTVALUE_UNSPECIFIED;
+	params.main_truncate = VACOPTVALUE_UNSPECIFIED;
+	params.main_max_eager_freeze_failure_rate = -1.0;
 
 	/*
 	 * Set this to an invalid value so it is clear whether or not a
@@ -2203,7 +2206,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 
 	/*
 	 * Set index_cleanup option based on index_cleanup reloption if it wasn't
-	 * specified in VACUUM command, or when running in an autovacuum worker
+	 * specified in VACUUM command, or when running in an autovacuum worker. A
+	 * TOAST table with no setting of its own inherits the main table's value.
 	 */
 	if (params.index_cleanup == VACOPTVALUE_UNSPECIFIED)
 	{
@@ -2219,15 +2223,21 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		{
 			case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
 				params.index_cleanup = VACOPTVALUE_ENABLED;
+				toast_vacuum_params.main_index_cleanup = VACOPTVALUE_ENABLED;
 				break;
 			case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
 				params.index_cleanup = VACOPTVALUE_DISABLED;
+				toast_vacuum_params.main_index_cleanup = VACOPTVALUE_DISABLED;
 				break;
 			case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
 				params.index_cleanup = VACOPTVALUE_AUTO;
+				toast_vacuum_params.main_index_cleanup = VACOPTVALUE_AUTO;
 				break;
 			case STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET:
-				params.index_cleanup = VACOPTVALUE_AUTO;
+				if (params.main_index_cleanup != VACOPTVALUE_UNSPECIFIED)
+					params.index_cleanup = params.main_index_cleanup;
+				else
+					params.index_cleanup = VACOPTVALUE_AUTO;
 				break;
 		}
 	}
@@ -2243,16 +2253,26 @@ 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.
+	 * parameter was specified. This overrides the GUC value.  A TOAST table
+	 * with no setting of its own inherits the main table's value.
 	 */
 	if (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;
+		toast_vacuum_params.main_max_eager_freeze_failure_rate =
+			((StdRdOptions *) rel->rd_options)->vacuum_max_eager_freeze_failure_rate;
+	}
+	else if (params.main_max_eager_freeze_failure_rate >= 0.0)
+		params.max_eager_freeze_failure_rate =
+			params.main_max_eager_freeze_failure_rate;
 
 	/*
 	 * Set truncate option based on truncate reloption or GUC if it wasn't
-	 * specified in VACUUM command, or when running in an autovacuum worker
+	 * specified in VACUUM command, or when running in an autovacuum worker. A
+	 * TOAST table with no setting of its own inherits the main table's value
+	 * before falling back to the GUC.
 	 */
 	if (params.truncate == VACOPTVALUE_UNSPECIFIED)
 	{
@@ -2261,10 +2281,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		if (opts && opts->vacuum_truncate != PG_TERNARY_UNSET)
 		{
 			if (opts->vacuum_truncate == PG_TERNARY_TRUE)
+			{
 				params.truncate = VACOPTVALUE_ENABLED;
+				toast_vacuum_params.main_truncate = VACOPTVALUE_ENABLED;
+			}
 			else
+			{
 				params.truncate = VACOPTVALUE_DISABLED;
+				toast_vacuum_params.main_truncate = VACOPTVALUE_DISABLED;
+			}
 		}
+		else if (params.main_truncate != VACOPTVALUE_UNSPECIFIED)
+			params.truncate = params.main_truncate;
 		else if (vacuum_truncate)
 			params.truncate = VACOPTVALUE_ENABLED;
 		else
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c8a1b66ca2e..7b72a262ca5 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -394,6 +394,8 @@ static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
 static void avl_sigusr2_handler(SIGNAL_ARGS);
 static bool av_worker_available(void);
 static void check_av_worker_gucs(void);
+static StdRdOptions *merge_autovac_opts(StdRdOptions *toast_opts,
+										StdRdOptions *main_opts);
 
 
 
@@ -2017,9 +2019,9 @@ do_autovacuum(void)
 	 * We do this in two passes: on the first one we collect the list of plain
 	 * relations and materialized views, and on the second one we collect
 	 * TOAST tables. The reason for doing the second pass is that during it we
-	 * want to use the main relation's pg_class.reloptions entry if the TOAST
-	 * table does not have any, and we cannot obtain it unless we know
-	 * beforehand what's the main table OID.
+	 * want to fill in any storage parameters that the TOAST table does not
+	 * set with the main relation's, and we cannot obtain those values unless
+	 * we know beforehand what's the main table OID.
 	 *
 	 * We need to check TOAST tables separately because in cases with short,
 	 * wide tables there might be proportionally much more activity in the
@@ -2144,6 +2146,8 @@ do_autovacuum(void)
 		bool		doanalyze;
 		bool		wraparound;
 		AutoVacuumScores scores;
+		av_relation *hentry;
+		bool		found;
 
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
@@ -2154,21 +2158,19 @@ do_autovacuum(void)
 		relid = classForm->oid;
 
 		/*
-		 * fetch reloptions -- if this toast table does not have them, try the
-		 * main rel
+		 * fetch reloptions -- merge any unset options from the main rel
+		 *
+		 * Note that we don't bother merging the non-autovacuum relopts here
+		 * because they do not impact our choice of whether to process the
+		 * table.
 		 */
 		relopts = (StdRdOptions *) extractRelOptions(tuple, pg_class_desc, NULL);
 		if (relopts)
 			free_relopts = true;
-		else
-		{
-			av_relation *hentry;
-			bool		found;
 
-			hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
-			if (found && hentry->ar_hasrelopts)
-				relopts = &hentry->ar_reloptions;
-		}
+		hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
+		if (found && hentry->ar_hasrelopts)
+			relopts = merge_autovac_opts(relopts, &hentry->ar_reloptions);
 
 		relation_needs_vacanalyze(relid, relopts, classForm,
 								  effective_multixact_freeze_max_age,
@@ -2793,6 +2795,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 	autovac_table *tab = NULL;
 	bool		wraparound;
 	StdRdOptions *relopts;
+	StdRdOptions *main_relopts = NULL;
 	bool		free_relopts = false;
 	AutoVacuumScores scores;
 
@@ -2803,21 +2806,25 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 	classForm = (Form_pg_class) GETSTRUCT(classTup);
 
 	/*
-	 * 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.
+	 * Get the applicable reloptions.  If it is a TOAST table, merge in the
+	 * main table's reloptions where they are unset.
 	 */
 	relopts = (StdRdOptions *) extractRelOptions(classTup, pg_class_desc, NULL);
 	if (relopts)
 		free_relopts = true;
-	else if (classForm->relkind == RELKIND_TOASTVALUE &&
-			 table_toast_map != NULL)
+
+	if (classForm->relkind == RELKIND_TOASTVALUE &&
+		table_toast_map != NULL)
 	{
 		av_relation *hentry;
 		bool		found;
 
 		hentry = hash_search(table_toast_map, &relid, HASH_FIND, &found);
 		if (found && hentry->ar_hasrelopts)
-			relopts = &hentry->ar_reloptions;
+		{
+			main_relopts = &hentry->ar_reloptions;
+			relopts = merge_autovac_opts(relopts, main_relopts);
+		}
 	}
 
 	relation_needs_vacanalyze(relid, relopts, classForm,
@@ -2904,6 +2911,48 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
 		tab->at_params.log_analyze_min_duration = log_analyze_min_duration;
 		tab->at_params.toast_parent = InvalidOid;
 
+		/*
+		 * For TOAST tables, provide fallbacks for options that are not part
+		 * of AutoVacOpts (and thus are not handled by merge_autovac_opts()).
+		 */
+		tab->at_params.main_index_cleanup = VACOPTVALUE_UNSPECIFIED;
+		tab->at_params.main_truncate = VACOPTVALUE_UNSPECIFIED;
+		tab->at_params.main_max_eager_freeze_failure_rate = -1.0;
+
+		if (main_relopts != NULL)
+		{
+			switch (main_relopts->vacuum_index_cleanup)
+			{
+				case STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON:
+					tab->at_params.main_index_cleanup = VACOPTVALUE_ENABLED;
+					break;
+				case STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF:
+					tab->at_params.main_index_cleanup = VACOPTVALUE_DISABLED;
+					break;
+				case STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO:
+					tab->at_params.main_index_cleanup = VACOPTVALUE_AUTO;
+					break;
+				case STDRD_OPTION_VACUUM_INDEX_CLEANUP_NOT_SET:
+					break;
+			}
+
+			switch (main_relopts->vacuum_truncate)
+			{
+				case PG_TERNARY_TRUE:
+					tab->at_params.main_truncate = VACOPTVALUE_ENABLED;
+					break;
+				case PG_TERNARY_FALSE:
+					tab->at_params.main_truncate = VACOPTVALUE_DISABLED;
+					break;
+				case PG_TERNARY_UNSET:
+					break;
+			}
+
+			if (main_relopts->vacuum_max_eager_freeze_failure_rate >= 0.0)
+				tab->at_params.main_max_eager_freeze_failure_rate =
+					main_relopts->vacuum_max_eager_freeze_failure_rate;
+		}
+
 		/* Determine the number of parallel vacuum workers to use */
 		tab->at_params.nworkers = 0;
 		if (avopts)
@@ -2960,9 +3009,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
  * "dovacuum" and "doanalyze", respectively.  Also return whether the vacuum is
  * being forced because of Xid or multixact wraparound.
  *
- * 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.
+ * relopts is a pointer to the StdRdOptions options (the relation's own for a
+ * plain table, or those merged with the main table's for a TOAST table), NULL
+ * if none.
  *
  * A table needs to be vacuumed if the number of dead tuples exceeds a
  * threshold.  This threshold is calculated as
@@ -3619,6 +3668,8 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
 	Relation	rel;
 	TableScanDesc scan;
 	HeapTuple	tup;
+	HTAB	   *table_toast_map;
+	HASHCTL		ctl;
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 
 	InitMaterializedSRF(fcinfo, 0);
@@ -3628,13 +3679,64 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
 	recentXid = ReadNextTransactionId();
 	recentMulti = ReadNextMultiXactId();
 
-	/* scan pg_class */
+	/* create hash table for toast <-> main relid mapping */
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(av_relation);
+	table_toast_map = hash_create("TOAST to main relid map",
+								  100,
+								  &ctl,
+								  HASH_ELEM | HASH_BLOBS);
+
 	rel = table_open(RelationRelationId, AccessShareLock);
+
+	/*
+	 * Do an initial pass over pg_class to collect the main relations'
+	 * reloptions, which we need in order to compute their TOAST tables'
+	 * effective options below.
+	 */
 	scan = table_beginscan_catalog(rel, 0, NULL);
 	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		Form_pg_class form = (Form_pg_class) GETSTRUCT(tup);
 		StdRdOptions *relopts;
+		av_relation *hentry;
+		bool		found;
+
+		/* skip ineligible entries */
+		if (form->relkind != RELKIND_RELATION &&
+			form->relkind != RELKIND_MATVIEW)
+			continue;
+		if (form->relpersistence == RELPERSISTENCE_TEMP)
+			continue;
+		if (!OidIsValid(form->reltoastrelid))
+			continue;
+
+		relopts = (StdRdOptions *) extractRelOptions(tup, RelationGetDescr(rel), NULL);
+		if (!relopts)
+			continue;
+
+		hentry = hash_search(table_toast_map, &form->reltoastrelid,
+							 HASH_ENTER, &found);
+		Assert(!found);
+
+		/* hash_search already filled in the key */
+		hentry->ar_relid = form->oid;
+		memcpy(&hentry->ar_reloptions, relopts, sizeof(StdRdOptions));
+
+		pfree(relopts);
+	}
+	table_endscan(scan);
+
+	/*
+	 * Now that we have all parent tables' reloptions, we can generate the
+	 * results.
+	 */
+	scan = table_beginscan_catalog(rel, 0, NULL);
+	while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	{
+		Form_pg_class form = (Form_pg_class) GETSTRUCT(tup);
+		StdRdOptions *relopts;
+		bool		free_relopts = false;
 		bool		dovacuum;
 		bool		doanalyze;
 		bool		wraparound;
@@ -3651,12 +3753,25 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
 			continue;
 
 		relopts = (StdRdOptions *) extractRelOptions(tup, RelationGetDescr(rel), NULL);
+		if (relopts)
+			free_relopts = true;
+		if (form->relkind == RELKIND_TOASTVALUE)
+		{
+			av_relation *hentry;
+			bool		found;
+
+			hentry = hash_search(table_toast_map, &form->oid,
+								 HASH_FIND, &found);
+			if (found)
+				relopts = merge_autovac_opts(relopts, &hentry->ar_reloptions);
+		}
+
 		relation_needs_vacanalyze(form->oid, relopts, form,
 								  effective_multixact_freeze_max_age,
 								  LOG_NEVER,
 								  &dovacuum, &doanalyze, &wraparound,
 								  &scores);
-		if (relopts)
+		if (free_relopts)
 			pfree(relopts);
 
 		vals[0] = ObjectIdGetDatum(form->oid);
@@ -3674,6 +3789,111 @@ pg_stat_get_autovacuum_scores(PG_FUNCTION_ARGS)
 	}
 	table_endscan(scan);
 	table_close(rel, AccessShareLock);
+	hash_destroy(table_toast_map);
 
 	return (Datum) 0;
 }
+
+/*
+ * Combine a TOAST table's relevant autovacuum options with the main table's.
+ * Any option the TOAST table leaves unset is taken from the main table's
+ * options.  Either argument may be NULL.  If both are NULL, NULL is returned.
+ * Otherwise, the options to use are returned.
+ *
+ * NB: This function destructively modifies toast_opts!
+ */
+static StdRdOptions *
+merge_autovac_opts(StdRdOptions *toast_opts, StdRdOptions *main_opts)
+{
+	/* ternary fields */
+	static const int ternary_offsets[] = {
+		offsetof(AutoVacOpts, enabled),
+	};
+
+	/* integer fields whose unset sentinel is -1 */
+	static const int int_offsets_1[] = {
+		offsetof(AutoVacOpts, vacuum_threshold),
+		offsetof(AutoVacOpts, vacuum_cost_limit),
+		offsetof(AutoVacOpts, freeze_min_age),
+		offsetof(AutoVacOpts, freeze_max_age),
+		offsetof(AutoVacOpts, freeze_table_age),
+		offsetof(AutoVacOpts, multixact_freeze_min_age),
+		offsetof(AutoVacOpts, multixact_freeze_max_age),
+		offsetof(AutoVacOpts, multixact_freeze_table_age),
+	};
+
+	/* integer fields whose unset sentinel is -2 */
+	static const int int_offsets_2[] = {
+		offsetof(AutoVacOpts, vacuum_max_threshold),
+		offsetof(AutoVacOpts, vacuum_ins_threshold),
+		offsetof(AutoVacOpts, log_vacuum_min_duration),
+	};
+
+	/* float fields */
+	static const int float_offsets[] = {
+		offsetof(AutoVacOpts, vacuum_cost_delay),
+		offsetof(AutoVacOpts, vacuum_scale_factor),
+		offsetof(AutoVacOpts, vacuum_ins_scale_factor),
+	};
+
+	AutoVacOpts *toast_avopts;
+	AutoVacOpts *main_avopts;
+
+	if (toast_opts == NULL)
+		return main_opts;
+	if (main_opts == NULL)
+		return toast_opts;
+
+	toast_avopts = &toast_opts->autovacuum;
+	main_avopts = &main_opts->autovacuum;
+
+	for (int i = 0; i < lengthof(ternary_offsets); i++)
+	{
+		pg_ternary *toast_opt;
+		pg_ternary *main_opt;
+
+		toast_opt = (pg_ternary *) ((char *) toast_avopts + ternary_offsets[i]);
+		main_opt = (pg_ternary *) ((char *) main_avopts + ternary_offsets[i]);
+
+		if (*toast_opt == PG_TERNARY_UNSET)
+			*toast_opt = *main_opt;
+	}
+
+	for (int i = 0; i < lengthof(int_offsets_1); i++)
+	{
+		int		   *toast_opt;
+		int		   *main_opt;
+
+		toast_opt = (int *) ((char *) toast_avopts + int_offsets_1[i]);
+		main_opt = (int *) ((char *) main_avopts + int_offsets_1[i]);
+
+		if (*toast_opt == -1)
+			*toast_opt = *main_opt;
+	}
+
+	for (int i = 0; i < lengthof(int_offsets_2); i++)
+	{
+		int		   *toast_opt;
+		int		   *main_opt;
+
+		toast_opt = (int *) ((char *) toast_avopts + int_offsets_2[i]);
+		main_opt = (int *) ((char *) main_avopts + int_offsets_2[i]);
+
+		if (*toast_opt == -2)
+			*toast_opt = *main_opt;
+	}
+
+	for (int i = 0; i < lengthof(float_offsets); i++)
+	{
+		double	   *toast_opt;
+		double	   *main_opt;
+
+		toast_opt = (double *) ((char *) toast_avopts + float_offsets[i]);
+		main_opt = (double *) ((char *) main_avopts + float_offsets[i]);
+
+		if (*toast_opt == -1.0)
+			*toast_opt = *main_opt;
+	}
+
+	return toast_opts;
+}
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index e62f23748dc..fdfceb38396 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -248,6 +248,14 @@ typedef struct VacuumParams
 	 * disabled.
 	 */
 	int			nworkers;
+
+	/*
+	 * Main table fallback values for TOAST tables to inherit when they have
+	 * no setting of their own.
+	 */
+	VacOptValue main_index_cleanup;
+	VacOptValue main_truncate;
+	double		main_max_eager_freeze_failure_rate;
 } VacuumParams;
 
 /*
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 01b6e1a872c..4750ea8d864 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -306,6 +306,9 @@ typedef struct ForeignKeyCacheInfo
  * RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only
  * be applied to relations that use this format or a superset for
  * private options data.
+ *
+ * NB: When adding a new member, be sure to update merge_autovac_opts() and/or
+ * table_recheck_autovac() as necessary!
  */
  /* autovacuum-related reloptions. */
 typedef struct AutoVacOpts
diff --git a/src/test/modules/injection_points/expected/vacuum.out b/src/test/modules/injection_points/expected/vacuum.out
index 58df59fa927..0f0790a8584 100644
--- a/src/test/modules/injection_points/expected/vacuum.out
+++ b/src/test/modules/injection_points/expected/vacuum.out
@@ -79,6 +79,17 @@ NOTICE:  notice triggered for injection point vacuum-truncate-enabled
 NOTICE:  notice triggered for injection point vacuum-index-cleanup-auto
 NOTICE:  notice triggered for injection point vacuum-truncate-enabled
 RESET vacuum_truncate;
+-- TOAST table inherits main table's resolved values
+CREATE TABLE vac_tab_toast_inherit(i int, j text) WITH
+  (autovacuum_enabled=false,
+   vacuum_index_cleanup=false,
+   vacuum_truncate=false, toast.vacuum_truncate=true);
+VACUUM vac_tab_toast_inherit;
+NOTICE:  notice triggered for injection point vacuum-index-cleanup-disabled
+NOTICE:  notice triggered for injection point vacuum-truncate-disabled
+NOTICE:  notice triggered for injection point vacuum-index-cleanup-disabled
+NOTICE:  notice triggered for injection point vacuum-truncate-enabled
+DROP TABLE vac_tab_toast_inherit;
 DROP TABLE vac_tab_auto;
 DROP TABLE vac_tab_on_toast_off;
 DROP TABLE vac_tab_off_toast_on;
diff --git a/src/test/modules/injection_points/sql/vacuum.sql b/src/test/modules/injection_points/sql/vacuum.sql
index 23760dd0f38..11a371e4ff7 100644
--- a/src/test/modules/injection_points/sql/vacuum.sql
+++ b/src/test/modules/injection_points/sql/vacuum.sql
@@ -33,6 +33,14 @@ SET vacuum_truncate = true;
 VACUUM vac_tab_auto;
 RESET vacuum_truncate;
 
+-- TOAST table inherits main table's resolved values
+CREATE TABLE vac_tab_toast_inherit(i int, j text) WITH
+  (autovacuum_enabled=false,
+   vacuum_index_cleanup=false,
+   vacuum_truncate=false, toast.vacuum_truncate=true);
+VACUUM vac_tab_toast_inherit;
+DROP TABLE vac_tab_toast_inherit;
+
 DROP TABLE vac_tab_auto;
 DROP TABLE vac_tab_on_toast_off;
 DROP TABLE vac_tab_off_toast_on;
-- 
2.50.1 (Apple Git-155)
