Skip to content

Commit 596fb5a

Browse files
committed
Teach autovacuum about multixact member wraparound.
The logic introduced in commit b69bf30 and repaired in commits 669c7d2 and 7be47c5 helps to ensure that we don't overwrite old multixact member information while it is still needed, but a user who creates many large multixacts can still exhaust the member space (and thus start getting errors) while autovacuum stands idly by. To fix this, progressively ramp down the effective value (but not the actual contents) of autovacuum_multixact_freeze_max_age as member space utilization increases. This makes autovacuum more aggressive and also reduces the threshold for a manual VACUUM to perform a full-table scan. This patch leaves unsolved the problem of ensuring that emergency autovacuums are triggered even when autovacuum=off. We'll need to fix that via a separate patch. Thomas Munro and Robert Haas
1 parent 83fbd9b commit 596fb5a

File tree

5 files changed

+130
-11
lines changed

5 files changed

+130
-11
lines changed

doc/src/sgml/maintenance.sgml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,9 @@ HINT: Stop the postmaster and use a standalone backend to VACUUM in "mydb".
630630
Like transaction IDs, multixact IDs are implemented as a
631631
32-bit counter and corresponding storage, all of which requires
632632
careful aging management, storage cleanup, and wraparound handling.
633+
There is a separate storage area which holds the list of members in
634+
each multixact, which also uses a 32-bit counter and which must also
635+
be managed.
633636
</para>
634637

635638
<para>
@@ -653,7 +656,10 @@ HINT: Stop the postmaster and use a standalone backend to VACUUM in "mydb".
653656
As a safety device, a whole-table vacuum scan will occur for any table
654657
whose multixact-age is greater than
655658
<xref linkend="guc-autovacuum-multixact-freeze-max-age">.
656-
This will occur even if autovacuum is nominally disabled.
659+
This will occur even if autovacuum is nominally disabled. Whole-table
660+
vacuum scans will also occur progressively for all tables, starting with
661+
those that have the oldest multixact-age, if the amount of used member
662+
storage space exceeds the amount 25% of the addressible storage space.
657663
</para>
658664
</sect3>
659665
</sect2>

src/backend/access/transam/multixact.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@
166166
(MXOffsetToFlagsOffset(xid) + MULTIXACT_FLAGBYTES_PER_GROUP + \
167167
((xid) % MULTIXACT_MEMBERS_PER_MEMBERGROUP) * sizeof(TransactionId))
168168

169+
/* Multixact members wraparound thresholds. */
170+
#define MULTIXACT_MEMBER_SAFE_THRESHOLD (MaxMultiXactOffset / 4)
171+
#define MULTIXACT_MEMBER_DANGER_THRESHOLD \
172+
(MaxMultiXactOffset - MaxMultiXactOffset / 4)
173+
169174

