diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 000a46fabb04ca21d5ebb853348e36f4cc0472b3..e12778b263c6c6b8dd5fa9a5f7b029a02a06e365 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -4892,6 +4892,33 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-autovacuum-multixact-freeze-max-age" xreflabel="autovacuum_multixact_freeze_max_age"> + <term><varname>autovacuum_multixact_freeze_max_age</varname> (<type>integer</type>)</term> + <indexterm> + <primary><varname>autovacuum_multixact_freeze_max_age</varname> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies the maximum age (in multixacts) that a table's + <structname>pg_class</>.<structfield>relminmxid</> field can + attain before a <command>VACUUM</> operation is forced to + prevent multixact ID wraparound within the table. + Note that the system will launch autovacuum processes to + prevent wraparound even when autovacuum is otherwise disabled. + </para> + + <para> + Vacuuming multixacts also allows removal of old files from the + <filename>pg_multixact/members</> and <filename>pg_multixact/offsets</> + subdirectories, which is why the default is a relatively low + 400 million multixacts. + This parameter can only be set at server start, but the setting + can be reduced for individual tables by changing storage parameters. + For more information see <xref linkend="vacuum-for-multixact-wraparound">. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-autovacuum-vacuum-cost-delay" xreflabel="autovacuum_vacuum_cost_delay"> <term><varname>autovacuum_vacuum_cost_delay</varname> (<type>integer</type>)</term> <indexterm> @@ -5300,7 +5327,7 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; <structname>pg_class</>.<structfield>relfrozenxid</> field has reached the age specified by this setting. The default is 150 million transactions. Although users can set this value anywhere from zero to - one billion, <command>VACUUM</> will silently limit the effective value + two billions, <command>VACUUM</> will silently limit the effective value to 95% of <xref linkend="guc-autovacuum-freeze-max-age">, so that a periodical manual <command>VACUUM</> has a chance to run before an anti-wraparound autovacuum is launched for the table. For more @@ -5331,6 +5358,47 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-vacuum-multixact-freeze-table-age" xreflabel="vacuum_multixact_freeze_table_age"> + <term><varname>vacuum_multixact_freeze_table_age</varname> (<type>integer</type>)</term> + <indexterm> + <primary><varname>vacuum_multixact_freeze_table_age</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + <command>VACUUM</> performs a whole-table scan if the table's + <structname>pg_class</>.<structfield>relminmxid</> field has reached + the age specified by this setting. The default is 150 million multixacts. + Although users can set this value anywhere from zero to two billions, + <command>VACUUM</> will silently limit the effective value to 95% of + <xref linkend="guc-autovacuum-multixact-freeze-max-age">, so that a + periodical manual <command>VACUUM</> has a chance to run before an + anti-wraparound is launched for the table. + For more information see <xref linkend="vacuum-for-multixact-wraparound">. + </para> + </listitem> + </varlistentry> + + <varlistentry id="guc-vacuum-multixact-freeze-min-age" xreflabel="vacuum_multixact_freeze_min_age"> + <term><varname>vacuum_multixact_freeze_min_age</varname> (<type>integer</type>)</term> + <indexterm> + <primary><varname>vacuum_multixact_freeze_min_age</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Specifies the cutoff age (in multixacts) that <command>VACUUM</> + should use to decide whether to replace multixact IDs with a newer + transaction ID or multixact ID while scanning a table. The default + is 5 million multixacts. + Although users can set this value anywhere from zero to one billion, + <command>VACUUM</> will silently limit the effective value to half + the value of <xref linkend="guc-autovacuum-multixact-freeze-max-age">, + so that there is not an unreasonably short time between forced + autovacuums. + For more information see <xref linkend="vacuum-for-multixact-wraparound">. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-bytea-output" xreflabel="bytea_output"> <term><varname>bytea_output</varname> (<type>enum</type>)</term> <indexterm> diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 339891e8a0c172d080f91a7d9c262c2d8d35c313..8ff309b78fe487bf9f50a5536ede44b85b10fc5e 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -108,7 +108,8 @@ <listitem> <simpara>To protect against loss of very old data due to - <firstterm>transaction ID wraparound</>.</simpara> + <firstterm>transaction ID wraparound</> or + <firstterm>multixact ID wraparound</>.</simpara> </listitem> </orderedlist> @@ -379,6 +380,11 @@ <secondary>wraparound</secondary> </indexterm> + <indexterm> + <primary>wraparound</primary> + <secondary>of transaction IDs</secondary> + </indexterm> + <para> <productname>PostgreSQL</productname>'s MVCC transaction semantics depend on being able to compare transaction ID (<acronym>XID</>) @@ -597,6 +603,54 @@ HINT: Stop the postmaster and vacuum that database in single-user mode. page for details about using single-user mode. </para> + <sect3 id="vacuum-for-multixact-wraparound"> + <title>Multixacts and Wraparound</title> + + <indexterm> + <primary>MultiXactId</primary> + </indexterm> + + <indexterm> + <primary>wraparound</primary> + <secondary>of multixact IDs</secondary> + </indexterm> + + <para> + <firstterm>Multixacts</> are used to implement row locking by + multiple transactions: since there is limited space in the tuple + header to store lock information, that information is stored as a + multixact separately in the <filename>pg_multixact</> subdirectory, + and only its ID is in the <structfield>xmax</> field + in the tuple header. + Similar to transaction IDs, multixact IDs are implemented as a + 32-bit counter and corresponding storage, all of which requires + careful aging management, storage cleanup, and wraparound handling. + </para> + + <para> + During a <command>VACUUM</> table scan, either partial or of the whole + table, any multixact ID older than + <xref linkend="guc-vacuum-multixact-freeze-min-age"> + is replaced by a different value, which can be the zero value, a single + transaction ID, or a newer multixact ID. For each table, + <structname>pg_class</>.<structfield>relminmxid</> stores the oldest + possible value still stored in any tuple of that table. Every time this + value is older than + <xref linkend="guc-vacuum-multixact-freeze-table-age">, a whole-table + scan is forced. Whole-table <command>VACUUM</> scans, regardless of + what causes them, enable advancing the value for that table. + Eventually, as all tables in all databases are scanned and their + oldest multixact values are advanced, on-disk storage for older + multixacts can be removed. + </para> + + <para> + As a safety device, a whole-table vacuum scan will occur for any table + whose multixact-age is greater than + <xref linkend="guc-autovacuum-multixact-freeze-max-age">. + This will occur even if autovacuum is nominally disabled. + </para> + </sect3> </sect2> <sect2 id="autovacuum"> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index e0b8a4ecaf60a0595f2e340aa4b9fc1d5a1d2812..7a01c63d5f028815d7dc78b8b9cb4efbd13e515e 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -985,7 +985,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI <para> Custom <xref linkend="guc-vacuum-freeze-min-age"> parameter. Note that autovacuum will ignore attempts to set a per-table - <literal>autovacuum_freeze_min_age</> larger than the half system-wide + <literal>autovacuum_freeze_min_age</> larger than half the system-wide <xref linkend="guc-autovacuum-freeze-max-age"> setting. </para> </listitem> @@ -1014,6 +1014,43 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI </listitem> </varlistentry> + <varlistentry> + <term><literal>autovacuum_multixact_freeze_min_age</literal>, <literal>toast.autovacuum_multixact_freeze_min_age</literal> (<type>integer</type>)</term> + <listitem> + <para> + Custom <xref linkend="guc-vacuum-multixact-freeze-min-age"> parameter. + Note that autovacuum will ignore attempts to set a per-table + <literal>autovacuum_multixact_freeze_min_age</> larger than half the + system-wide <xref linkend="guc-autovacuum-multixact-freeze-max-age"> + setting. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>autovacuum_multixact_freeze_max_age</literal>, <literal>toast.autovacuum_multixact_freeze_max_age</literal> (<type>integer</type>)</term> + <listitem> + <para> + Custom <xref linkend="guc-autovacuum-multixact-freeze-max-age"> parameter. Note + that autovacuum will ignore attempts to set a per-table + <literal>autovacuum_multixact_freeze_max_age</> larger than the + system-wide setting (it can only be set smaller). Note that while you + can set <literal>autovacuum_multixact_freeze_max_age</> very small, + or even zero, this is usually unwise since it will force frequent + vacuuming. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>autovacuum_multixact_freeze_table_age</literal>, <literal>toast.autovacuum_multixact_freeze_table_age</literal> (<type>integer</type>)</term> + <listitem> + <para> + Custom <xref linkend="guc-vacuum-multixact-freeze-table-age"> parameter. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect2> diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index fa08c45a139c874998cd3337afd8be57a78fba4f..530a1aee7bbe20f1b00a7d185462eb7b6761cb71 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -171,6 +171,14 @@ static relopt_int intRelOpts[] = }, -1, 0, 1000000000 }, + { + { + "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", @@ -179,6 +187,14 @@ static relopt_int intRelOpts[] = }, -1, 100000000, 2000000000 }, + { + { + "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", @@ -186,6 +202,14 @@ static relopt_int intRelOpts[] = 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 freeze row versions", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST + }, -1, 0, 2000000000 + }, + /* list terminator */ {{NULL}} }; @@ -1166,6 +1190,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, autovacuum) +offsetof(AutoVacOpts, multixact_freeze_min_age)}, + {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, + offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, multixact_freeze_max_age)}, + {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, + offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, 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 93824653376fd6f785d7f2cdf44e40bce3be2720..d4ad6787a59dddce26ba80da251031dc541e2288 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 c03fa6895452d479f9c2709eb92e5ad7dc8cd331..51b6b1a30213b2691be61faa2ffde5b6246ae6dd 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 14a5e5a2d41629e0b75e21e05a2f096052246f66..8b18e4acb72b53307397a6e07428b8ba396e63b5 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -850,7 +850,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, * Since we're going to rewrite the whole table anyway, there's no reason * not to be aggressive about this. */ - vacuum_set_xid_limits(0, 0, OldHeap->rd_rel->relisshared, + vacuum_set_xid_limits(0, 0, 0, 0, OldHeap->rd_rel->relisshared, &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff, NULL); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 3455a0b9ae653e94857ca0b9b95b1dcc248e718f..5ae7763534b35be938ac45960ef775460739bdcb 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); + } } /* diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 75e5f157eaaa1242d8936f2aec98a515cf3d69b3..d77892ee7f8ce0f1cfd5ca67b5f0d4e0b2b5e37d 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -205,6 +205,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); @@ -212,8 +214,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 f90cb6797de7f819cdbb087128a9d92b25e75340..6a59703025be5a30b0920120621d82cbacb60baa 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3242,6 +3242,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 9438e7861d8f189a9362ea661684bb6b78f3fac9..0bcbf42bfc4e5fdc64ed03865ba895ba7c2b902f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1503,6 +1503,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 0787eb7c5d4c2eda4ec57592c2f3bbe0281a1de0..ab3538a4afafaa863d00361058ab97c87e04274a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -8726,6 +8726,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; @@ -8740,6 +8742,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; @@ -8754,6 +8758,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 ')' @@ -8761,9 +8767,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; @@ -8773,9 +8787,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 */ @@ -8805,6 +8827,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; @@ -8817,6 +8841,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 77e683a6f1815836ae32ad38601ffee8097302b8..8926325faab29066e787efb64e2834f4f71fbd0c 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -117,6 +117,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; @@ -145,6 +146,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; @@ -186,6 +189,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; @@ -1130,7 +1135,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; @@ -1956,11 +1961,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); @@ -2511,6 +2520,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; @@ -2544,12 +2555,24 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ? avopts->freeze_table_age : default_freeze_table_age; + multixact_freeze_min_age = (avopts && + avopts->multixact_freeze_min_age >= 0) + ? avopts->multixact_freeze_min_age + : default_multixact_freeze_min_age; + + multixact_freeze_table_age = (avopts && + avopts->multixact_freeze_table_age >= 0) + ? avopts->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; @@ -2568,7 +2591,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 @@ -2587,7 +2610,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). @@ -2629,6 +2653,7 @@ relation_needs_vacanalyze(Oid relid, /* freeze parameters */ int freeze_max_age; + int multixact_freeze_max_age; TransactionId xidForceLimit; MultiXactId multiForceLimit; @@ -2662,6 +2687,10 @@ relation_needs_vacanalyze(Oid relid, ? Min(relopts->freeze_max_age, autovacuum_freeze_max_age) : autovacuum_freeze_max_age; + multixact_freeze_max_age = (relopts && relopts->multixact_freeze_max_age >= 0) + ? Min(relopts->multixact_freeze_max_age, autovacuum_multixact_freeze_max_age) + : autovacuum_multixact_freeze_max_age; + av_enabled = (relopts ? relopts->enabled : true); /* Force vacuum if table is at risk of wraparound */ @@ -2673,7 +2702,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, @@ -2755,6 +2784,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 2812a73d5454bd3cb13b83c06b3a980218b27ace..86afde17de536540a9e1c2f7ca38bd989ad6376d 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1975,6 +1975,26 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"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."), @@ -2398,6 +2418,16 @@ static struct config_int ConfigureNamesInt[] = 200000000, 100000000, 2000000000, 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, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index d10e8a5783a2fc210ac16563582a8d3fe27fb4ff..480c9e9797e3238a495f2435ecea465bedb5e696 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -448,7 +448,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' @@ -482,6 +482,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 @@ -509,6 +512,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/vacuum.h b/src/include/commands/vacuum.h index 7c368905393dc1d262f1a2888a0fe01980a16c62..70350e02cb2d8e3bb2683dad6748c7520db46b35 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 ad58b3949b60848c2e3911770e576d5ba5eeddcf..f649ad4aa07227b7f4c6b16e99b12f8ba7f80f20 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2533,6 +2533,10 @@ typedef struct VacuumStmt int options; /* OR of VacuumOption flags */ int freeze_min_age; /* min freeze age, or -1 to use default */ int freeze_table_age; /* age at which to scan whole table */ + 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 */ RangeVar *relation; /* single table to process, or NULL */ List *va_cols; /* list of column names, or NIL for all */ } VacuumStmt; diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index 543445cc2cbe5086eb0c16b4af0bde7e81b7da93..a43fcb11d1c14a255ba9da6eeefcde7aed6e26e6 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -25,6 +25,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 9b8a4c9aa50b56ec8d8095bc4c5ef9e0c3430546..c87dadc0ebdfd0baa247c878fcf8b1dbbdd55613 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -206,6 +206,9 @@ typedef struct AutoVacOpts int freeze_min_age; int freeze_max_age; int freeze_table_age; + int multixact_freeze_min_age; + int multixact_freeze_max_age; + int multixact_freeze_table_age; float8 vacuum_scale_factor; float8 analyze_scale_factor; } AutoVacOpts;