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.