170175
/*
171176
* Links to shared-memory data structures for MultiXact control
@@ -2597,6 +2602,89 @@ find_multixact_start(MultiXactId multi)
25972602
return offset;
25982603
}
25992604

2605+
/*
2606+
* Determine how many multixacts, and how many multixact members, currently
2607+
* exist.
2608+
*/
2609+
static void
2610+
ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members)
2611+
{
2612+
MultiXactOffset nextOffset;
2613+
MultiXactOffset oldestOffset;
2614+
MultiXactId oldestMultiXactId;
2615+
MultiXactId nextMultiXactId;
2616+
2617+
LWLockAcquire(MultiXactGenLock, LW_SHARED);
2618+
nextOffset = MultiXactState->nextOffset;
2619+
oldestMultiXactId = MultiXactState->oldestMultiXactId;
2620+
nextMultiXactId = MultiXactState->nextMXact;
2621+
LWLockRelease(MultiXactGenLock);
2622+
2623+
oldestOffset = find_multixact_start(oldestMultiXactId);
2624+
*members = nextOffset - oldestOffset;
2625+
*multixacts = nextMultiXactId - oldestMultiXactId;
2626+
}
2627+
2628+
/*
2629+
* Multixact members can be removed once the multixacts that refer to them
2630+
* are older than every datminxmid. autovacuum_multixact_freeze_max_age and
2631+
* vacuum_multixact_freeze_table_age work together to make sure we never have
2632+
* too many multixacts; we hope that, at least under normal circumstances,
2633+
* this will also be sufficient to keep us from using too many offsets.
2634+
* However, if the average multixact has many members, we might exhaust the
2635+
* members space while still using few enough members that these limits fail
2636+
* to trigger full table scans for relminmxid advancement. At that point,
2637+
* we'd have no choice but to start failing multixact-creating operations
2638+
* with an error.
2639+
*
2640+
* To prevent that, if more than a threshold portion of the members space is
2641+
* used, we effectively reduce autovacuum_multixact_freeze_max_age and
2642+
* to a value just less than the number of multixacts in use. We hope that
2643+
* this will quickly trigger autovacuuming on the table or tables with the
2644+
* oldest relminmxid, thus allowing datminmxid values to advance and removing
2645+
* some members.
2646+
*
2647+
* As the fraction of the member space currently in use grows, we become
2648+
* more aggressive in clamping this value. That not only causes autovacuum
2649+
* to ramp up, but also makes any manual vacuums the user issues more
2650+
* aggressive. This happens because vacuum_set_xid_limits() clamps the
2651+
* freeze table and and the minimum freeze age based on the effective
2652+
* autovacuum_multixact_freeze_max_age this function returns. In the worst
2653+
* case, we'll claim the freeze_max_age to zero, and every vacuum of any
2654+
* table will try to freeze every multixact.
2655+
*
2656+
* It's possible that these thresholds should be user-tunable, but for now
2657+
* we keep it simple.
2658+
*/
2659+
int
2660+
MultiXactMemberFreezeThreshold(void)
2661+
{
2662+
MultiXactOffset members;
2663+
uint32 multixacts;
2664+
uint32 victim_multixacts;
2665+
double fraction;
2666+
2667+
ReadMultiXactCounts(&multixacts, &members);
2668+
2669+
/* If member space utilization is low, no special action is required. */
2670+
if (members <= MULTIXACT_MEMBER_SAFE_THRESHOLD)
2671+
return autovacuum_multixact_freeze_max_age;
2672+
2673+
/*
2674+
* Compute a target for relminmxid advancement. The number of multixacts
2675+
* we try to eliminate from the system is based on how far we are past
2676+
* MULTIXACT_MEMBER_SAFE_THRESHOLD.
2677+
*/
2678+
fraction = (double) (members - MULTIXACT_MEMBER_SAFE_THRESHOLD) /
2679+
(MULTIXACT_MEMBER_DANGER_THRESHOLD - MULTIXACT_MEMBER_SAFE_THRESHOLD);
2680+
victim_multixacts = multixacts * fraction;
2681+
2682+
/* fraction could be > 1.0, but lowest possible freeze age is zero */
2683+
if (victim_multixacts > multixacts)
2684+
return 0;
2685+
return multixacts - victim_multixacts;
2686+
}
2687+
26002688
/*
26012689
* SlruScanDirectory callback.
26022690
* This callback deletes segments that are outside the range determined by

src/backend/commands/vacuum.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ vacuum_set_xid_limits(int freeze_min_age,
426426
{
427427
int freezemin;
428428
int mxid_freezemin;
429+
int effective_multixact_freeze_max_age;
429430
TransactionId limit;
430431
TransactionId safeLimit;
431432
MultiXactId mxactLimit;
@@ -482,17 +483,24 @@ vacuum_set_xid_limits(int freeze_min_age,
482483

483484
*freezeLimit = limit;
484485

486+
/*
487+
* Compute the multixact age for which freezing is urgent. This is
488+
* normally autovacuum_multixact_freeze_max_age, but may be less if we
489+
* are short of multixact member space.
490+
*/
491+
effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
492+
485493
/*
486494
* Determine the minimum multixact freeze age to use: as specified by
487495
* caller, or vacuum_multixact_freeze_min_age, but in any case not more
488-
* than half autovacuum_multixact_freeze_max_age, so that autovacuums to
496+
* than half effective_multixact_freeze_max_age, so that autovacuums to
489497
* prevent MultiXact wraparound won't occur too frequently.
490498
*/
491499
mxid_freezemin = multixact_freeze_min_age;
492500
if (mxid_freezemin < 0)
493501
mxid_freezemin = vacuum_multixact_freeze_min_age;
494502
mxid_freezemin = Min(mxid_freezemin,
495-
autovacuum_multixact_freeze_max_age / 2);
503+
effective_multixact_freeze_max_age / 2);
496504
Assert(mxid_freezemin >= 0);
497505

