diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/access/common/reloptions.c | 29 | ||||
-rw-r--r-- | src/backend/access/transam/multixact.c | 21 | ||||
-rw-r--r-- | src/backend/access/transam/varsup.c | 3 | ||||
-rw-r--r-- | src/backend/commands/cluster.c | 29 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 67 | ||||
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 6 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 26 | ||||
-rw-r--r-- | src/backend/postmaster/autovacuum.c | 71 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 30 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 11 | ||||
-rw-r--r-- | src/include/commands/cluster.h | 3 | ||||
-rw-r--r-- | src/include/commands/vacuum.h | 4 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 5 | ||||
-rw-r--r-- | src/include/postmaster/autovacuum.h | 1 | ||||
-rw-r--r-- | src/include/utils/rel.h | 20 |
17 files changed, 284 insertions, 46 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index c439702a011..17bbcb5dbab 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -164,6 +164,14 @@ static relopt_int intRelOpts[] = }, { { + "autovacuum_multixact_freeze_min_age", + "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST + }, + -1, 0, 1000000000 + }, + { + { "autovacuum_freeze_max_age", "Age at which to autovacuum a table to prevent transaction ID wraparound", RELOPT_KIND_HEAP | RELOPT_KIND_TOAST @@ -172,11 +180,26 @@ static relopt_int intRelOpts[] = }, { { + "autovacuum_multixact_freeze_max_age", + "Multixact age at which to autovacuum a table to prevent multixact wraparound", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST + }, + -1, 100000000, 2000000000 + }, + { + { "autovacuum_freeze_table_age", "Age at which VACUUM should perform a full table sweep to replace old Xid values with FrozenXID", RELOPT_KIND_HEAP | RELOPT_KIND_TOAST }, -1, 0, 2000000000 }, + { + { + "autovacuum_multixact_freeze_table_age", + "Age of multixact at which VACUUM should perform a full table sweep to replace old multixact values with newer ones", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST + }, -1, 0, 2000000000 + }, /* list terminator */ {{NULL}} }; @@ -1146,6 +1169,12 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, freeze_max_age)}, {"autovacuum_freeze_table_age", RELOPT_TYPE_INT, offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, freeze_table_age)}, + {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT, + offsetof(StdRdOptions, autovacuum2) +offsetof(AutoVacOpts2, multixact_freeze_min_age)}, + {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, + offsetof(StdRdOptions, autovacuum2) +offsetof(AutoVacOpts2, multixact_freeze_max_age)}, + {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, + offsetof(StdRdOptions, autovacuum2) +offsetof(AutoVacOpts2, multixact_freeze_table_age)}, {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL, offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_factor)}, {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index e2b31ee441f..541612e503b 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2055,11 +2055,13 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) Assert(MultiXactIdIsValid(oldest_datminmxid)); /* - * The place where we actually get into deep trouble is halfway around - * from the oldest potentially-existing XID/multi. (This calculation is - * probably off by one or two counts for Xids, because the special XIDs - * reduce the size of the loop a little bit. But we throw in plenty of - * slop below, so it doesn't matter.) + * Since multixacts wrap differently from transaction IDs, this logic is + * not entirely correct: in some scenarios we could go for longer than 2 + * billion multixacts without seeing any data loss, and in some others we + * could get in trouble before that if the new pg_multixact/members data + * stomps on the previous cycle's data. For lack of a better mechanism we + * use the same logic as for transaction IDs, that is, start taking action + * halfway around the oldest potentially-existing multixact. */ multiWrapLimit = oldest_datminmxid + (MaxMultiXactId >> 1); if (multiWrapLimit < FirstMultiXactId) @@ -2093,12 +2095,13 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid) /* * We'll start trying to force autovacuums when oldest_datminmxid gets to - * be more than autovacuum_freeze_max_age mxids old. + * be more than autovacuum_multixact_freeze_max_age mxids old. * - * It's a bit ugly to just reuse limits for xids that way, but it doesn't - * seem worth adding separate GUCs for that purpose. + * Note: autovacuum_multixact_freeze_max_age is a PGC_POSTMASTER parameter + * so that we don't have to worry about dealing with on-the-fly changes in + * its value. See SetTransactionIdLimit. */ - multiVacLimit = oldest_datminmxid + autovacuum_freeze_max_age; + multiVacLimit = oldest_datminmxid + autovacuum_multixact_freeze_max_age; if (multiVacLimit < FirstMultiXactId) multiVacLimit += FirstMultiXactId; diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 0579c84bea2..7252ee25c88 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -313,7 +313,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid) * value. It doesn't look practical to update shared state from a GUC * assign hook (too many processes would try to execute the hook, * resulting in race conditions as well as crashes of those not connected - * to shared memory). Perhaps this can be improved someday. + * to shared memory). Perhaps this can be improved someday. See also + * SetMultiXactIdLimit. */ xidVacLimit = oldest_datfrozenxid + autovacuum_freeze_max_age; if (xidVacLimit < FirstNormalTransactionId) diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 1cdb22048a4..c903f0eba7c 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -64,9 +64,13 @@ typedef struct static void rebuild_relation(Relation OldHeap, Oid indexOid, - int freeze_min_age, int freeze_table_age, bool verbose); + int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age, + bool verbose); static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, - int freeze_min_age, int freeze_table_age, bool verbose, + int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age, + bool verbose, bool *pSwapToastByContent, TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); static List *get_tables_to_cluster(MemoryContext cluster_context); @@ -179,7 +183,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel) * Do the job. We use a -1 freeze_min_age to avoid having CLUSTER * freeze tuples earlier than a plain VACUUM would. */ - cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1); + cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1, -1, -1); } else { @@ -230,7 +234,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel) PushActiveSnapshot(GetTransactionSnapshot()); /* Do the job. As above, use a -1 freeze_min_age. */ cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, - -1, -1); + -1, -1, -1, -1); PopActiveSnapshot(); CommitTransactionCommand(); } @@ -262,7 +266,8 @@ cluster(ClusterStmt *stmt, bool isTopLevel) */ void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, - int freeze_min_age, int freeze_table_age) + int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age) { Relation OldHeap; @@ -407,6 +412,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, /* rebuild_relation does all the dirty work */ rebuild_relation(OldHeap, indexOid, freeze_min_age, freeze_table_age, + multixact_freeze_min_age, multixact_freeze_table_age, verbose); /* NB: rebuild_relation does heap_close() on OldHeap */ @@ -566,7 +572,9 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) */ static void rebuild_relation(Relation OldHeap, Oid indexOid, - int freeze_min_age, int freeze_table_age, bool verbose) + int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age, + bool verbose) { Oid tableOid = RelationGetRelid(OldHeap); Oid tableSpace = OldHeap->rd_rel->reltablespace; @@ -591,7 +599,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid, /* Copy the heap data into the new table in the desired order */ copy_heap_data(OIDNewHeap, tableOid, indexOid, - freeze_min_age, freeze_table_age, verbose, + freeze_min_age, freeze_table_age, + multixact_freeze_min_age, multixact_freeze_table_age, + verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); /* @@ -733,7 +743,9 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace) */ static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, - int freeze_min_age, int freeze_table_age, bool verbose, + int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age, + bool verbose, bool *pSwapToastByContent, TransactionId *pFreezeXid, MultiXactId *pCutoffMulti) { @@ -849,6 +861,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, * compute xids used to freeze and weed out dead tuples. */ vacuum_set_xid_limits(freeze_min_age, freeze_table_age, + multixact_freeze_min_age, multixact_freeze_table_age, OldHeap->rd_rel->relisshared, &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff, NULL); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d50333f24c8..d5d915e280f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -55,6 +55,8 @@ */ int vacuum_freeze_min_age; int vacuum_freeze_table_age; +int vacuum_multixact_freeze_min_age; +int vacuum_multixact_freeze_table_age; /* A few variables that don't seem worth passing around as parameters */ @@ -398,6 +400,8 @@ get_rel_oids(Oid relid, const RangeVar *vacrel) void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, + int multixact_freeze_table_age, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit, @@ -406,9 +410,11 @@ vacuum_set_xid_limits(int freeze_min_age, MultiXactId *mxactFullScanLimit) { int freezemin; + int mxid_freezemin; TransactionId limit; TransactionId safeLimit; - MultiXactId mxactLimit; + MultiXactId mxactLimit; + MultiXactId safeMxactLimit; /* * We can always ignore processes running lazy vacuum. This is because we @@ -462,13 +468,36 @@ vacuum_set_xid_limits(int freeze_min_age, *freezeLimit = limit; /* - * simplistic MultiXactId removal limit: use the same policy as for - * freezing Xids (except we use the oldest known mxact instead of the - * current next value). + * Determine the minimum multixact freeze age to use: as specified by + * caller, or vacuum_multixact_freeze_min_age, but in any case not more + * than half autovacuum_multixact_freeze_max_age, so that autovacuums to + * prevent MultiXact wraparound won't occur too frequently. */ - mxactLimit = GetOldestMultiXactId() - freezemin; + mxid_freezemin = multixact_freeze_min_age; + if (mxid_freezemin < 0) + mxid_freezemin = vacuum_multixact_freeze_min_age; + mxid_freezemin = Min(mxid_freezemin, + autovacuum_multixact_freeze_max_age / 2); + Assert(mxid_freezemin >= 0); + + /* compute the cutoff multi, being careful to generate a valid value */ + mxactLimit = GetOldestMultiXactId() - mxid_freezemin; if (mxactLimit < FirstMultiXactId) mxactLimit = FirstMultiXactId; + + safeMxactLimit = + ReadNextMultiXactId() - autovacuum_multixact_freeze_max_age; + if (safeMxactLimit < FirstMultiXactId) + safeMxactLimit = FirstMultiXactId; + + if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit)) + { + ereport(WARNING, + (errmsg("oldest multixact is far in the past"), + errhint("Close open transactions with multixacts soon to avoid wraparound problems."))); + mxactLimit = safeMxactLimit; + } + *multiXactCutoff = mxactLimit; if (xidFullScanLimit != NULL) @@ -501,9 +530,23 @@ vacuum_set_xid_limits(int freeze_min_age, *xidFullScanLimit = limit; /* - * Compute MultiXactId limit to cause a full-table vacuum, being - * careful not to generate an invalid multi. We just copy the logic - * (and limits) from plain XIDs here. + * Similar to the above, determine the table freeze age to use for + * multixacts: as specified by the caller, or + * vacuum_multixact_freeze_table_age, but in any case not more than + * autovacuum_multixact_freeze_table_age * 0.95, so that if you have + * e.g. nightly VACUUM schedule, the nightly VACUUM gets a chance to + * freeze multixacts before anti-wraparound autovacuum is launched. + */ + freezetable = multixact_freeze_table_age; + if (freezetable < 0) + freezetable = vacuum_multixact_freeze_table_age; + freezetable = Min(freezetable, + autovacuum_multixact_freeze_max_age * 0.95); + Assert(freezetable >= 0); + + /* + * Compute MultiXact limit causing a full-table vacuum, being careful + * to generate a valid MultiXact value. */ mxactLimit = ReadNextMultiXactId() - freezetable; if (mxactLimit < FirstMultiXactId) @@ -511,6 +554,10 @@ vacuum_set_xid_limits(int freeze_min_age, *mxactFullScanLimit = mxactLimit; } + else + { + Assert(mxactFullScanLimit == NULL); + } } /* @@ -1150,7 +1197,9 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound) /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */ cluster_rel(relid, InvalidOid, false, (vacstmt->options & VACOPT_VERBOSE) != 0, - vacstmt->freeze_min_age, vacstmt->freeze_table_age); + vacstmt->freeze_min_age, vacstmt->freeze_table_age, + vacstmt->multixact_freeze_min_age, + vacstmt->multixact_freeze_table_age); } else lazy_vacuum_rel(onerel, vacstmt, vac_strategy); diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 402a9e74751..ce4172cb100 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -203,6 +203,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, vac_strategy = bstrategy; vacuum_set_xid_limits(vacstmt->freeze_min_age, vacstmt->freeze_table_age, + vacstmt->multixact_freeze_min_age, + vacstmt->multixact_freeze_table_age, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit, &xidFullScanLimit, &MultiXactCutoff, &mxactFullScanLimit); @@ -210,8 +212,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt, /* * We request a full scan if either the table's frozen Xid is now older * than or equal to the requested Xid full-table scan limit; or if the - * table's minimum MultiXactId is older than or equal to the requested mxid - * full-table scan limit. + * table's minimum MultiXactId is older than or equal to the requested + * mxid full-table scan limit. */ scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid, xidFullScanLimit); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 6b20e317323..79ff80bb4df 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3206,6 +3206,8 @@ _copyVacuumStmt(const VacuumStmt *from) COPY_SCALAR_FIELD(options); COPY_SCALAR_FIELD(freeze_min_age); COPY_SCALAR_FIELD(freeze_table_age); + COPY_SCALAR_FIELD(multixact_freeze_min_age); + COPY_SCALAR_FIELD(multixact_freeze_table_age); COPY_NODE_FIELD(relation); COPY_NODE_FIELD(va_cols); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index b49e1e731de..c481fc4eedc 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1496,6 +1496,8 @@ _equalVacuumStmt(const VacuumStmt *a, const VacuumStmt *b) COMPARE_SCALAR_FIELD(options); COMPARE_SCALAR_FIELD(freeze_min_age); COMPARE_SCALAR_FIELD(freeze_table_age); + COMPARE_SCALAR_FIELD(multixact_freeze_min_age); + COMPARE_SCALAR_FIELD(multixact_freeze_table_age); COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(va_cols); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3feced6b687..ed22e463d4c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -8474,6 +8474,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; + n->multixact_freeze_min_age = $3 ? 0 : -1; + n->multixact_freeze_table_age = $3 ? 0 : -1; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; @@ -8488,6 +8490,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; + n->multixact_freeze_min_age = $3 ? 0 : -1; + n->multixact_freeze_table_age = $3 ? 0 : -1; n->relation = $5; n->va_cols = NIL; $$ = (Node *)n; @@ -8502,6 +8506,8 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->options |= VACOPT_VERBOSE; n->freeze_min_age = $3 ? 0 : -1; n->freeze_table_age = $3 ? 0 : -1; + n->multixact_freeze_min_age = $3 ? 0 : -1; + n->multixact_freeze_table_age = $3 ? 0 : -1; $$ = (Node *)n; } | VACUUM '(' vacuum_option_list ')' @@ -8509,9 +8515,17 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_VACUUM | $3; if (n->options & VACOPT_FREEZE) + { n->freeze_min_age = n->freeze_table_age = 0; + n->multixact_freeze_min_age = 0; + n->multixact_freeze_table_age = 0; + } else + { n->freeze_min_age = n->freeze_table_age = -1; + n->multixact_freeze_min_age = -1; + n->multixact_freeze_table_age = -1; + } n->relation = NULL; n->va_cols = NIL; $$ = (Node *) n; @@ -8521,9 +8535,17 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose VacuumStmt *n = makeNode(VacuumStmt); n->options = VACOPT_VACUUM | $3; if (n->options & VACOPT_FREEZE) + { n->freeze_min_age = n->freeze_table_age = 0; + n->multixact_freeze_min_age = 0; + n->multixact_freeze_table_age = 0; + } else + { n->freeze_min_age = n->freeze_table_age = -1; + n->multixact_freeze_min_age = -1; + n->multixact_freeze_table_age = -1; + } n->relation = $5; n->va_cols = $6; if (n->va_cols != NIL) /* implies analyze */ @@ -8553,6 +8575,8 @@ AnalyzeStmt: n->options |= VACOPT_VERBOSE; n->freeze_min_age = -1; n->freeze_table_age = -1; + n->multixact_freeze_min_age = -1; + n->multixact_freeze_table_age = -1; n->relation = NULL; n->va_cols = NIL; $$ = (Node *)n; @@ -8565,6 +8589,8 @@ AnalyzeStmt: n->options |= VACOPT_VERBOSE; n->freeze_min_age = -1; n->freeze_table_age = -1; + n->multixact_freeze_min_age = -1; + n->multixact_freeze_table_age = -1; n->relation = $3; n->va_cols = $4; $$ = (Node *)n; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 38e206d6c4f..f29032c5ccc 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -116,6 +116,7 @@ double autovacuum_vac_scale; int autovacuum_anl_thresh; double autovacuum_anl_scale; int autovacuum_freeze_max_age; +int autovacuum_multixact_freeze_max_age; int autovacuum_vac_cost_delay; int autovacuum_vac_cost_limit; @@ -144,6 +145,8 @@ static MultiXactId recentMulti; /* Default freeze ages to use for autovacuum (varies by database) */ static int default_freeze_min_age; static int default_freeze_table_age; +static int default_multixact_freeze_min_age; +static int default_multixact_freeze_table_age; /* Memory context for long-lived data */ static MemoryContext AutovacMemCxt; @@ -185,6 +188,8 @@ typedef struct autovac_table bool at_doanalyze; int at_freeze_min_age; int at_freeze_table_age; + int at_multixact_freeze_min_age; + int at_multixact_freeze_table_age; int at_vacuum_cost_delay; int at_vacuum_cost_limit; bool at_wraparound; @@ -297,6 +302,7 @@ static void FreeWorkerInfo(int code, Datum arg); static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map, TupleDesc pg_class_desc); static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, + AutoVacOpts2 *relopts2, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, bool *dovacuum, bool *doanalyze, bool *wraparound); @@ -304,7 +310,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, static void autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy); static AutoVacOpts *extract_autovac_opts(HeapTuple tup, - TupleDesc pg_class_desc); + TupleDesc pg_class_desc, + AutoVacOpts2 **opts2); static PgStat_StatTabEntry *get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared, PgStat_StatDBEntry *dbentry); @@ -1129,7 +1136,7 @@ do_start_worker(void) /* Also determine the oldest datminmxid we will consider. */ recentMulti = ReadNextMultiXactId(); - multiForceLimit = recentMulti - autovacuum_freeze_max_age; + multiForceLimit = recentMulti - autovacuum_multixact_freeze_max_age; if (multiForceLimit < FirstMultiXactId) multiForceLimit -= FirstMultiXactId; @@ -1955,11 +1962,15 @@ do_autovacuum(void) { default_freeze_min_age = 0; default_freeze_table_age = 0; + default_multixact_freeze_min_age = 0; + default_multixact_freeze_table_age = 0; } else { 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_table_age = vacuum_multixact_freeze_table_age; } ReleaseSysCache(tuple); @@ -2011,6 +2022,7 @@ do_autovacuum(void) Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); PgStat_StatTabEntry *tabentry; AutoVacOpts *relopts; + AutoVacOpts2 *relopts2; Oid relid; bool dovacuum; bool doanalyze; @@ -2023,12 +2035,12 @@ do_autovacuum(void) relid = HeapTupleGetOid(tuple); /* Fetch reloptions and the pgstat entry for this table */ - relopts = extract_autovac_opts(tuple, pg_class_desc); + relopts = extract_autovac_opts(tuple, pg_class_desc, &relopts2); tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, shared, dbentry); /* Check if it needs vacuum or analyze */ - relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + relation_needs_vacanalyze(relid, relopts, relopts2, classForm, tabentry, &dovacuum, &doanalyze, &wraparound); /* @@ -2125,6 +2137,7 @@ do_autovacuum(void) PgStat_StatTabEntry *tabentry; Oid relid; AutoVacOpts *relopts = NULL; + AutoVacOpts2 *relopts2 = NULL; bool dovacuum; bool doanalyze; bool wraparound; @@ -2141,7 +2154,7 @@ do_autovacuum(void) * fetch reloptions -- if this toast table does not have them, try the * main rel */ - relopts = extract_autovac_opts(tuple, pg_class_desc); + relopts = extract_autovac_opts(tuple, pg_class_desc, &relopts2); if (relopts == NULL) { av_relation *hentry; @@ -2156,7 +2169,7 @@ do_autovacuum(void) tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, shared, dbentry); - relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + relation_needs_vacanalyze(relid, relopts, relopts2, classForm, tabentry, &dovacuum, &doanalyze, &wraparound); /* ignore analyze for toast tables */ @@ -2397,12 +2410,17 @@ deleted: * * Given a relation's pg_class tuple, return the AutoVacOpts portion of * reloptions, if set; otherwise, return NULL. + * + * 9.3 kludge: return the separate "AutoVacOpts2" part too. */ static AutoVacOpts * -extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) +extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc, AutoVacOpts2 **opts2) { bytea *relopts; AutoVacOpts *av; + AutoVacOpts2 *av2; + + *opts2 = NULL; Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION || ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW || @@ -2414,6 +2432,11 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) av = palloc(sizeof(AutoVacOpts)); memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts)); + + av2 = palloc(sizeof(AutoVacOpts2)); + memcpy(av2, &(((StdRdOptions *) relopts)->autovacuum2), sizeof(AutoVacOpts2)); + *opts2 = av2; + pfree(relopts); return av; @@ -2465,6 +2488,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, PgStat_StatDBEntry *dbentry; bool wraparound; AutoVacOpts *avopts; + AutoVacOpts2 *avopts2; /* use fresh stats */ autovac_refresh_stats(); @@ -2482,7 +2506,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * 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. */ - avopts = extract_autovac_opts(classTup, pg_class_desc); + avopts = extract_autovac_opts(classTup, pg_class_desc, &avopts2); if (classForm->relkind == RELKIND_TOASTVALUE && avopts == NULL && table_toast_map != NULL) { @@ -2498,7 +2522,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, shared, dbentry); - relation_needs_vacanalyze(relid, avopts, classForm, tabentry, + relation_needs_vacanalyze(relid, avopts, avopts2, classForm, tabentry, &dovacuum, &doanalyze, &wraparound); /* ignore ANALYZE for toast tables */ @@ -2510,6 +2534,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, { int freeze_min_age; int freeze_table_age; + int multixact_freeze_min_age; + int multixact_freeze_table_age; int vac_cost_limit; int vac_cost_delay; @@ -2543,12 +2569,24 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ? avopts->freeze_table_age : default_freeze_table_age; + multixact_freeze_min_age = (avopts2 && + avopts2->multixact_freeze_min_age >= 0) + ? avopts2->multixact_freeze_min_age + : default_multixact_freeze_min_age; + + multixact_freeze_table_age = (avopts2 && + avopts2->multixact_freeze_table_age >= 0) + ? avopts2->multixact_freeze_table_age + : default_multixact_freeze_table_age; + tab = palloc(sizeof(autovac_table)); tab->at_relid = relid; tab->at_dovacuum = dovacuum; tab->at_doanalyze = doanalyze; tab->at_freeze_min_age = freeze_min_age; tab->at_freeze_table_age = freeze_table_age; + tab->at_multixact_freeze_min_age = multixact_freeze_min_age; + tab->at_multixact_freeze_table_age = multixact_freeze_table_age; tab->at_vacuum_cost_limit = vac_cost_limit; tab->at_vacuum_cost_delay = vac_cost_delay; tab->at_wraparound = wraparound; @@ -2567,7 +2605,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * * Check whether a relation needs to be vacuumed or analyzed; return each into * "dovacuum" and "doanalyze", respectively. Also return whether the vacuum is - * being forced because of Xid wraparound. + * being forced because of Xid or multixact wraparound. * * relopts is a pointer to the AutoVacOpts options (either for itself in the * case of a plain table, or for either itself or its parent table in the case @@ -2586,7 +2624,8 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * analyze. This is asymmetric to the VACUUM case. * * We also force vacuum if the table's relfrozenxid is more than freeze_max_age - * transactions back. + * transactions back, and if its relminmxid is more than + * multixact_freeze_max_age multixacts back. * * A table whose autovacuum_enabled option is false is * automatically skipped (unless we have to vacuum it due to freeze_max_age). @@ -2601,6 +2640,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, + AutoVacOpts2 *relopts2, Form_pg_class classForm, PgStat_StatTabEntry *tabentry, /* output params below */ @@ -2628,6 +2668,7 @@ relation_needs_vacanalyze(Oid relid, /* freeze parameters */ int freeze_max_age; + int multixact_freeze_max_age; TransactionId xidForceLimit; MultiXactId multiForceLimit; @@ -2661,6 +2702,10 @@ relation_needs_vacanalyze(Oid relid, ? Min(relopts->freeze_max_age, autovacuum_freeze_max_age) : autovacuum_freeze_max_age; + multixact_freeze_max_age = (relopts2 && relopts2->multixact_freeze_max_age >= 0) + ? Min(relopts2->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age) + : autovacuum_multixact_freeze_max_age; + av_enabled = (relopts ? relopts->enabled : true); /* Force vacuum if table is at risk of wraparound */ @@ -2672,7 +2717,7 @@ relation_needs_vacanalyze(Oid relid, xidForceLimit)); if (!force_vacuum) { - multiForceLimit = recentMulti - autovacuum_freeze_max_age; + multiForceLimit = recentMulti - multixact_freeze_max_age; if (multiForceLimit < FirstMultiXactId) multiForceLimit -= FirstMultiXactId; force_vacuum = MultiXactIdPrecedes(classForm->relminmxid, @@ -2754,6 +2799,8 @@ autovacuum_do_vac_analyze(autovac_table *tab, vacstmt.options |= VACOPT_ANALYZE; vacstmt.freeze_min_age = tab->at_freeze_min_age; vacstmt.freeze_table_age = tab->at_freeze_table_age; + vacstmt.multixact_freeze_min_age = tab->at_multixact_freeze_min_age; + vacstmt.multixact_freeze_table_age = tab->at_multixact_freeze_table_age; /* we pass the OID, but might need this anyway for an error message */ vacstmt.relation = &rangevar; vacstmt.va_cols = NIL; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a055231cfc0..68af19221e0 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1907,6 +1907,26 @@ static struct config_int ConfigureNamesInt[] = }, { + {"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."), + NULL + }, + &vacuum_multixact_freeze_min_age, + 5000000, 0, 1000000000, + NULL, NULL, NULL + }, + + { + {"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."), + NULL + }, + &vacuum_multixact_freeze_table_age, + 150000000, 0, 2000000000, + NULL, NULL, NULL + }, + + { {"vacuum_defer_cleanup_age", PGC_SIGHUP, REPLICATION_MASTER, gettext_noop("Number of transactions by which VACUUM and HOT cleanup should be deferred, if any."), NULL @@ -2296,6 +2316,16 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, { + /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */ + {"autovacuum_multixact_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM, + gettext_noop("Multixact age at which to autovacuum a table to prevent multixact wraparound."), + NULL + }, + &autovacuum_multixact_freeze_max_age, + 400000000, 10000000, 2000000000, + NULL, NULL, NULL + }, + { /* see max_connections */ {"autovacuum_max_workers", PGC_POSTMASTER, AUTOVACUUM, gettext_noop("Sets the maximum number of simultaneously running autovacuum worker processes."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 0303ac78c5f..18196f8cd3f 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -362,12 +362,12 @@ # panic #log_min_error_statement = error # values in order of decreasing detail: - # debug5 + # debug5 # debug4 # debug3 # debug2 # debug1 - # info + # info # notice # warning # error @@ -431,7 +431,7 @@ #track_counts = on #track_io_timing = off #track_functions = none # none, pl, all -#track_activity_query_size = 1024 # (change requires restart) +#track_activity_query_size = 1024 # (change requires restart) #update_process_title = on #stats_temp_directory = 'pg_stat_tmp' @@ -465,6 +465,9 @@ #autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze #autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum # (change requires restart) +#autovacuum_multixact_freeze_max_age = 400000000 # maximum Multixact age + # before forced vacuum + # (change requires restart) #autovacuum_vacuum_cost_delay = 20ms # default vacuum cost delay for # autovacuum, in milliseconds; # -1 means use vacuum_cost_delay @@ -492,6 +495,8 @@ #lock_timeout = 0 # in milliseconds, 0 is disabled #vacuum_freeze_min_age = 50000000 #vacuum_freeze_table_age = 150000000 +#vacuum_multixact_freeze_min_age = 5000000 +#vacuum_multixact_freeze_table_age = 150000000 #bytea_output = 'hex' # hex, escape #xmlbinary = 'base64' #xmloption = 'content' diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 52ca1d1c797..9b0a53015b0 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -20,7 +20,8 @@ extern void cluster(ClusterStmt *stmt, bool isTopLevel); extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, - bool verbose, int freeze_min_age, int freeze_table_age); + bool verbose, int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, int multixact_freeze_table_age); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 44a3c3bd52e..4bc0f1437cb 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -136,6 +136,8 @@ extern PGDLLIMPORT int default_statistics_target; /* PGDLLIMPORT for * PostGIS */ extern int vacuum_freeze_min_age; extern int vacuum_freeze_table_age; +extern int vacuum_multixact_freeze_min_age; +extern int vacuum_multixact_freeze_table_age; /* in commands/vacuum.c */ @@ -156,6 +158,8 @@ extern void vac_update_relstats(Relation relation, TransactionId frozenxid, MultiXactId minmulti); extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age, + int multixact_freeze_min_age, + int multixact_freeze_table_age, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0eac9fb97e2..92981616afd 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2432,6 +2432,11 @@ typedef struct VacuumStmt int freeze_table_age; /* age at which to scan whole table */ RangeVar *relation; /* single table to process, or NULL */ List *va_cols; /* list of column names, or NIL for all */ + /* place these at the end, to avoid ABI break within 9.3 branch */ + int multixact_freeze_min_age; /* min multixact freeze age, + * or -1 to use default */ + int multixact_freeze_table_age; /* multixact age at which to + * scan whole table */ } VacuumStmt; /* ---------------------- diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index e96f07aaff9..70e0566fc04 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -24,6 +24,7 @@ extern double autovacuum_vac_scale; extern int autovacuum_anl_thresh; extern double autovacuum_anl_scale; extern int autovacuum_freeze_max_age; +extern int autovacuum_multixact_freeze_max_age; extern int autovacuum_vac_cost_delay; extern int autovacuum_vac_cost_limit; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 58cc3f7ea1a..fab7f6957a4 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -187,7 +187,11 @@ typedef struct RelationData * be applied to relations that use this format or a superset for * private options data. */ - /* autovacuum-related reloptions. */ + /* + * autovacuum-related reloptions. + * + * Split in two to avoid ABI break. + */ typedef struct AutoVacOpts { bool enabled; @@ -202,12 +206,26 @@ typedef struct AutoVacOpts float8 analyze_scale_factor; } AutoVacOpts; +/* + * The multixact freeze parameters were added after 9.3.2 had been released; + * to preserve ABI compatibility with modules that might have been compiled + * prior to 9.3.3, these are placed in a separate struct so that they can be + * located at the end of the containing struct. + */ +typedef struct AutoVacOpts2 +{ + int multixact_freeze_min_age; + int multixact_freeze_max_age; + int multixact_freeze_table_age; +} AutoVacOpts2; + typedef struct StdRdOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ int fillfactor; /* page fill factor in percent (0..100) */ AutoVacOpts autovacuum; /* autovacuum-related options */ bool security_barrier; /* for views */ + AutoVacOpts2 autovacuum2; /* rest of autovacuum options */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 |