aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/vacuum.c125
1 files changed, 99 insertions, 26 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8822a154dcc..ec9a7b65874 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -66,7 +66,10 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
-static void vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti);
+static void vac_truncate_clog(TransactionId frozenXID,
+ MultiXactId minMulti,
+ TransactionId lastSaneFrozenXid,
+ MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast,
bool for_wraparound);
@@ -733,19 +736,33 @@ vac_update_relstats(Relation relation,
}
/*
- * relfrozenxid should never go backward. Caller can pass
- * InvalidTransactionId if it has no new data.
+ * Update relfrozenxid, unless caller passed InvalidTransactionId
+ * indicating it has no new data.
+ *
+ * Ordinarily, we don't let relfrozenxid go backwards: if things are
+ * working correctly, the only way the new frozenxid could be older would
+ * be if a previous VACUUM was done with a tighter freeze_min_age, in
+ * which case we don't want to forget the work it already did. However,
+ * if the stored relfrozenxid is "in the future", then it must be corrupt
+ * and it seems best to overwrite it with the cutoff we used this time.
+ * See vac_update_datfrozenxid() concerning what we consider to be "in the
+ * future".
*/
if (TransactionIdIsNormal(frozenxid) &&
- TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid))
+ pgcform->relfrozenxid != frozenxid &&
+ (TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
+ TransactionIdPrecedes(GetOldestXmin(NULL, true),
+ pgcform->relfrozenxid)))
{
pgcform->relfrozenxid = frozenxid;
dirty = true;
}
- /* relminmxid must never go backward, either */
+ /* Similarly for relminmxid */
if (MultiXactIdIsValid(minmulti) &&
- MultiXactIdPrecedes(pgcform->relminmxid, minmulti))
+ pgcform->relminmxid != minmulti &&
+ (MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
+ MultiXactIdPrecedes(GetOldestMultiXactId(), pgcform->relminmxid)))
{
pgcform->relminmxid = minmulti;
dirty = true;
@@ -772,8 +789,8 @@ vac_update_relstats(Relation relation,
* truncate pg_clog and pg_multixact.
*
* We violate transaction semantics here by overwriting the database's
- * existing pg_database tuple with the new value. This is reasonably
- * safe since the new value is correct whether or not this transaction
+ * existing pg_database tuple with the new values. This is reasonably
+ * safe since the new values are correct whether or not this transaction
* commits. As with vac_update_relstats, this avoids leaving dead tuples
* behind after a VACUUM.
*/
@@ -786,7 +803,10 @@ vac_update_datfrozenxid(void)
SysScanDesc scan;
HeapTuple classTup;
TransactionId newFrozenXid;
+ TransactionId lastSaneFrozenXid;
MultiXactId newMinMulti;
+ MultiXactId lastSaneMinMulti;
+ bool bogus = false;
bool dirty = false;
/*
@@ -795,13 +815,13 @@ vac_update_datfrozenxid(void)
* committed pg_class entries for new tables; see AddNewRelationTuple().
* So we cannot produce a wrong minimum by starting with this.
*/
- newFrozenXid = GetOldestXmin(NULL, true);
+ newFrozenXid = lastSaneFrozenXid = GetOldestXmin(NULL, true);
/*
* Similarly, initialize the MultiXact "min" with the value that would be
* used on pg_class for new tables. See AddNewRelationTuple().
*/
- newMinMulti = GetOldestMultiXactId();
+ newMinMulti = lastSaneMinMulti = GetOldestMultiXactId();
/*
* We must seqscan pg_class to find the minimum Xid, because there is no
@@ -828,6 +848,21 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(classForm->relfrozenxid));
Assert(MultiXactIdIsValid(classForm->relminmxid));
+ /*
+ * If things are working properly, no relation should have a
+ * relfrozenxid or relminmxid that is "in the future". However, such
+ * cases have been known to arise due to bugs in pg_upgrade. If we
+ * see any entries that are "in the future", chicken out and don't do
+ * anything. This ensures we won't truncate clog before those
+ * relations have been scanned and cleaned up.
+ */
+ if (TransactionIdPrecedes(lastSaneFrozenXid, classForm->relfrozenxid) ||
+ MultiXactIdPrecedes(lastSaneMinMulti, classForm->relminmxid))
+ {
+ bogus = true;
+ break;
+ }
+
if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid))
newFrozenXid = classForm->relfrozenxid;
@@ -839,6 +874,10 @@ vac_update_datfrozenxid(void)
systable_endscan(scan);
heap_close(relation, AccessShareLock);
+ /* chicken out if bogus data found */
+ if (bogus)
+ return;
+
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
@@ -852,21 +891,30 @@ vac_update_datfrozenxid(void)
dbform = (Form_pg_database) GETSTRUCT(tuple);
/*
- * Don't allow datfrozenxid to go backward (probably can't happen anyway);
- * and detect the common case where it doesn't go forward either.
+ * As in vac_update_relstats(), we ordinarily don't want to let
+ * datfrozenxid go backward; but if it's "in the future" then it must be
+ * corrupt and it seems best to overwrite it.
*/
- if (TransactionIdPrecedes(dbform->datfrozenxid, newFrozenXid))
+ if (dbform->datfrozenxid != newFrozenXid &&
+ (TransactionIdPrecedes(dbform->datfrozenxid, newFrozenXid) ||
+ TransactionIdPrecedes(lastSaneFrozenXid, dbform->datfrozenxid)))
{
dbform->datfrozenxid = newFrozenXid;
dirty = true;
}
+ else
+ newFrozenXid = dbform->datfrozenxid;
- /* ditto */
- if (MultiXactIdPrecedes(dbform->datminmxid, newMinMulti))
+ /* Ditto for datminmxid */
+ if (dbform->datminmxid != newMinMulti &&
+ (MultiXactIdPrecedes(dbform->datminmxid, newMinMulti) ||
+ MultiXactIdPrecedes(lastSaneMinMulti, dbform->datminmxid)))
{
dbform->datminmxid = newMinMulti;
dirty = true;
}
+ else
+ newMinMulti = dbform->datminmxid;
if (dirty)
heap_inplace_update(relation, tuple);
@@ -875,12 +923,13 @@ vac_update_datfrozenxid(void)
heap_close(relation, RowExclusiveLock);
/*
- * If we were able to advance datfrozenxid, see if we can truncate
- * pg_clog. Also do it if the shared XID-wrap-limit info is stale, since
- * this action will update that too.
+ * If we were able to advance datfrozenxid or datminmxid, see if we can
+ * truncate pg_clog and/or pg_multixact. Also do it if the shared
+ * XID-wrap-limit info is stale, since this action will update that too.
*/
if (dirty || ForceTransactionIdLimitUpdate())
- vac_truncate_clog(newFrozenXid, newMinMulti);
+ vac_truncate_clog(newFrozenXid, newMinMulti,
+ lastSaneFrozenXid, lastSaneMinMulti);
}
@@ -890,16 +939,22 @@ vac_update_datfrozenxid(void)
* Scan pg_database to determine the system-wide oldest datfrozenxid,
* and use it to truncate the transaction commit log (pg_clog).
* Also update the XID wrap limit info maintained by varsup.c.
+ * Likewise for datminmxid.
*
- * The passed XID is simply the one I just wrote into my pg_database
- * entry. It's used to initialize the "min" calculation.
+ * The passed frozenXID and minMulti are the updated values for my own
+ * pg_database entry. They're used to initialize the "min" calculations.
+ * The caller also passes the "last sane" XID and MXID, since it has
+ * those at hand already.
*
* This routine is only invoked when we've managed to change our
- * DB's datfrozenxid entry, or we found that the shared XID-wrap-limit
- * info is stale.
+ * DB's datfrozenxid/datminmxid values, or we found that the shared
+ * XID-wrap-limit info is stale.
*/
static void
-vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti)
+vac_truncate_clog(TransactionId frozenXID,
+ MultiXactId minMulti,
+ TransactionId lastSaneFrozenXid,
+ MultiXactId lastSaneMinMulti)
{
TransactionId myXID = GetCurrentTransactionId();
Relation relation;
@@ -907,14 +962,15 @@ vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti)
HeapTuple tuple;
Oid oldestxid_datoid;
Oid minmulti_datoid;
+ bool bogus = false;
bool frozenAlreadyWrapped = false;
- /* init oldest datoids to sync with my frozen values */
+ /* init oldest datoids to sync with my frozenXID/minMulti values */
oldestxid_datoid = MyDatabaseId;
minmulti_datoid = MyDatabaseId;
/*
- * Scan pg_database to compute the minimum datfrozenxid
+ * Scan pg_database to compute the minimum datfrozenxid/datminmxid
*
* Note: we need not worry about a race condition with new entries being
* inserted by CREATE DATABASE. Any such entry will have a copy of some
@@ -936,6 +992,19 @@ vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti)
Assert(TransactionIdIsNormal(dbform->datfrozenxid));
Assert(MultiXactIdIsValid(dbform->datminmxid));
+ /*
+ * If things are working properly, no database should have a
+ * datfrozenxid or datminmxid that is "in the future". However, such
+ * cases have been known to arise due to bugs in pg_upgrade. If we
+ * see any entries that are "in the future", chicken out and don't do
+ * anything. This ensures we won't truncate clog before those
+ * databases have been scanned and cleaned up. (We will issue the
+ * "already wrapped" warning if appropriate, though.)
+ */
+ if (TransactionIdPrecedes(lastSaneFrozenXid, dbform->datfrozenxid) ||
+ MultiXactIdPrecedes(lastSaneMinMulti, dbform->datminmxid))
+ bogus = true;
+
if (TransactionIdPrecedes(myXID, dbform->datfrozenxid))
frozenAlreadyWrapped = true;
else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
@@ -969,6 +1038,10 @@ vac_truncate_clog(TransactionId frozenXID, MultiXactId minMulti)
return;
}
+ /* chicken out if data is bogus in any other way */
+ if (bogus)
+ return;
+
/*
* Truncate CLOG to the oldest computed value. Note we don't truncate
* multixacts; that will be done by the next checkpoint.