Fix full-table-vacuum request mechanism for MultiXactIds
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 28 Nov 2013 19:52:54 +0000 (16:52 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Sat, 30 Nov 2013 00:47:13 +0000 (21:47 -0300)
While autovacuum dutifully launched anti-multixact-wraparound vacuums
when the multixact "age" was reached, the vacuum code was not aware that
it needed to make them be full table vacuums.  As the resulting
partial-table vacuums aren't capable of actually increasing relminmxid,
autovacuum continued to launch anti-wraparound vacuums that didn't have
the intended effect, until age of relfrozenxid caused the vacuum to
finally be a full table one via vacuum_freeze_table_age.

To fix, introduce logic for multixacts similar to that for plain
TransactionIds, using the same GUCs.

Backpatch to 9.3, where permanent MultiXactIds were introduced.

Andres Freund, some cleanup by Álvaro

src/backend/access/transam/multixact.c
src/backend/commands/cluster.c
src/backend/commands/vacuum.c
src/backend/commands/vacuumlazy.c
src/include/access/multixact.h
src/include/commands/vacuum.h

index de0193aaf6453d2b74e275c6cc207f262389e29d..90fa030caf2c10c2490024d0b4a2ae35bf7485c9 100644 (file)
@@ -2374,6 +2374,21 @@ MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2)
    return (diff < 0);
 }
 
+/*
+ * MultiXactIdPrecedesOrEquals -- is multi1 logically <= multi2?
+ *
+ * XXX do we need to do something special for InvalidMultiXactId?
+ * (Doesn't look like it.)
+ */
+bool
+MultiXactIdPrecedesOrEquals(MultiXactId multi1, MultiXactId multi2)
+{
+   int32       diff = (int32) (multi1 - multi2);
+
+   return (diff <= 0);
+}
+
+
 /*
  * Decide which of two offsets is earlier.
  */
index afc6a7865081443958204ba1ef922b32fb90fc7e..0b8ac8c8d8e1f7bacde29df0404a195021cddf51 100644 (file)
@@ -176,7 +176,10 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
        /* close relation, keep lock till commit */
        heap_close(rel, NoLock);
 
-       /* Do the job */
+       /*
+        * 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);
    }
    else
@@ -226,6 +229,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
            StartTransactionCommand();
            /* functions in indexes may want a snapshot set */
            PushActiveSnapshot(GetTransactionSnapshot());
+           /* Do the job.  As above, use a -1 freeze_min_age. */
            cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose,
                        -1, -1);
            PopActiveSnapshot();
