diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 03ca079..4ac3d69 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -908,6 +908,41 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
 }
 
 /*
+ * Check if we could add "distance" to "start", without moving past
+ * "boundary", using modulo arithmetic.
+ */
+static bool
+MultiXactOffsetWouldWrap(MultiXactOffset boundary,
+						 MultiXactOffset start,
+						 int distance)
+{
+	MultiXactOffset finish = start + distance;
+	Assert(distance >= 0);
+	if (start < boundary)
+		/*------------------------------------------------------------
+		 *
+		 *  <----S----B---->
+		 *
+		 *  [---)            = F wrapped past B (and UINT_MAX)
+		 *       [---)       = F OK
+		 *            [----] = F wrapped past B
+		 *------------------------------------------------------------
+		 */
+		return finish >= boundary || finish < start;
+	else
+		/*------------------------------------------------------------
+		 *
+		 *  <----B----S---->
+		 *
+		 *  [---)            = F OK
+		 *       [---)       = F wrapped past B (and UINT_MAX)
+		 *            [----] = F OK
+		 *------------------------------------------------------------
+		 */
+		return finish >= boundary && finish < start;
+}
+
+/*
  * GetNewMultiXactId
  *		Get the next MultiXactId.
  *
@@ -1063,13 +1098,13 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
 	 * multixact freeze settings would have to be reduced for that to have any
 	 * effect, and we can't do that from here.
 	 */
-	if (MultiXactOffsetPrecedes(MultiXactState->offsetStopLimit, nextOffset + nmembers))
+	if (MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit, nextOffset, nmembers))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("database is not accepting commands that generate new MultiXactIds to avoid \"members\" wraparound data loss in database with OID %u",
 						MultiXactState->oldestMultiXactDB),
 				 errhint("Execute a database-wide VACUUM in that database, with reduced vacuum_multixact_freeze_min_age and vacuum_multixact_freeze_table_age settings.")));
