diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 928f9fe..1d7d942 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2578,6 +2578,32 @@ find_multixact_start(MultiXactId multi) } /* + * Returns an instantaneous snapshot of the current number of active + * multixacts and the number of members in the members SLRU area. + */ +void +ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members) +{ + MultiXactOffset nextOffset; + MultiXactOffset oldestOffset; + MultiXactId oldestMultiXactId; + MultiXactId nextMultiXactId; + + LWLockAcquire(MultiXactGenLock, LW_SHARED); + nextOffset = MultiXactState->nextOffset; + oldestMultiXactId = MultiXactState->oldestMultiXactId; + nextMultiXactId = MultiXactState->nextMXact; + LWLockRelease(MultiXactGenLock); + /* + * XXX: Could we store oldestMultiXactMemberOffset in shmem and + * pg_controdata, alongside oldestMultiXactId? + */ + oldestOffset = find_multixact_start(oldestMultiXactId); + *members = nextOffset - oldestOffset; + *multixacts = nextMultiXactId - oldestMultiXactId; +} + +/* * SlruScanDirectory callback. * This callback deletes segments that are outside the range determined by * the given page numbers. diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index be4cd1d..0592817 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -297,10 +297,12 @@ static void do_autovacuum(void); static void FreeWorkerInfo(int code, Datum arg); static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map, - TupleDesc pg_class_desc); + TupleDesc pg_class_desc, + int max_multixact_age_to_avoid_member_wrap); static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, + int multixact_freeze_age_to_avoid_member_wrap, bool *dovacuum, bool *doanalyze, bool *wraparound); static void autovacuum_do_vac_analyze(autovac_table *tab, @@ -1052,6 +1054,68 @@ db_comparator(const void *a, const void *b) } /* + * Computes a multixact age that we can use to trigger earlier wraparound + * vacuums than usual, if special action is required to avoid impending + * exhaustion of the addressing space of multixact members (caused by large + * multixacts). + * + * If less than a "safe member count" is active, then we return a number at + * least as large as autovacuum_multixact_freeze_max_age, so that no special + * action is taken. This should apply to most typical users who don't make + * use of large multixacts. + * + * If more than the "dangerous member count" is active, then we return a max + * freeze age of zero to trigger aggressive wraparound vacuuming. + * + * In between, we return the current number of active multixids scaled down + * linearly for higher usage fractions, so that vacuuming becomes more + * aggressive as the member SLRU grows, in the hope that different tables will + * be vacuumed at different times due to their varying relminmxid values. + * + * 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 50% and 75%. + */ +#define SAFE_MEMBER_COUNT (MaxMultiXactOffset / 2) +#define DANGEROUS_MEMBER_COUNT (MaxMultiXactOffset - (SAFE_MEMBER_COUNT / 2)) +int +compute_max_multixact_age_to_avoid_member_wrap(void) +{ + MultiXactOffset members; + uint32 multixacts; + double fraction; + + ReadMultiXactCounts(&multixacts, &members); + + if (members <= SAFE_MEMBER_COUNT) + { + /* + * There is no danger of member wrap, so return a number that is not + * lower than autovacuum_multixact_freeze_max_age. + */ + return autovacuum_multixact_freeze_max_age; /* XXX: MAXINT? */ + } + + if (members >= DANGEROUS_MEMBER_COUNT) + { + /* We need a wraparound vacuum for all tables now. */ + return 0; + } + + /* + * Choose a cutoff age which is a fraction of the approximate current + * number of active multixacts. If we are using an amount of member + * address space near SAFE_MEMBER_COUNT, we use a number close to the + * number of active multixacts, so that only tables with the oldest + * relminmxid values become candidates for wraparound vacuums. As we get + * closer to DANGEROUS_MEMBER_COUNT, we use a number closer to zero, so + * that more tables become candidates for wraparound vacuums. + */ + fraction = (double) (members - SAFE_MEMBER_COUNT) / + (double) (DANGEROUS_MEMBER_COUNT - SAFE_MEMBER_COUNT); + return (int) (multixacts * (1.0 - fraction)); +} + +/* * do_start_worker * * Bare-bones procedure for starting an autovacuum worker from the launcher. @@ -1118,7 +1182,8 @@ do_start_worker(void) /* Also determine the oldest datminmxid we will consider. */ recentMulti = ReadNextMultiXactId(); - multiForceLimit = recentMulti - autovacuum_multixact_freeze_max_age; + multiForceLimit = recentMulti - Min(autovacuum_multixact_freeze_max_age, + compute_max_multixact_age_to_avoid_member_wrap()); if (multiForceLimit < FirstMultiXactId) multiForceLimit -= FirstMultiXactId; @@ -1881,6 +1946,7 @@ do_autovacuum(void) BufferAccessStrategy bstrategy; ScanKeyData key; TupleDesc pg_class_desc; + int max_multixact_age_to_avoid_member_wrap; /* * StartTransactionCommand and CommitTransactionCommand will automatically @@ -1920,6 +1986,8 @@ do_autovacuum(void) elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); dbForm = (Form_pg_database) GETSTRUCT(tuple); + max_multixact_age_to_avoid_member_wrap = compute_max_multixact_age_to_avoid_member_wrap(); + if (dbForm->datistemplate || !dbForm->datallowconn) { default_freeze_min_age = 0; @@ -2001,6 +2069,7 @@ do_autovacuum(void) /* Check if it needs vacuum or analyze */ relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + max_multixact_age_to_avoid_member_wrap, &dovacuum, &doanalyze, &wraparound); /* @@ -2129,6 +2198,7 @@ do_autovacuum(void) shared, dbentry); relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + max_multixact_age_to_avoid_member_wrap, &dovacuum, &doanalyze, &wraparound); /* ignore analyze for toast tables */ @@ -2235,7 +2305,8 @@ do_autovacuum(void) * the race condition is not closed but it is very small. */ MemoryContextSwitchTo(AutovacMemCxt); - tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc); + tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc, + max_multixact_age_to_avoid_member_wrap); if (tab == NULL) { /* someone else vacuumed the table, or it went away */ @@ -2442,7 +2513,8 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared, */ static autovac_table * table_recheck_autovac(Oid relid, HTAB *table_toast_map, - TupleDesc pg_class_desc) + TupleDesc pg_class_desc, + int max_multixact_age_to_avoid_member_wrap) { Form_pg_class classForm; HeapTuple classTup; @@ -2488,6 +2560,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, shared, dbentry); relation_needs_vacanalyze(relid, avopts, classForm, tabentry, + max_multixact_age_to_avoid_member_wrap, &dovacuum, &doanalyze, &wraparound); /* ignore ANALYZE for toast tables */ @@ -2550,6 +2623,16 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ? avopts->multixact_freeze_table_age : default_multixact_freeze_table_age; + /* + * Override the multixact freeze settings if we are running out of + * member address space. + */ + if (max_multixact_age_to_avoid_member_wrap < multixact_freeze_table_age) + { + multixact_freeze_table_age = max_multixact_age_to_avoid_member_wrap; + multixact_freeze_min_age = 0; + } + tab = palloc(sizeof(autovac_table)); tab->at_relid = relid; tab->at_vacoptions = VACOPT_SKIPTOAST | @@ -2624,6 +2707,7 @@ relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, + int max_multixact_age_to_avoid_member_wrap, /* output params below */ bool *dovacuum, bool *doanalyze, @@ -2687,6 +2771,10 @@ relation_needs_vacanalyze(Oid relid, ? Min(relopts->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age) : autovacuum_multixact_freeze_max_age; + /* Special settings if we are running out of member address space. */ + if (max_multixact_age_to_avoid_member_wrap < multixact_freeze_max_age) + multixact_freeze_max_age = max_multixact_age_to_avoid_member_wrap; + av_enabled = (relopts ? relopts->enabled : true); /* Force vacuum if table is at risk of wraparound */ diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h index 640b198..0500435 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 void ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members); 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..7fc7bd3 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 compute_max_multixact_age_to_avoid_member_wrap(void); + #ifdef EXEC_BACKEND extern void AutoVacLauncherMain(int argc, char *argv[]) pg_attribute_noreturn(); extern void AutoVacWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();