aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/common/reloptions.c29
-rw-r--r--src/backend/access/transam/multixact.c21
-rw-r--r--src/backend/access/transam/varsup.c3
-rw-r--r--src/backend/commands/cluster.c29
-rw-r--r--src/backend/commands/vacuum.c67
-rw-r--r--src/backend/commands/vacuumlazy.c6
-rw-r--r--src/backend/nodes/copyfuncs.c2
-rw-r--r--src/backend/nodes/equalfuncs.c2
-rw-r--r--src/backend/parser/gram.y26
-rw-r--r--src/backend/postmaster/autovacuum.c71
-rw-r--r--src/backend/utils/misc/guc.c30
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample11
-rw-r--r--src/include/commands/cluster.h3
-rw-r--r--src/include/commands/vacuum.h4
-rw-r--r--src/include/nodes/parsenodes.h5
-rw-r--r--src/include/postmaster/autovacuum.h1
-rw-r--r--src/include/utils/rel.h20
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