-	else if (MultiXactOffsetPrecedes(MultiXactState->offsetWarnLimit, nextOffset + nmembers))
+	else if (MultiXactOffsetWouldWrap(MultiXactState->offsetWarnLimit, nextOffset, nmembers))
 		ereport(WARNING,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg_plural("database with OID %u must be vacuumed before %d more multixact member is used",
@@ -2513,6 +2548,29 @@ DetermineSafeOldestOffset(MultiXactId oldestMXact)
 }
 
 /*
+ * Returns an instantaneous snapshot of the current number of members in the
+ * members SLRU area.
+ */
+MultiXactOffset
+ReadMultiXactMemberCount(void)
+{
+	MultiXactOffset nextOffset;
+	MultiXactOffset oldestOffset;
+	MultiXactId oldestMultiXactId;
+
+	LWLockAcquire(MultiXactGenLock, LW_SHARED);
+	nextOffset = MultiXactState->nextOffset;
+	oldestMultiXactId = MultiXactState->oldestMultiXactId;
+	LWLockRelease(MultiXactGenLock);
+	/*
+	 * TODO: In future, could oldestMultiXactMemberOffset be stored in shmem,
+	 * pg_controdata, alongside oldestMultiXactId?
+	 */
+	oldestOffset = read_offset_for_multi(oldestMultiXactId);
+	return nextOffset - oldestOffset;
+}
+
+/*
  * SlruScanDirectory callback.
  *		This callback deletes segments that are outside the range determined by
  *		the given page numbers.
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7ead161..3502a7f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -601,7 +601,7 @@ vacuum_set_xid_limits(Relation rel,
 		if (freezetable < 0)
 			freezetable = vacuum_multixact_freeze_table_age;
 		freezetable = Min(freezetable,
-						  autovacuum_multixact_freeze_max_age * 0.95);
+						  autovacuum_multixact_freeze_max_age_adjusted() * 0.95);
 		Assert(freezetable >= 0);
 
 		/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index be4cd1d..071d492 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -1052,6 +1052,34 @@ db_comparator(const void *a, const void *b)
 }
 
 /*
+ * Returns vacuum_multixact_freeze_max_age, adjusted down to prevent excessive use
+ * of addressable multixact member space if required.
+ *
+ * The goal is to avoid the situation where new multixacts can't be created
+ * because the offsets used to address pg_multixact/members would wrap around.
+ *
+ * If less than "safe fraction" of the total available member space is used,
+ * then we make no adjustment.  If more than the "excessive fraction" of the
+ * total available member space is used, then we use a freeze max age of zero
+ * to trigger aggressive vacuuming.  In between, we scale the given freeze max
+ * age down linearly, so that vacuuming becomes more aggressive as the member
+ * SLRU grows.
+ */
+int
+autovacuum_multixact_freeze_max_age_adjusted()
+{
+	// TODO: where to get these constants?
+	const double safe_fraction = 0.25;
+	const double excessive_fraction = 0.75;
+	double used_fraction = ReadMultiXactMemberCount() / (double) MaxMultiXactOffset;
+	// TODO: change this to consider the offset SLRU size as well, as described by Robert?
+	double scale_factor =
+		(excessive_fraction - used_fraction) / (excessive_fraction - safe_fraction);
+	scale_factor = Min(Max(scale_factor, 0.0), 1.0); /* Clamp value in range 0...1. */
+	return autovacuum_multixact_freeze_max_age * scale_factor;
+}
+
+/*
  * do_start_worker
  *
  * Bare-bones procedure for starting an autovacuum worker from the launcher.
@@ -1118,7 +1146,7 @@ do_start_worker(void)
 
 	/* Also determine the oldest datminmxid we will consider. */
 	recentMulti = ReadNextMultiXactId();
-	multiForceLimit = recentMulti - autovacuum_multixact_freeze_max_age;
+	multiForceLimit = recentMulti - autovacuum_multixact_freeze_max_age_adjusted();
 	if (multiForceLimit < FirstMultiXactId)
 		multiForceLimit -= FirstMultiXactId;
 
@@ -1931,7 +1959,9 @@ do_autovacuum(void)
 	{
 		default_freeze_min_age = vacuum_freeze_min_age;
 		default_freeze_table_age = vacuum_freeze_table_age;
-		default_multixact_freeze_min_age = vacuum_multixact_freeze_min_age;
+		default_multixact_freeze_min_age =
+			Min(vacuum_multixact_freeze_min_age,
+				autovacuum_multixact_freeze_max_age_adjusted());
 		default_multixact_freeze_table_age = vacuum_multixact_freeze_table_age;
 	}
 
@@ -2684,8 +2714,8 @@ relation_needs_vacanalyze(Oid relid,
 		: autovacuum_freeze_max_age;
 
 	multixact_freeze_max_age = (relopts && relopts->multixact_freeze_max_age >= 0)
-		? Min(relopts->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age)
-		: autovacuum_multixact_freeze_max_age;
+		? Min(relopts->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age_adjusted())
+		: autovacuum_multixact_freeze_max_age_adjusted();
 
 	av_enabled = (relopts ? relopts->enabled : true);
 
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 640b198..c4afbc2 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -126,6 +126,7 @@ extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
 						  MultiXactOffset minMultiOffset);
 extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
 extern void MultiXactSetSafeTruncate(MultiXactId safeTruncateMulti);
+extern MultiXactOffset ReadMultiXactMemberCount(void);
 
 extern void multixact_twophase_recover(TransactionId xid, uint16 info,
 						   void *recdata, uint32 len);
diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h
index 6eaaf4c..71931ef 100644
--- a/src/include/postmaster/autovacuum.h
+++ b/src/include/postmaster/autovacuum.h
@@ -53,6 +53,8 @@ extern void AutoVacWorkerFailed(void);
 /* autovacuum cost-delay balancer */
 extern void AutoVacuumUpdateDelay(void);
 
+extern int autovacuum_multixact_freeze_max_age_adjusted(void);
+
 #ifdef EXEC_BACKEND
 extern void AutoVacLauncherMain(int argc, char *argv[]) pg_attribute_noreturn();
 extern void AutoVacWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();
