Separate multixact freezing parameters from xid's
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 13 Feb 2014 22:30:30 +0000 (19:30 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 13 Feb 2014 22:36:31 +0000 (19:36 -0300)
Previously we were piggybacking on transaction ID parameters to freeze
multixacts; but since there isn't necessarily any relationship between
rates of Xid and multixact consumption, this turns out not to be a good
idea.

Therefore, we now have multixact-specific freezing parameters:

vacuum_multixact_freeze_min_age: when to remove multis as we come across
them in vacuum (default to 5 million, i.e. early in comparison to Xid's
default of 50 million)

vacuum_multixact_freeze_table_age: when to force whole-table scans
instead of scanning only the pages marked as not all visible in
visibility map (default to 150 million, same as for Xids).  Whichever of
both which reaches the 150 million mark earlier will cause a whole-table
scan.

autovacuum_multixact_freeze_max_age: when for cause emergency,
uninterruptible whole-table scans (default to 400 million, double as
that for Xids).  This means there shouldn't be more frequent emergency
vacuuming than previously, unless multixacts are being used very
rapidly.

Backpatch to 9.3 where multixacts were made to persist enough to require
freezing.  To avoid an ABI break in 9.3, VacuumStmt has a couple of
fields in an unnatural place, and StdRdOptions is split in two so that
the newly added fields can go at the end.

Patch by me, reviewed by Robert Haas, with additional input from Andres
Freund and Tom Lane.

19 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/maintenance.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/access/common/reloptions.c
src/backend/access/transam/multixact.c
src/backend/access/transam/varsup.c
src/backend/commands/cluster.c
src/backend/commands/vacuum.c
src/backend/commands/vacuumlazy.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/postmaster/autovacuum.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/include/commands/vacuum.h
src/include/nodes/parsenodes.h
src/include/postmaster/autovacuum.h
src/include/utils/rel.h

index 000a46fabb04ca21d5ebb853348e36f4cc0472b3..e12778b263c6c6b8dd5fa9a5f7b029a02a06e365 100644 (file)
@@ -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>
index 339891e8a0c172d080f91a7d9c262c2d8d35c313..8ff309b78fe487bf9f50a5536ede44b85b10fc5e 100644 (file)
 
      <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>
 
     <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">
index e0b8a4ecaf60a0595f2e340aa4b9fc1d5a1d2812..7a01c63d5f028815d7dc78b8b9cb4efbd13e515e 100644 (file)
@@ -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>
index fa08c45a139c874998cd3337afd8be57a78fba4f..530a1aee7bbe20f1b00a7d185462eb7b6761cb71 100644 (file)
@@ -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,
index 93824653376fd6f785d7f2cdf44e40bce3be2720..d4ad6787a59dddce26ba80da251031dc541e2288 100644 (file)
@@ -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;
 
index c03fa6895452d479f9c2709eb92e5ad7dc8cd331..51b6b1a30213b2691be61faa2ffde5b6246ae6dd 100644 (file)
@@ -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)
index 14a5e5a2d41629e0b75e21e05a2f096052246f66..8b18e4acb72b53307397a6e07428b8ba396e63b5 100644 (file)
@@ -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);
 
index 3455a0b9ae653e94857ca0b9b95b1dcc248e718f..5ae7763534b35be938ac45960ef775460739bdcb 100644 (file)
@@ -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);
+   }
 }
 
 /*
index 75e5f157eaaa1242d8936f2aec98a515cf3d69b3..d77892ee7f8ce0f1cfd5ca67b5f0d4e0b2b5e37d 100644 (file)
@@ -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);
index f90cb6797de7f819cdbb087128a9d92b25e75340..6a59703025be5a30b0920120621d82cbacb60baa 100644 (file)
@@ -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);
 
index 9438e7861d8f189a9362ea661684bb6b78f3fac9..0bcbf42bfc4e5fdc64ed03865ba895ba7c2b902f 100644 (file)
@@ -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);
 
index 0787eb7c5d4c2eda4ec57592c2f3bbe0281a1de0..ab3538a4afafaa863d00361058ab97c87e04274a 100644 (file)
@@ -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;
index 77e683a6f1815836ae32ad38601ffee8097302b8..8926325faab29066e787efb64e2834f4f71fbd0c 100644 (file)
@@ -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;
index 2812a73d5454bd3cb13b83c06b3a980218b27ace..86afde17de536540a9e1c2f7ca38bd989ad6376d 100644 (file)
@@ -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,
index d10e8a5783a2fc210ac16563582a8d3fe27fb4ff..480c9e9797e3238a495f2435ecea465bedb5e696 100644 (file)
 #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'
 
 #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
 #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'
index 7c368905393dc1d262f1a2888a0fe01980a16c62..70350e02cb2d8e3bb2683dad6748c7520db46b35 100644 (file)
@@ -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,
index ad58b3949b60848c2e3911770e576d5ba5eeddcf..f649ad4aa07227b7f4c6b16e99b12f8ba7f80f20 100644 (file)
@@ -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;
index 543445cc2cbe5086eb0c16b4af0bde7e81b7da93..a43fcb11d1c14a255ba9da6eeefcde7aed6e26e6 100644 (file)
@@ -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;
 
index 9b8a4c9aa50b56ec8d8095bc4c5ef9e0c3430546..c87dadc0ebdfd0baa247c878fcf8b1dbbdd55613 100644 (file)
@@ -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;