]> granicus.if.org Git - postgresql/commitdiff
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:30:30 +0000 (19:30 -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.

20 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/cluster.h
src/include/commands/vacuum.h
src/include/nodes/parsenodes.h
src/include/postmaster/autovacuum.h
src/include/utils/rel.h

index efac9ebf6422ee2e6381965fb6453dfa5b252008..54716dd357b86b7efcf21ec7ef298483fb4cf305 100644 (file)
@@ -4730,6 +4730,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>
@@ -5138,7 +5165,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
@@ -5169,6 +5196,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 ae6456bc45ec701712ef1a53389c23ba2217186a..291f48a472fc81026aa78af138b9ecbb1113166a 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</>)
@@ -599,6 +605,54 @@ HINT:  Stop the postmaster and use a standalone backend to VACUUM in "mydb".
     page for details about using a single-user backend.
    </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 26eca6731c2ecd1d3eb1eebed59163c2de569707..38134ddc4e4f5a37eda63783d701527f9d124368 100644 (file)
@@ -981,7 +981,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>
@@ -1010,6 +1010,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 c439702a01163b94f065364fcc14b7918810e867..17bbcb5dbaba3725c50d5beb855da32071020093 100644 (file)
@@ -162,6 +162,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",
@@ -170,6 +178,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",
@@ -177,6 +193,13 @@ 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 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,
index e2b31ee441f662deae9220bc7fb75ace12e7b446..541612e503ba1f82db181aac093bffa6b0af25b4 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 0579c84bea20ff19eca87a88ccd6c943bae4f943..7252ee25c88db734c3ba9238c89307a89fd06835 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 1cdb22048a43956c5cccd2b4047f138ee394ed68..c903f0eba7c44d06c3ad0746d68e9dbba70392fd 100644 (file)
@@ -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);
index d50333f24c830b68e535722e12e0473fc44b4fa7..d5d915e280f5f881adbe41a4eb61e93c7dd84c77 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);
+       }
 }
 
 /*
@@ -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);
index 402a9e74751d102218587c50dda5bd9474a718c4..ce4172cb100873a63df7223e66478966b6c7e6a4 100644 (file)
@@ -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);
index 6b20e317323ebc31a1f46a255466a6b6ee5215e1..79ff80bb4dfe1b9ab2864296e4f9fa53890a5045 100644 (file)
@@ -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);
 
index b49e1e731deccf79345077ce5c2e2bc779bbeaae..c481fc4eedc0cbafa01b9c94dfdfe06ffa5be4b4 100644 (file)
@@ -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);
 
index 3feced6b6870a896087ca2ffb4a243ceb3c5f8bf..ed22e463d4c8335077c9a9909a453c0ebfa01e32 100644 (file)
@@ -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;
index 38e206d6c4f3694380524b3508aa2fddb17821d0..f29032c5ccc58b5924e9e29bccf8b41fe9988e77 100644 (file)
@@ -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;
index a055231cfc0c62acc083d309b6c15145740cb27c..68af19221e0cec117f6c999bfb36b8c965ef52e3 100644 (file)
@@ -1906,6 +1906,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."),
@@ -2295,6 +2315,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 0303ac78c5fdb173232ebe2c85ca3039547c20b8..18196f8cd3fc9ba445895fca7808518828bdf4ef 100644 (file)
                                        #   panic
 
 #log_min_error_statement = error       # values in order of decreasing detail:
-                                       #   debug5
+                                       #   debug5
                                        #   debug4
                                        #   debug3
                                        #   debug2
                                        #   debug1
-                                       #   info
+                                       #   info
                                        #   notice
                                        #   warning
                                        #   error
 #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 52ca1d1c7978d36904ca80e1710cba4066d26a97..9b0a53015b0c0e13dbf21dca96834236fadee061 100644 (file)
@@ -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);
index 44a3c3bd52e3fec48b15b8b5dc580b1f347eb004..4bc0f1437cb85cfa197c9138b18196bbcabad31c 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 0eac9fb97e293a1d02c356b3b0531327361b37d6..92981616afd2d83282b92096a5ad45f1ed4c8b0b 100644 (file)
@@ -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;
 
 /* ----------------------
index e96f07aaff94429c7e3559e5151add6b0c05cfa8..70e0566fc044f7ebc9a359a9779322778cffc078 100644 (file)
@@ -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;
 
index 58cc3f7ea1ad2801ee164659fa9e01141fd96f0b..fab7f6957a4d37ab3753a20a76253b4a8a523c93 100644 (file)
@@ -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