Initialize the minimum frozen Xid in vac_update_datfrozenxid using
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 11 Sep 2008 14:01:35 +0000 (14:01 +0000)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 11 Sep 2008 14:01:35 +0000 (14:01 +0000)
GetOldestXmin() instead of RecentGlobalXmin; this is safer because we do not
depend on the latter being correctly set elsewhere, and while it is more
expensive, this code path is not performance-critical.  This is a real
risk for autovacuum, because it can execute whole cycles without doing
a single vacuum, which would mean that RecentGlobalXmin would stay at its
initialization value, FirstNormalTransactionId, causing a bogus value to be
inserted in pg_database.  This bug could explain some recent reports of
failure to truncate pg_clog.

At the same time, change the initialization of RecentGlobalXmin to
InvalidTransactionId, and ensure that it's set to something else whenever
it's going to be used.  Using it as FirstNormalTransactionId in HOT page
pruning could incur in data loss.  InitPostgres takes care of setting it
to a valid value, but the extra checks are there to prevent "special"
backends from behaving in unusual ways.

Per Tom Lane's detailed problem dissection in 29544.1221061979@sss.pgh.pa.us

src/backend/access/heap/heapam.c
src/backend/access/index/indexam.c
src/backend/commands/vacuum.c
src/backend/executor/nodeBitmapHeapscan.c
src/backend/utils/init/postinit.c
src/backend/utils/time/snapmgr.c

index 33bf772646c644ea6786c023e693b510c3408575..05a1f6a02422b69b4ca8cc8911af9423aef41368 100644 (file)
@@ -219,6 +219,7 @@ heapgetpage(HeapScanDesc scan, BlockNumber page)
        /*
         * Prune and repair fragmentation for the whole page, if possible.
         */
+       Assert(TransactionIdIsValid(RecentGlobalXmin));
        heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
 
        /*
@@ -1469,6 +1470,8 @@ heap_hot_search_buffer(ItemPointer tid, Buffer buffer, Snapshot snapshot,
        if (all_dead)
                *all_dead = true;
 
+       Assert(TransactionIdIsValid(RecentGlobalXmin));
+
        Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
        offnum = ItemPointerGetOffsetNumber(tid);
        at_chain_start = true;
index 13c4726fc9d290de504b52da8e261012d0609924..0d101acbe9ddf5da683e01e3d14ce1cfb4c8ac94 100644 (file)
@@ -419,6 +419,8 @@ index_getnext(IndexScanDesc scan, ScanDirection direction)
        SCAN_CHECKS;
        GET_SCAN_PROCEDURE(amgettuple);
 
+       Assert(TransactionIdIsValid(RecentGlobalXmin));
+
        /*
         * We always reset xs_hot_dead; if we are here then either we are just
         * starting the scan, or we previously returned a visible tuple, and in
index de684fd8a645b6ace1fd9a976ad9be8efb34ccc0..409744949e36ee29343e1368874d3c1a84d2d028 100644 (file)
@@ -790,14 +790,12 @@ vac_update_datfrozenxid(void)
        bool            dirty = false;
 
        /*
-        * Initialize the "min" calculation with RecentGlobalXmin.      Any
-        * not-yet-committed pg_class entries for new tables must have
-        * relfrozenxid at least this high, because any other open xact must have
-        * RecentXmin >= its PGPROC.xmin >= our RecentGlobalXmin; see
-        * AddNewRelationTuple().  So we cannot produce a wrong minimum by
-        * starting with this.
+        * Initialize the "min" calculation with GetOldestXmin, which is a
+        * reasonable approximation to the minimum relfrozenxid for not-yet-
+        * committed pg_class entries for new tables; see AddNewRelationTuple().
+        * Se we cannot produce a wrong minimum by starting with this.
         */
-       newFrozenXid = RecentGlobalXmin;
+       newFrozenXid = GetOldestXmin(true, true);
 
        /*
         * We must seqscan pg_class to find the minimum Xid, because there is no
@@ -990,18 +988,16 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
        /* Begin a transaction for vacuuming this relation */
        StartTransactionCommand();
 