@@ -853,13 +857,12 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
        *pSwapToastByContent = false;
 
    /*
-    * compute xids used to freeze and weed out dead tuples.  We use -1
-    * freeze_min_age to avoid having CLUSTER freeze tuples earlier than a
-    * plain VACUUM would.
+    * compute xids used to freeze and weed out dead tuples.
     */
    vacuum_set_xid_limits(freeze_min_age, freeze_table_age,
                          OldHeap->rd_rel->relisshared,
-                         &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff);
+                         &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
+                         NULL);
 
    /*
     * FreezeXid will become the table's new relfrozenxid, and that mustn't go
index 27aea739e6b5fd9d743d6863c56da1efa2ada689..7dee79ca5ff3b8e8581a8dcabb0ed7af33e23409 100644 (file)
@@ -376,6 +376,24 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
 
 /*
  * vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
+ *
+ * The output parameters are:
+ * - oldestXmin is the cutoff value used to distinguish whether tuples are
+ *   DEAD or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum).
+ * - freezeLimit is the Xid below which all Xids are replaced by
+ *   FrozenTransactionId during vacuum.
+ * - xidFullScanLimit (computed from the the table_freeze_age parameter)
+ *   represents a minimum Xid value; a table whose relfrozenxid is older than
+ *   this will have a full-table vacuum applied to it, to freeze tuples across
+ *   the whole table.  Vacuuming a table younger than this value can use a
+ *   partial scan.
+ * - multiXactCutoff is the value below which all MultiXactIds are removed from
+ *   Xmax.
+ * - mxactFullScanLimit is a value against which a table's relminmxid value is
+ *   compared to produce a full-table vacuum, as with xidFullScanLimit.
+ *
+ * xidFullScanLimit and mxactFullScanLimit can be passed as NULL if caller is
+ * not interested.
  */
 void
 vacuum_set_xid_limits(int freeze_min_age,
@@ -383,12 +401,14 @@ vacuum_set_xid_limits(int freeze_min_age,
                      bool sharedRel,
                      TransactionId *oldestXmin,
                      TransactionId *freezeLimit,
-                     TransactionId *freezeTableLimit,
-                     MultiXactId *multiXactCutoff)
+                     TransactionId *xidFullScanLimit,
+                     MultiXactId *multiXactCutoff,
+                     MultiXactId *mxactFullScanLimit)
 {
    int         freezemin;
    TransactionId limit;
    TransactionId safeLimit;
+   MultiXactId mxactLimit;
 
    /*
     * We can always ignore processes running lazy vacuum.  This is because we
@@ -441,10 +461,22 @@ vacuum_set_xid_limits(int freeze_min_age,
 
    *freezeLimit = limit;
 
-   if (freezeTableLimit != NULL)
+   /*
+    * 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).
+    */
+   mxactLimit = GetOldestMultiXactId() - freezemin;
+   if (mxactLimit < FirstMultiXactId)
+       mxactLimit = FirstMultiXactId;
+   *multiXactCutoff = mxactLimit;
+
+   if (xidFullScanLimit != NULL)
    {
        int         freezetable;
 
+       Assert(mxactFullScanLimit != NULL);
+
        /*
         * Determine the table freeze age to use: as specified by the caller,
         * or vacuum_freeze_table_age, but in any case not more than
@@ -459,29 +491,25 @@ vacuum_set_xid_limits(int freeze_min_age,
        Assert(freezetable >= 0);
 
        /*
-        * Compute the cutoff XID, being careful not to generate a "permanent"
-        * XID.
+        * Compute XID limit causing a full-table vacuum, being careful not to
+        * generate a "permanent" XID.
         */
        limit = ReadNewTransactionId() - freezetable;
        if (!TransactionIdIsNormal(limit))
            limit = FirstNormalTransactionId;
 
-       *freezeTableLimit = limit;
-   }
-
-   if (multiXactCutoff != NULL)
-   {
-       MultiXactId mxLimit;
+       *xidFullScanLimit = limit;
 
        /*
-        * simplistic multixactid freezing: use the same freezing policy as
-        * for Xids
+        * 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.
         */
-       mxLimit = GetOldestMultiXactId() - freezemin;
-       if (mxLimit < FirstMultiXactId)
-           mxLimit = FirstMultiXactId;
+       mxactLimit = ReadNextMultiXactId() - freezetable;
+       if (mxactLimit < FirstMultiXactId)
+           mxactLimit = FirstMultiXactId;
 
-       *multiXactCutoff = mxLimit;
+       *mxactFullScanLimit = mxactLimit;
    }
 }
 
index 6688ab38565c590f605cb0310f49966086ee5272..fe2d9e78fa8ef62ec35780ec4f266292bf3fabdb 100644 (file)
@@ -180,7 +180,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
                write_rate;
    bool        scan_all;       /* should we scan all pages? */
    bool        scanned_all;    /* did we actually scan all pages? */
-   TransactionId freezeTableLimit;
+   TransactionId xidFullScanLimit;
+   MultiXactId mxactFullScanLimit;
    BlockNumber new_rel_pages;
    double      new_rel_tuples;
    BlockNumber new_rel_allvisible;
@@ -203,10 +204,19 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
 
    vacuum_set_xid_limits(vacstmt->freeze_min_age, vacstmt->freeze_table_age,
                          onerel->rd_rel->relisshared,
-                         &OldestXmin, &FreezeLimit, &freezeTableLimit,
-                         &MultiXactCutoff);
+                         &OldestXmin, &FreezeLimit, &xidFullScanLimit,
+                         &MultiXactCutoff, &mxactFullScanLimit);
+
+   /*
+    * 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.
+    */
    scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
-                                            freezeTableLimit);
+                                            xidFullScanLimit);
+   scan_all |= MultiXactIdPrecedesOrEquals(onerel->rd_rel->relminmxid,
+                                           mxactFullScanLimit);
 
    vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
 
index 8a9eddee387c7261d22b1f8eb0f4da7fc5dbb56e..e6db81a8270b3b815e97003014f76481bfca088b 100644 (file)
@@ -87,6 +87,8 @@ extern void MultiXactIdSetOldestMember(void);
 extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
                      bool allow_old);
 extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
+extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
+                           MultiXactId multi2);
 
 extern void AtEOXact_MultiXact(void);
 extern void AtPrepare_MultiXact(void);
index 08bec256ba855d7c105672ce93da0d3b6efb19fb..44a3c3bd52e3fec48b15b8b5dc580b1f347eb004 100644 (file)
@@ -159,8 +159,9 @@ extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age,
                      bool sharedRel,
                      TransactionId *oldestXmin,
                      TransactionId *freezeLimit,
-                     TransactionId *freezeTableLimit,
-                     MultiXactId *multiXactCutoff);
+                     TransactionId *xidFullScanLimit,
+                     MultiXactId *multiXactCutoff,
+                     MultiXactId *mxactFullScanLimit);
 extern void vac_update_datfrozenxid(void);
 extern void vacuum_delay_point(void);