498506
/* compute the cutoff multi, being careful to generate a valid value */
@@ -501,7 +509,7 @@ vacuum_set_xid_limits(int freeze_min_age,
501509
mxactLimit = FirstMultiXactId;
502510

503511
safeMxactLimit =
504-
ReadNextMultiXactId() - autovacuum_multixact_freeze_max_age;
512+
ReadNextMultiXactId() - effective_multixact_freeze_max_age;
505513
if (safeMxactLimit < FirstMultiXactId)
506514
safeMxactLimit = FirstMultiXactId;
507515

@@ -556,7 +564,7 @@ vacuum_set_xid_limits(int freeze_min_age,
556564
if (freezetable < 0)
557565
freezetable = vacuum_multixact_freeze_table_age;
558566
freezetable = Min(freezetable,
559-
autovacuum_multixact_freeze_max_age * 0.95);
567+
effective_multixact_freeze_max_age * 0.95);
560568
Assert(freezetable >= 0);
561569

562570
/*

src/backend/postmaster/autovacuum.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,13 @@ static void do_autovacuum(void);
302302
static void FreeWorkerInfo(int code, Datum arg);
303303

304304
static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map,
305-
TupleDesc pg_class_desc);
305+
TupleDesc pg_class_desc,
306+
int effective_multixact_freeze_max_age);
306307
static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
307308
AutoVacOpts2 *relopts2,
308309
Form_pg_class classForm,
309310
PgStat_StatTabEntry *tabentry,
311+
int effective_multixact_freeze_max_age,
310312
bool *dovacuum, bool *doanalyze, bool *wraparound);
311313

312314
static void autovacuum_do_vac_analyze(autovac_table *tab,
@@ -1148,7 +1150,7 @@ do_start_worker(void)
11481150

11491151
/* Also determine the oldest datminmxid we will consider. */
11501152
recentMulti = ReadNextMultiXactId();
1151-
multiForceLimit = recentMulti - autovacuum_multixact_freeze_max_age;
1153+
multiForceLimit = recentMulti - MultiXactMemberFreezeThreshold();
11521154
if (multiForceLimit < FirstMultiXactId)
11531155
multiForceLimit -= FirstMultiXactId;
11541156

@@ -1937,6 +1939,7 @@ do_autovacuum(void)
19371939
BufferAccessStrategy bstrategy;
19381940
ScanKeyData key;
19391941
TupleDesc pg_class_desc;
1942+
int effective_multixact_freeze_max_age;
19401943

19411944
/*
19421945
* StartTransactionCommand and CommitTransactionCommand will automatically
@@ -1966,6 +1969,13 @@ do_autovacuum(void)
19661969
*/
19671970
pgstat_vacuum_stat();
19681971

1972+
/*
1973+
* Compute the multixact age for which freezing is urgent. This is
1974+
* normally autovacuum_multixact_freeze_max_age, but may be less if we
1975+
* are short of multixact member space.
1976+
*/
1977+
effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold();
1978+
19691979
/*
19701980
* Find the pg_database entry and select the default freeze ages. We use
19711981
* zero in template and nonconnectable databases, else the system-wide
@@ -2059,6 +2069,7 @@ do_autovacuum(void)
20592069

20602070
/* Check if it needs vacuum or analyze */
20612071
relation_needs_vacanalyze(relid, relopts, relopts2, classForm, tabentry,
2072+
effective_multixact_freeze_max_age,
20622073
&dovacuum, &doanalyze, &wraparound);
20632074

20642075
/*
@@ -2188,6 +2199,7 @@ do_autovacuum(void)
21882199
shared, dbentry);
21892200

21902201
relation_needs_vacanalyze(relid, relopts, relopts2, classForm, tabentry,
2202+
effective_multixact_freeze_max_age,
21912203
&dovacuum, &doanalyze, &wraparound);
21922204

21932205
/* ignore analyze for toast tables */
@@ -2278,7 +2290,8 @@ do_autovacuum(void)
22782290
* the race condition is not closed but it is very small.
22792291
*/
22802292
MemoryContextSwitchTo(AutovacMemCxt);
2281-
tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc);
2293+
tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc,
2294+
effective_multixact_freeze_max_age);
22822295
if (tab == NULL)
22832296
{
22842297
/* someone else vacuumed the table, or it went away */
@@ -2495,7 +2508,8 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared,
24952508
*/
24962509
static autovac_table *
24972510
table_recheck_autovac(Oid relid, HTAB *table_toast_map,
2498-
TupleDesc pg_class_desc)
2511+
TupleDesc pg_class_desc,
2512+
int effective_multixact_freeze_max_age)
24992513
{
25002514
Form_pg_class classForm;
25012515
HeapTuple classTup;
@@ -2542,6 +2556,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
25422556
shared, dbentry);
25432557

25442558
relation_needs_vacanalyze(relid, avopts, avopts2, classForm, tabentry,
2559+
effective_multixact_freeze_max_age,
25452560
&dovacuum, &doanalyze, &wraparound);
25462561

25472562
/* ignore ANALYZE for toast tables */
@@ -2670,6 +2685,7 @@ relation_needs_vacanalyze(Oid relid,
26702685
AutoVacOpts2 *relopts2,
26712686
Form_pg_class classForm,
26722687
PgStat_StatTabEntry *tabentry,
2688+
int effective_multixact_freeze_max_age,
26732689
/* output params below */
26742690
bool *dovacuum,
26752691
bool *doanalyze,
@@ -2730,8 +2746,8 @@ relation_needs_vacanalyze(Oid relid,
27302746
: autovacuum_freeze_max_age;
27312747

27322748
multixact_freeze_max_age = (relopts2 && relopts2->multixact_freeze_max_age >= 0)
2733-
? Min(relopts2->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age)
2734-
: autovacuum_multixact_freeze_max_age;
2749+
? Min(relopts2->multixact_freeze_max_age, effective_multixact_freeze_max_age)
2750+
: effective_multixact_freeze_max_age;
27352751

27362752
av_enabled = (relopts ? relopts->enabled : true);
27372753

src/include/access/multixact.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
126126
MultiXactOffset minMultiOffset);
127127
extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
128128
extern void MultiXactSetSafeTruncate(MultiXactId safeTruncateMulti);
129+
extern int MultiXactMemberFreezeThreshold(void);
129130

130131
extern void multixact_twophase_recover(TransactionId xid, uint16 info,
131132
void *recdata, uint32 len);

0 commit comments

Comments
 (0)