-       if (vacstmt->full)
-       {
-               /* functions in indexes may want a snapshot set */
-               PushActiveSnapshot(GetTransactionSnapshot());
-       }
-       else
+       /*
+        * Functions in indexes may want a snapshot set.  Also, setting
+        * a snapshot ensures that RecentGlobalXmin is kept truly recent.
+        */
+       PushActiveSnapshot(GetTransactionSnapshot());
+
+       if (!vacstmt->full)
        {
                /*
-                * During a lazy VACUUM we do not run any user-supplied functions, and
-                * so it should be safe to not create a transaction snapshot.
-                *
-                * We can furthermore set the PROC_IN_VACUUM flag, which lets other
+                * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets other
                 * concurrent VACUUMs know that they can ignore this one while
                 * determining their OldestXmin.  (The reason we don't set it during a
                 * full VACUUM is exactly that we may have to run user- defined
@@ -1050,8 +1046,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
 
        if (!onerel)
        {
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
@@ -1082,8 +1077,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
                                        (errmsg("skipping \"%s\" --- only table or database owner can vacuum it",
                                                        RelationGetRelationName(onerel))));
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
@@ -1099,8 +1093,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
                                (errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
                                                RelationGetRelationName(onerel))));
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
@@ -1115,8 +1108,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
        if (isOtherTempNamespace(RelationGetNamespace(onerel)))
        {
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
@@ -1168,8 +1160,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
        /*
         * Complete the transaction and free all temporary memory used.
         */
-       if (vacstmt->full)
-               PopActiveSnapshot();
+       PopActiveSnapshot();
        CommitTransactionCommand();
 
        /*
index daa12dd3827eebe4fa9b26e4104605457ee4883a..64a598da2bf4897d9ac5f2f6a04a1f4fa9a11e4f 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "access/heapam.h"
 #include "access/relscan.h"
+#include "access/transam.h"
 #include "executor/execdebug.h"
 #include "executor/nodeBitmapHeapscan.h"
 #include "pgstat.h"
@@ -262,6 +263,7 @@ bitgetpage(HeapScanDesc scan, TBMIterateResult *tbmres)
        /*
         * Prune and repair fragmentation for the whole page, if possible.
         */
+       Assert(TransactionIdIsValid(RecentGlobalXmin));
        heap_page_prune_opt(scan->rs_rd, buffer, RecentGlobalXmin);
 
        /*
index f8ad868677768f6a481b56bae8381d49601c04d1..461edd96cfabf190680dc3eb7953cd7e0764d307 100644 (file)
@@ -47,6 +47,7 @@
 #include "utils/plancache.h"
 #include "utils/portal.h"
 #include "utils/relcache.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
@@ -461,10 +462,15 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
        on_shmem_exit(ShutdownPostgres, 0);
 
        /*
-        * Start a new transaction here before first access to db
+        * Start a new transaction here before first access to db, and get a
+        * snapshot.  We don't have a use for the snapshot itself, but we're
+        * interested in the secondary effect that it sets RecentGlobalXmin.
         */
        if (!bootstrap)
+       {
                StartTransactionCommand();
+               (void) GetTransactionSnapshot();
+       }
 
        /*
         * Now that we have a transaction, we can take locks.  Take a writer's
index 419c88a30debfd932c79640a9cf5044c39fcb7ce..6e59a566c41f3da2608cc8f95ad69be20b55879d 100644 (file)
@@ -59,10 +59,14 @@ static Snapshot     SecondarySnapshot = NULL;
  * These are updated by GetSnapshotData.  We initialize them this way
  * for the convenience of TransactionIdIsInProgress: even in bootstrap
  * mode, we don't want it to say that BootstrapTransactionId is in progress.
+ *
+ * RecentGlobalXmin is initialized to InvalidTransactionId, to ensure that no
+ * one tries to use a stale value.  Readers should ensure that it has been set
+ * to something else before using it.
  */
 TransactionId TransactionXmin = FirstNormalTransactionId;
 TransactionId RecentXmin = FirstNormalTransactionId;
-TransactionId RecentGlobalXmin = FirstNormalTransactionId;
+TransactionId RecentGlobalXmin = InvalidTransactionId;
 
 /*
  * Elements of the list of registered snapshots.