diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index c8faa17..634224b 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2574,6 +2574,29 @@ find_multixact_start(MultiXactId multi) } /* + * 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 = find_multixact_start(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..9848ce7 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -1052,6 +1052,39 @@ 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 "minimum safe member count" of the total available member + * space is used, then we make no adjustment (this should apply to most + * typical users). If more than the "max safe member count" 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. + * + * Based on the assumption that there is no reasonable way for an end user to + * configure the thresholds for this, we just define constants at 25% and 75%. + */ +#define MIN_SAFE_MEMBER_COUNT (MaxMultiXactOffset / 4) +#define MAX_SAFE_MEMBER_COUNT (MaxMultiXactOffset - MIN_SAFE_MEMBER_COUNT) +int +autovacuum_multixact_freeze_max_age_adjusted() +{ + MultiXactOffset mxact_member_count = ReadMultiXactMemberCount(); + if (mxact_member_count <= MIN_SAFE_MEMBER_COUNT) + return autovacuum_multixact_freeze_max_age; + else if (mxact_member_count >= MAX_SAFE_MEMBER_COUNT) + return 0; + else return (int) (autovacuum_multixact_freeze_max_age + * (((double) (MAX_SAFE_MEMBER_COUNT - mxact_member_count)) + / ((double) (MAX_SAFE_MEMBER_COUNT - MIN_SAFE_MEMBER_COUNT)))); +} + +/* * do_start_worker * * Bare-bones procedure for starting an autovacuum worker from the launcher. @@ -1118,7 +1151,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 +1964,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 +2719,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();