diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 42756a9e6df8d..86a88de853fe5 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -52,6 +52,7 @@ #include "access/xloginsert.h" #include "access/xlogutils.h" #include "catalog/catalog.h" +#include "commands/vacuum.h" #include "miscadmin.h" #include "pgstat.h" #include "port/atomics.h" @@ -6121,12 +6122,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple) */ static TransactionId FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, - TransactionId relfrozenxid, TransactionId relminmxid, - TransactionId cutoff_xid, MultiXactId cutoff_multi, - uint16 *flags, TransactionId *mxid_oldest_xid_out) + const struct VacuumCutoffs *cutoffs, uint16 *flags, + TransactionId *mxid_oldest_xid_out) { - TransactionId xid = InvalidTransactionId; - int i; + TransactionId newxmax = InvalidTransactionId; MultiXactMember *members; int nmembers; bool need_replace; @@ -6149,12 +6148,12 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, *flags |= FRM_INVALIDATE_XMAX; return InvalidTransactionId; } - else if (MultiXactIdPrecedes(multi, relminmxid)) + else if (MultiXactIdPrecedes(multi, cutoffs->relminmxid)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("found multixact %u from before relminmxid %u", - multi, relminmxid))); - else if (MultiXactIdPrecedes(multi, cutoff_multi)) + multi, cutoffs->relminmxid))); + else if (MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff)) { /* * This old multi cannot possibly have members still running, but @@ -6167,39 +6166,39 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("multixact %u from before cutoff %u found to be still running", - multi, cutoff_multi))); + multi, cutoffs->MultiXactCutoff))); if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)) { *flags |= FRM_INVALIDATE_XMAX; - xid = InvalidTransactionId; + newxmax = InvalidTransactionId; } else { - /* replace multi by update xid */ - xid = MultiXactIdGetUpdateXid(multi, t_infomask); + /* replace multi with single XID for its updater */ + newxmax = MultiXactIdGetUpdateXid(multi, t_infomask); /* wasn't only a lock, xid needs to be valid */ - Assert(TransactionIdIsValid(xid)); + Assert(TransactionIdIsValid(newxmax)); - if (TransactionIdPrecedes(xid, relfrozenxid)) + if (TransactionIdPrecedes(newxmax, cutoffs->relfrozenxid)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("found update xid %u from before relfrozenxid %u", - xid, relfrozenxid))); + newxmax, cutoffs->relfrozenxid))); /* - * If the xid is older than the cutoff, it has to have aborted, - * otherwise the tuple would have gotten pruned away. + * If the new xmax xid is older than OldestXmin, it has to have + * aborted, otherwise the tuple would have been pruned away */ - if (TransactionIdPrecedes(xid, cutoff_xid)) + if (TransactionIdPrecedes(newxmax, cutoffs->OldestXmin)) { - if (TransactionIdDidCommit(xid)) + if (TransactionIdDidCommit(newxmax)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("cannot freeze committed update xid %u", xid))); + errmsg_internal("cannot freeze committed update xid %u", newxmax))); *flags |= FRM_INVALIDATE_XMAX; - xid = InvalidTransactionId; + newxmax = InvalidTransactionId; } else { @@ -6211,17 +6210,14 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * Don't push back mxid_oldest_xid_out using FRM_RETURN_IS_XID Xid, or * when no Xids will remain */ - return xid; + return newxmax; } /* - * This multixact might have or might not have members still running, but - * we know it's valid and is newer than the cutoff point for multis. - * However, some member(s) of it may be below the cutoff for Xids, so we + * Some member(s) of this Multi may be below FreezeLimit xid cutoff, so we * need to walk the whole members array to figure out what to do, if * anything. */ - nmembers = GetMultiXactIdMembers(multi, &members, false, HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)); @@ -6232,12 +6228,15 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, return InvalidTransactionId; } - /* is there anything older than the cutoff? */ need_replace = false; temp_xid_out = *mxid_oldest_xid_out; /* init for FRM_NOOP */ - for (i = 0; i < nmembers; i++) + for (int i = 0; i < nmembers; i++) { - if (TransactionIdPrecedes(members[i].xid, cutoff_xid)) + TransactionId xid = members[i].xid; + + Assert(!TransactionIdPrecedes(xid, cutoffs->relfrozenxid)); + + if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit)) { need_replace = true; break; @@ -6247,7 +6246,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, } /* - * In the simplest case, there is no member older than the cutoff; we can + * In the simplest case, there is no member older than FreezeLimit; we can * keep the existing MultiXactId as-is, avoiding a more expensive second * pass over the multi */ @@ -6275,110 +6274,97 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, update_committed = false; temp_xid_out = *mxid_oldest_xid_out; /* init for FRM_RETURN_IS_MULTI */ - for (i = 0; i < nmembers; i++) + /* + * Determine whether to keep each member xid, or to ignore it instead + */ + for (int i = 0; i < nmembers; i++) { - /* - * Determine whether to keep this member or ignore it. - */ - if (ISUPDATE_from_mxstatus(members[i].status)) - { - TransactionId txid = members[i].xid; + TransactionId xid = members[i].xid; + MultiXactStatus mstatus = members[i].status; - Assert(TransactionIdIsValid(txid)); - if (TransactionIdPrecedes(txid, relfrozenxid)) - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("found update xid %u from before relfrozenxid %u", - txid, relfrozenxid))); + Assert(!TransactionIdPrecedes(xid, cutoffs->relfrozenxid)); + if (!ISUPDATE_from_mxstatus(mstatus)) + { /* - * It's an update; should we keep it? If the transaction is known - * aborted or crashed then it's okay to ignore it, otherwise not. - * Note that an updater older than cutoff_xid cannot possibly be - * committed, because HeapTupleSatisfiesVacuum would have returned - * HEAPTUPLE_DEAD and we would not be trying to freeze the tuple. - * - * As with all tuple visibility routines, it's critical to test - * TransactionIdIsInProgress before TransactionIdDidCommit, - * because of race conditions explained in detail in - * heapam_visibility.c. + * Locker XID (not updater XID). We only keep lockers that are + * still running. */ - if (TransactionIdIsCurrentTransactionId(txid) || - TransactionIdIsInProgress(txid)) - { - Assert(!TransactionIdIsValid(update_xid)); - update_xid = txid; - } - else if (TransactionIdDidCommit(txid)) - { - /* - * The transaction committed, so we can tell caller to set - * HEAP_XMAX_COMMITTED. (We can only do this because we know - * the transaction is not running.) - */ - Assert(!TransactionIdIsValid(update_xid)); - update_committed = true; - update_xid = txid; - } - else + if (TransactionIdIsCurrentTransactionId(xid) || + TransactionIdIsInProgress(xid)) { + newmembers[nnewmembers++] = members[i]; + has_lockers = true; + /* - * Not in progress, not committed -- must be aborted or - * crashed; we can ignore it. + * Cannot possibly be older than VACUUM's OldestXmin, so we + * don't need a NewRelfrozenXid step here */ + Assert(TransactionIdPrecedesOrEquals(cutoffs->OldestXmin, xid)); } - /* - * Since the tuple wasn't totally removed when vacuum pruned, the - * update Xid cannot possibly be older than the xid cutoff. The - * presence of such a tuple would cause corruption, so be paranoid - * and check. - */ - if (TransactionIdIsValid(update_xid) && - TransactionIdPrecedes(update_xid, cutoff_xid)) - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("found update xid %u from before xid cutoff %u", - update_xid, cutoff_xid))); + continue; + } + + /* + * Updater XID (not locker XID). Should we keep it? + * + * Since the tuple wasn't totally removed when vacuum pruned, the + * update Xid cannot possibly be older than OldestXmin cutoff. The + * presence of such a tuple would cause corruption, so be paranoid and + * check. + */ + if (TransactionIdPrecedes(xid, cutoffs->OldestXmin)) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg_internal("found update xid %u from before removable cutoff %u", + xid, cutoffs->OldestXmin))); + if (TransactionIdIsValid(update_xid)) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg_internal("multixact %u has two or more updating members", + multi), + errdetail_internal("First updater XID=%u second updater XID=%u.", + update_xid, xid))); + /* + * If the transaction is known aborted or crashed then it's okay to + * ignore it, otherwise not. + * + * As with all tuple visibility routines, it's critical to test + * TransactionIdIsInProgress before TransactionIdDidCommit, because of + * race conditions explained in detail in heapam_visibility.c. + */ + if (TransactionIdIsCurrentTransactionId(xid) || + TransactionIdIsInProgress(xid)) + update_xid = xid; + else if (TransactionIdDidCommit(xid)) + { /* - * We determined that this is an Xid corresponding to an update - * that must be retained -- add it to new members list for later. - * - * Also consider pushing back temp_xid_out, which is needed when - * we later conclude that a new multi is required (i.e. when we go - * on to set FRM_RETURN_IS_MULTI for our caller because we also - * need to retain a locker that's still running). + * The transaction committed, so we can tell caller to set + * HEAP_XMAX_COMMITTED. (We can only do this because we know the + * transaction is not running.) */ - if (TransactionIdIsValid(update_xid)) - { - newmembers[nnewmembers++] = members[i]; - if (TransactionIdPrecedes(members[i].xid, temp_xid_out)) - temp_xid_out = members[i].xid; - } + update_committed = true; + update_xid = xid; } else { - /* We only keep lockers if they are still running */ - if (TransactionIdIsCurrentTransactionId(members[i].xid) || - TransactionIdIsInProgress(members[i].xid)) - { - /* - * Running locker cannot possibly be older than the cutoff. - * - * The cutoff is <= VACUUM's OldestXmin, which is also the - * initial value used for top-level relfrozenxid_out tracking - * state. A running locker cannot be older than VACUUM's - * OldestXmin, either, so we don't need a temp_xid_out step. - */ - Assert(TransactionIdIsNormal(members[i].xid)); - Assert(!TransactionIdPrecedes(members[i].xid, cutoff_xid)); - Assert(!TransactionIdPrecedes(members[i].xid, - *mxid_oldest_xid_out)); - newmembers[nnewmembers++] = members[i]; - has_lockers = true; - } + /* + * Not in progress, not committed -- must be aborted or crashed; + * we can ignore it. + */ + continue; } + + /* + * We determined that this is an Xid corresponding to an update that + * must be retained -- add it to new members list for later. Also + * consider pushing back mxid_oldest_xid_out. + */ + newmembers[nnewmembers++] = members[i]; + if (TransactionIdPrecedes(xid, temp_xid_out)) + temp_xid_out = xid; } pfree(members); @@ -6391,7 +6377,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, { /* nothing worth keeping!? Tell caller to remove the whole thing */ *flags |= FRM_INVALIDATE_XMAX; - xid = InvalidTransactionId; + newxmax = InvalidTransactionId; /* Don't push back mxid_oldest_xid_out -- no Xids will remain */ } else if (TransactionIdIsValid(update_xid) && !has_lockers) @@ -6407,7 +6393,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, *flags |= FRM_RETURN_IS_XID; if (update_committed) *flags |= FRM_MARK_COMMITTED; - xid = update_xid; + newxmax = update_xid; /* Don't push back mxid_oldest_xid_out using FRM_RETURN_IS_XID Xid */ } else @@ -6417,26 +6403,29 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * one, to set as new Xmax in the tuple. The oldest surviving member * might push back mxid_oldest_xid_out. */ - xid = MultiXactIdCreateFromMembers(nnewmembers, newmembers); + newxmax = MultiXactIdCreateFromMembers(nnewmembers, newmembers); *flags |= FRM_RETURN_IS_MULTI; *mxid_oldest_xid_out = temp_xid_out; } pfree(newmembers); - return xid; + return newxmax; } /* * heap_prepare_freeze_tuple * * Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac) - * are older than the specified cutoff XID and cutoff MultiXactId. If so, + * are older than the FreezeLimit and/or MultiXactCutoff freeze cutoffs. If so, * setup enough state (in the *frz output argument) to later execute and - * WAL-log what we would need to do, and return true. Return false if nothing - * is to be changed. In addition, set *totally_frozen to true if the tuple - * will be totally frozen after these operations are performed and false if - * more freezing will eventually be required. + * WAL-log what caller needs to do for the tuple, and return true. Return + * false if nothing can be changed about the tuple right now. + * + * Also sets *totally_frozen to true if the tuple will be totally frozen once + * caller executes returned freeze plan (or if the tuple was already totally + * frozen by an earlier VACUUM). This indicates that there are no remaining + * XIDs or MultiXactIds that will need to be processed by a future VACUUM. * * VACUUM caller must assemble HeapTupleFreeze entries for every tuple that we * returned true for when called. A later heap_freeze_execute_prepared call @@ -6454,12 +6443,6 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * Each call here pushes back *relfrozenxid_out and/or *relminmxid_out as * needed to avoid unsafe final values in rel's authoritative pg_class tuple. * - * NB: cutoff_xid *must* be <= VACUUM's OldestXmin, to ensure that any - * XID older than it could neither be running nor seen as running by any - * open transaction. This ensures that the replacement will not change - * anyone's idea of the tuple state. - * Similarly, cutoff_multi must be <= VACUUM's OldestMxact. - * * NB: This function has side effects: it might allocate a new MultiXactId. * It will be set as tuple's new xmax when our *frz output is processed within * heap_execute_freeze_tuple later on. If the tuple is in a shared buffer @@ -6467,16 +6450,17 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, */ bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, - TransactionId relfrozenxid, TransactionId relminmxid, - TransactionId cutoff_xid, TransactionId cutoff_multi, + const struct VacuumCutoffs *cutoffs, HeapTupleFreeze *frz, bool *totally_frozen, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out) { - bool changed = false; - bool xmax_already_frozen = false; - bool xmin_frozen; - bool freeze_xmax; + bool xmin_already_frozen = false, + xmax_already_frozen = false; + bool freeze_xmin = false, + replace_xvac = false, + replace_xmax = false, + freeze_xmax = false; TransactionId xid; frz->frzflags = 0; @@ -6485,37 +6469,29 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, frz->xmax = HeapTupleHeaderGetRawXmax(tuple); /* - * Process xmin. xmin_frozen has two slightly different meanings: in the - * !XidIsNormal case, it means "the xmin doesn't need any freezing" (it's - * already a permanent value), while in the block below it is set true to - * mean "xmin won't need freezing after what we do to it here" (false - * otherwise). In both cases we're allowed to set totally_frozen, as far - * as xmin is concerned. Both cases also don't require relfrozenxid_out - * handling, since either way the tuple's xmin will be a permanent value - * once we're done with it. + * Process xmin, while keeping track of whether it's already frozen, or + * will become frozen when our freeze plan is executed by caller (could be + * neither). */ xid = HeapTupleHeaderGetXmin(tuple); if (!TransactionIdIsNormal(xid)) - xmin_frozen = true; + xmin_already_frozen = true; else { - if (TransactionIdPrecedes(xid, relfrozenxid)) + if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("found xmin %u from before relfrozenxid %u", - xid, relfrozenxid))); + xid, cutoffs->relfrozenxid))); - xmin_frozen = TransactionIdPrecedes(xid, cutoff_xid); - if (xmin_frozen) + freeze_xmin = TransactionIdPrecedes(xid, cutoffs->FreezeLimit); + if (freeze_xmin) { if (!TransactionIdDidCommit(xid)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen", - xid, cutoff_xid))); - - frz->t_infomask |= HEAP_XMIN_FROZEN; - changed = true; + xid, cutoffs->FreezeLimit))); } else { @@ -6525,10 +6501,27 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, } } + /* + * Old-style VACUUM FULL is gone, but we have to process xvac for as long + * as we support having MOVED_OFF/MOVED_IN tuples in the database + */ + xid = HeapTupleHeaderGetXvac(tuple); + if (TransactionIdIsNormal(xid)) + { + Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid)); + Assert(TransactionIdPrecedes(xid, cutoffs->OldestXmin)); + + /* + * For Xvac, we always freeze proactively. This allows totally_frozen + * tracking to ignore xvac. + */ + replace_xvac = true; + } + /* * Process xmax. To thoroughly examine the current Xmax value we need to * resolve a MultiXactId to its member Xids, in case some of them are - * below the given cutoff for Xids. In that case, those values might need + * below the given FreezeLimit. In that case, those values might need * freezing, too. Also, if a multi needs freezing, we cannot simply take * it out --- if there's a live updater Xid, it needs to be kept. * @@ -6543,13 +6536,9 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, uint16 flags; TransactionId mxid_oldest_xid_out = *relfrozenxid_out; - newxmax = FreezeMultiXactId(xid, tuple->t_infomask, - relfrozenxid, relminmxid, - cutoff_xid, cutoff_multi, + newxmax = FreezeMultiXactId(xid, tuple->t_infomask, cutoffs, &flags, &mxid_oldest_xid_out); - freeze_xmax = (flags & FRM_INVALIDATE_XMAX); - if (flags & FRM_RETURN_IS_XID) { /* @@ -6558,8 +6547,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, * Might have to ratchet back relfrozenxid_out here, though never * relminmxid_out. */ - Assert(!freeze_xmax); - Assert(TransactionIdIsValid(newxmax)); + Assert(!TransactionIdPrecedes(newxmax, cutoffs->OldestXmin)); if (TransactionIdPrecedes(newxmax, *relfrozenxid_out)) *relfrozenxid_out = newxmax; @@ -6574,7 +6562,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, frz->xmax = newxmax; if (flags & FRM_MARK_COMMITTED) frz->t_infomask |= HEAP_XMAX_COMMITTED; - changed = true; + replace_xmax = true; } else if (flags & FRM_RETURN_IS_MULTI) { @@ -6587,9 +6575,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, * Might have to ratchet back relfrozenxid_out here, though never * relminmxid_out. */ - Assert(!freeze_xmax); - Assert(MultiXactIdIsValid(newxmax)); - Assert(!MultiXactIdPrecedes(newxmax, *relminmxid_out)); + Assert(!MultiXactIdPrecedes(newxmax, cutoffs->OldestMxact)); Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out, *relfrozenxid_out)); *relfrozenxid_out = mxid_oldest_xid_out; @@ -6605,10 +6591,8 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, GetMultiXactIdHintBits(newxmax, &newbits, &newbits2); frz->t_infomask |= newbits; frz->t_infomask2 |= newbits2; - frz->xmax = newxmax; - - changed = true; + replace_xmax = true; } else if (flags & FRM_NOOP) { @@ -6617,7 +6601,6 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, * Might have to ratchet back relminmxid_out, relfrozenxid_out, or * both together. */ - Assert(!freeze_xmax); Assert(MultiXactIdIsValid(newxmax) && xid == newxmax); Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out, *relfrozenxid_out)); @@ -6628,23 +6611,27 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, else { /* - * Keeping nothing (neither an Xid nor a MultiXactId) in xmax. - * Won't have to ratchet back relminmxid_out or relfrozenxid_out. + * Freeze plan for tuple "freezes xmax" in the strictest sense: + * it'll leave nothing in xmax (neither an Xid nor a MultiXactId). */ - Assert(freeze_xmax); + Assert(flags & FRM_INVALIDATE_XMAX); + Assert(MultiXactIdPrecedes(xid, cutoffs->OldestMxact)); Assert(!TransactionIdIsValid(newxmax)); + + /* Will set t_infomask/t_infomask2 flags in freeze plan below */ + freeze_xmax = true; } } else if (TransactionIdIsNormal(xid)) { /* Raw xmax is normal XID */ - if (TransactionIdPrecedes(xid, relfrozenxid)) + if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid)) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("found xmax %u from before relfrozenxid %u", - xid, relfrozenxid))); + xid, cutoffs->relfrozenxid))); - if (TransactionIdPrecedes(xid, cutoff_xid)) + if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit)) { /* * If we freeze xmax, make absolutely sure that it's not an XID @@ -6663,7 +6650,6 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, } else { - freeze_xmax = false; if (TransactionIdPrecedes(xid, *relfrozenxid_out)) *relfrozenxid_out = xid; } @@ -6672,19 +6658,41 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, { /* Raw xmax is InvalidTransactionId XID */ Assert((tuple->t_infomask & HEAP_XMAX_IS_MULTI) == 0); - freeze_xmax = false; xmax_already_frozen = true; - /* No need for relfrozenxid_out handling for already-frozen xmax */ } else ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg_internal("found xmax %u (infomask 0x%04x) not frozen, not multi, not normal", + errmsg_internal("found raw xmax %u (infomask 0x%04x) not invalid and not multi", xid, tuple->t_infomask))); + if (freeze_xmin) + { + Assert(!xmin_already_frozen); + + frz->t_infomask |= HEAP_XMIN_FROZEN; + } + if (replace_xvac) + { + /* + * If a MOVED_OFF tuple is not dead, the xvac transaction must have + * failed; whereas a non-dead MOVED_IN tuple must mean the xvac + * transaction succeeded. + */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + frz->frzflags |= XLH_INVALID_XVAC; + else + frz->frzflags |= XLH_FREEZE_XVAC; + } + if (replace_xmax) + { + Assert(!xmax_already_frozen && !freeze_xmax); + + /* Already set t_infomask/t_infomask2 flags in freeze plan */ + } if (freeze_xmax) { - Assert(!xmax_already_frozen); + Assert(!xmax_already_frozen && !replace_xmax); frz->xmax = InvalidTransactionId; @@ -6697,52 +6705,20 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, frz->t_infomask |= HEAP_XMAX_INVALID; frz->t_infomask2 &= ~HEAP_HOT_UPDATED; frz->t_infomask2 &= ~HEAP_KEYS_UPDATED; - changed = true; } /* - * Old-style VACUUM FULL is gone, but we have to keep this code as long as - * we support having MOVED_OFF/MOVED_IN tuples in the database. + * Determine if this tuple is already totally frozen, or will become + * totally frozen */ - if (tuple->t_infomask & HEAP_MOVED) - { - xid = HeapTupleHeaderGetXvac(tuple); - - /* - * For Xvac, we ignore the cutoff_xid and just always perform the - * freeze operation. The oldest release in which such a value can - * actually be set is PostgreSQL 8.4, because old-style VACUUM FULL - * was removed in PostgreSQL 9.0. Note that if we were to respect - * cutoff_xid here, we'd need to make surely to clear totally_frozen - * when we skipped freezing on that basis. - * - * No need for relfrozenxid_out handling, since we always freeze xvac. - */ - if (TransactionIdIsNormal(xid)) - { - /* - * If a MOVED_OFF tuple is not dead, the xvac transaction must - * have failed; whereas a non-dead MOVED_IN tuple must mean the - * xvac transaction succeeded. - */ - if (tuple->t_infomask & HEAP_MOVED_OFF) - frz->frzflags |= XLH_INVALID_XVAC; - else - frz->frzflags |= XLH_FREEZE_XVAC; + *totally_frozen = ((freeze_xmin || xmin_already_frozen) && + (freeze_xmax || xmax_already_frozen)); - /* - * Might as well fix the hint bits too; usually XMIN_COMMITTED - * will already be set here, but there's a small chance not. - */ - Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID)); - frz->t_infomask |= HEAP_XMIN_COMMITTED; - changed = true; - } - } + /* A "totally_frozen" tuple must not leave anything behind in xmax */ + Assert(!*totally_frozen || !replace_xmax); - *totally_frozen = (xmin_frozen && - (freeze_xmax || xmax_already_frozen)); - return changed; + /* Tell caller if this tuple has a usable freeze plan set in *frz */ + return freeze_xmin || replace_xvac || replace_xmax || freeze_xmax; } /* @@ -6861,19 +6837,25 @@ heap_freeze_execute_prepared(Relation rel, Buffer buffer, bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId relfrozenxid, TransactionId relminmxid, - TransactionId cutoff_xid, TransactionId cutoff_multi) + TransactionId FreezeLimit, TransactionId MultiXactCutoff) { HeapTupleFreeze frz; bool do_freeze; - bool tuple_totally_frozen; - TransactionId relfrozenxid_out = cutoff_xid; - MultiXactId relminmxid_out = cutoff_multi; + bool totally_frozen; + struct VacuumCutoffs cutoffs; + TransactionId NewRelfrozenXid = FreezeLimit; + MultiXactId NewRelminMxid = MultiXactCutoff; + + cutoffs.relfrozenxid = relfrozenxid; + cutoffs.relminmxid = relminmxid; + cutoffs.OldestXmin = FreezeLimit; + cutoffs.OldestMxact = MultiXactCutoff; + cutoffs.FreezeLimit = FreezeLimit; + cutoffs.MultiXactCutoff = MultiXactCutoff; - do_freeze = heap_prepare_freeze_tuple(tuple, - relfrozenxid, relminmxid, - cutoff_xid, cutoff_multi, - &frz, &tuple_totally_frozen, - &relfrozenxid_out, &relminmxid_out); + do_freeze = heap_prepare_freeze_tuple(tuple, &cutoffs, + &frz, &totally_frozen, + &NewRelfrozenXid, &NewRelminMxid); /* * Note that because this is not a WAL-logged operation, we don't need to @@ -7308,23 +7290,24 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple) * never freeze here, which makes tracking the oldest extant XID/MXID simple. */ bool -heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, - MultiXactId cutoff_multi, +heap_tuple_would_freeze(HeapTupleHeader tuple, + const struct VacuumCutoffs *cutoffs, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out) { TransactionId xid; MultiXactId multi; - bool would_freeze = false; + bool freeze = false; /* First deal with xmin */ xid = HeapTupleHeaderGetXmin(tuple); if (TransactionIdIsNormal(xid)) { + Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid)); if (TransactionIdPrecedes(xid, *relfrozenxid_out)) *relfrozenxid_out = xid; - if (TransactionIdPrecedes(xid, cutoff_xid)) - would_freeze = true; + if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit)) + freeze = true; } /* Now deal with xmax */ @@ -7337,11 +7320,12 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, if (TransactionIdIsNormal(xid)) { + Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid)); /* xmax is a non-permanent XID */ if (TransactionIdPrecedes(xid, *relfrozenxid_out)) *relfrozenxid_out = xid; - if (TransactionIdPrecedes(xid, cutoff_xid)) - would_freeze = true; + if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit)) + freeze = true; } else if (!MultiXactIdIsValid(multi)) { @@ -7353,7 +7337,7 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, if (MultiXactIdPrecedes(multi, *relminmxid_out)) *relminmxid_out = multi; /* heap_prepare_freeze_tuple always freezes pg_upgrade'd xmax */ - would_freeze = true; + freeze = true; } else { @@ -7361,10 +7345,11 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, MultiXactMember *members; int nmembers; + Assert(MultiXactIdPrecedesOrEquals(cutoffs->relminmxid, multi)); if (MultiXactIdPrecedes(multi, *relminmxid_out)) *relminmxid_out = multi; - if (MultiXactIdPrecedes(multi, cutoff_multi)) - would_freeze = true; + if (MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff)) + freeze = true; /* need to check whether any member of the mxact is old */ nmembers = GetMultiXactIdMembers(multi, &members, false, @@ -7373,11 +7358,11 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, for (int i = 0; i < nmembers; i++) { xid = members[i].xid; - Assert(TransactionIdIsNormal(xid)); + Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid)); if (TransactionIdPrecedes(xid, *relfrozenxid_out)) *relfrozenxid_out = xid; - if (TransactionIdPrecedes(xid, cutoff_xid)) - would_freeze = true; + if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit)) + freeze = true; } if (nmembers > 0) pfree(members); @@ -7388,14 +7373,15 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, xid = HeapTupleHeaderGetXvac(tuple); if (TransactionIdIsNormal(xid)) { + Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid)); if (TransactionIdPrecedes(xid, *relfrozenxid_out)) *relfrozenxid_out = xid; /* heap_prepare_freeze_tuple always freezes xvac */ - would_freeze = true; + freeze = true; } } - return would_freeze; + return freeze; } /* diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index d59711b7ecb21..e40150379513c 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -144,6 +144,10 @@ typedef struct LVRelState Relation *indrels; int nindexes; + /* Buffer access strategy and parallel vacuum state */ + BufferAccessStrategy bstrategy; + ParallelVacuumState *pvs; + /* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */ bool aggressive; /* Use visibility map to skip? (disabled by DISABLE_PAGE_SKIPPING) */ @@ -158,21 +162,9 @@ typedef struct LVRelState bool do_index_cleanup; bool do_rel_truncate; - /* Buffer access strategy and parallel vacuum state */ - BufferAccessStrategy bstrategy; - ParallelVacuumState *pvs; - - /* rel's initial relfrozenxid and relminmxid */ - TransactionId relfrozenxid; - MultiXactId relminmxid; - double old_live_tuples; /* previous value of pg_class.reltuples */ - /* VACUUM operation's cutoffs for freezing and pruning */ - TransactionId OldestXmin; + struct VacuumCutoffs cutoffs; GlobalVisState *vistest; - /* VACUUM operation's target cutoffs for freezing XIDs and MultiXactIds */ - TransactionId FreezeLimit; - MultiXactId MultiXactCutoff; /* Tracks oldest extant XID/MXID for setting relfrozenxid/relminmxid */ TransactionId NewRelfrozenXid; MultiXactId NewRelminMxid; @@ -314,14 +306,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, LVRelState *vacrel; bool verbose, instrument, - aggressive, skipwithvm, frozenxid_updated, minmulti_updated; - TransactionId OldestXmin, - FreezeLimit; - MultiXactId OldestMxact, - MultiXactCutoff; BlockNumber orig_rel_pages, new_rel_pages, new_rel_allvisible; @@ -353,27 +340,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM, RelationGetRelid(rel)); - /* - * Get OldestXmin cutoff, which is used to determine which deleted tuples - * are considered DEAD, not just RECENTLY_DEAD. Also get related cutoffs - * used to determine which XIDs/MultiXactIds will be frozen. If this is - * an aggressive VACUUM then lazy_scan_heap cannot leave behind unfrozen - * XIDs < FreezeLimit (all MXIDs < MultiXactCutoff also need to go away). - */ - aggressive = vacuum_set_xid_limits(rel, params, &OldestXmin, &OldestMxact, - &FreezeLimit, &MultiXactCutoff); - - skipwithvm = true; - if (params->options & VACOPT_DISABLE_PAGE_SKIPPING) - { - /* - * Force aggressive mode, and disable skipping blocks using the - * visibility map (even those set all-frozen) - */ - aggressive = true; - skipwithvm = false; - } - /* * Setup error traceback support for ereport() first. The idea is to set * up an error context callback to display additional information on any @@ -396,25 +362,12 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, errcallback.arg = vacrel; errcallback.previous = error_context_stack; error_context_stack = &errcallback; - if (verbose) - { - Assert(!IsAutoVacuumWorkerProcess()); - if (aggressive) - ereport(INFO, - (errmsg("aggressively vacuuming \"%s.%s.%s\"", - get_database_name(MyDatabaseId), - vacrel->relnamespace, vacrel->relname))); - else - ereport(INFO, - (errmsg("vacuuming \"%s.%s.%s\"", - get_database_name(MyDatabaseId), - vacrel->relnamespace, vacrel->relname))); - } /* Set up high level stuff about rel and its indexes */ vacrel->rel = rel; vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes, &vacrel->indrels); + vacrel->bstrategy = bstrategy; if (instrument && vacrel->nindexes > 0) { /* Copy index names used by instrumentation (not error reporting) */ @@ -435,8 +388,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, Assert(params->index_cleanup != VACOPTVALUE_UNSPECIFIED); Assert(params->truncate != VACOPTVALUE_UNSPECIFIED && params->truncate != VACOPTVALUE_AUTO); - vacrel->aggressive = aggressive; - vacrel->skipwithvm = skipwithvm; vacrel->failsafe_active = false; vacrel->consider_bypass_optimization = true; vacrel->do_index_vacuuming = true; @@ -459,11 +410,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, Assert(params->index_cleanup == VACOPTVALUE_AUTO); } - vacrel->bstrategy = bstrategy; - vacrel->relfrozenxid = rel->rd_rel->relfrozenxid; - vacrel->relminmxid = rel->rd_rel->relminmxid; - vacrel->old_live_tuples = rel->rd_rel->reltuples; - /* Initialize page counters explicitly (be tidy) */ vacrel->scanned_pages = 0; vacrel->removed_pages = 0; @@ -489,32 +435,53 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->missed_dead_tuples = 0; /* - * Determine the extent of the blocks that we'll scan in lazy_scan_heap, - * and finalize cutoffs used for freezing and pruning in lazy_scan_prune. + * Get cutoffs that determine which deleted tuples are considered DEAD, + * not just RECENTLY_DEAD, and which XIDs/MXIDs to freeze. Then determine + * the extent of the blocks that we'll scan in lazy_scan_heap. It has to + * happen in this order to ensure that the OldestXmin cutoff field works + * as an upper bound on the XIDs stored in the pages we'll actually scan + * (NewRelfrozenXid tracking must never be allowed to miss unfrozen XIDs). * + * Next acquire vistest, a related cutoff that's used in heap_page_prune. * We expect vistest will always make heap_page_prune remove any deleted * tuple whose xmax is < OldestXmin. lazy_scan_prune must never become * confused about whether a tuple should be frozen or removed. (In the * future we might want to teach lazy_scan_prune to recompute vistest from * time to time, to increase the number of dead tuples it can prune away.) - * - * We must determine rel_pages _after_ OldestXmin has been established. - * lazy_scan_heap's physical heap scan (scan of pages < rel_pages) is - * thereby guaranteed to not miss any tuples with XIDs < OldestXmin. These - * XIDs must at least be considered for freezing (though not necessarily - * frozen) during its scan. */ + vacrel->aggressive = vacuum_get_cutoffs(rel, params, &vacrel->cutoffs); vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel); - vacrel->OldestXmin = OldestXmin; vacrel->vistest = GlobalVisTestFor(rel); - /* FreezeLimit controls XID freezing (always <= OldestXmin) */ - vacrel->FreezeLimit = FreezeLimit; - /* MultiXactCutoff controls MXID freezing (always <= OldestMxact) */ - vacrel->MultiXactCutoff = MultiXactCutoff; /* Initialize state used to track oldest extant XID/MXID */ - vacrel->NewRelfrozenXid = OldestXmin; - vacrel->NewRelminMxid = OldestMxact; + vacrel->NewRelfrozenXid = vacrel->cutoffs.OldestXmin; + vacrel->NewRelminMxid = vacrel->cutoffs.OldestMxact; vacrel->skippedallvis = false; + skipwithvm = true; + if (params->options & VACOPT_DISABLE_PAGE_SKIPPING) + { + /* + * Force aggressive mode, and disable skipping blocks using the + * visibility map (even those set all-frozen) + */ + vacrel->aggressive = true; + skipwithvm = false; + } + + vacrel->skipwithvm = skipwithvm; + + if (verbose) + { + if (vacrel->aggressive) + ereport(INFO, + (errmsg("aggressively vacuuming \"%s.%s.%s\"", + get_database_name(MyDatabaseId), + vacrel->relnamespace, vacrel->relname))); + else + ereport(INFO, + (errmsg("vacuuming \"%s.%s.%s\"", + get_database_name(MyDatabaseId), + vacrel->relnamespace, vacrel->relname))); + } /* * Allocate dead_items array memory using dead_items_alloc. This handles @@ -569,13 +536,13 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff. * Non-aggressive VACUUMs may advance them by any amount, or not at all. */ - Assert(vacrel->NewRelfrozenXid == OldestXmin || - TransactionIdPrecedesOrEquals(aggressive ? FreezeLimit : - vacrel->relfrozenxid, + Assert(vacrel->NewRelfrozenXid == vacrel->cutoffs.OldestXmin || + TransactionIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.FreezeLimit : + vacrel->cutoffs.relfrozenxid, vacrel->NewRelfrozenXid)); - Assert(vacrel->NewRelminMxid == OldestMxact || - MultiXactIdPrecedesOrEquals(aggressive ? MultiXactCutoff : - vacrel->relminmxid, + Assert(vacrel->NewRelminMxid == vacrel->cutoffs.OldestMxact || + MultiXactIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.MultiXactCutoff : + vacrel->cutoffs.relminmxid, vacrel->NewRelminMxid)); if (vacrel->skippedallvis) { @@ -584,7 +551,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * chose to skip an all-visible page range. The state that tracks new * values will have missed unfrozen XIDs from the pages we skipped. */ - Assert(!aggressive); + Assert(!vacrel->aggressive); vacrel->NewRelfrozenXid = InvalidTransactionId; vacrel->NewRelminMxid = InvalidMultiXactId; } @@ -669,14 +636,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, * implies aggressive. Produce distinct output for the corner * case all the same, just in case. */ - if (aggressive) + if (vacrel->aggressive) msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); else msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n"); } else { - if (aggressive) + if (vacrel->aggressive) msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n"); else msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n"); @@ -702,20 +669,23 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, _("tuples missed: %lld dead from %u pages not removed due to cleanup lock contention\n"), (long long) vacrel->missed_dead_tuples, vacrel->missed_dead_pages); - diff = (int32) (ReadNextTransactionId() - OldestXmin); + diff = (int32) (ReadNextTransactionId() - + vacrel->cutoffs.OldestXmin); appendStringInfo(&buf, _("removable cutoff: %u, which was %d XIDs old when operation ended\n"), - OldestXmin, diff); + vacrel->cutoffs.OldestXmin, diff); if (frozenxid_updated) { - diff = (int32) (vacrel->NewRelfrozenXid - vacrel->relfrozenxid); + diff = (int32) (vacrel->NewRelfrozenXid - + vacrel->cutoffs.relfrozenxid); appendStringInfo(&buf, _("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"), vacrel->NewRelfrozenXid, diff); } if (minmulti_updated) { - diff = (int32) (vacrel->NewRelminMxid - vacrel->relminmxid); + diff = (int32) (vacrel->NewRelminMxid - + vacrel->cutoffs.relminmxid); appendStringInfo(&buf, _("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"), vacrel->NewRelminMxid, diff); @@ -1610,7 +1580,7 @@ lazy_scan_prune(LVRelState *vacrel, offnum <= maxoff; offnum = OffsetNumberNext(offnum)) { - bool tuple_totally_frozen; + bool totally_frozen; /* * Set the offset number so that we can display it along with any @@ -1666,7 +1636,8 @@ lazy_scan_prune(LVRelState *vacrel, * since heap_page_prune() looked. Handle that here by restarting. * (See comments at the top of function for a full explanation.) */ - res = HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf); + res = HeapTupleSatisfiesVacuum(&tuple, vacrel->cutoffs.OldestXmin, + buf); if (unlikely(res == HEAPTUPLE_DEAD)) goto retry; @@ -1723,7 +1694,8 @@ lazy_scan_prune(LVRelState *vacrel, * that everyone sees it as committed? */ xmin = HeapTupleHeaderGetXmin(tuple.t_data); - if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin)) + if (!TransactionIdPrecedes(xmin, + vacrel->cutoffs.OldestXmin)) { prunestate->all_visible = false; break; @@ -1774,13 +1746,8 @@ lazy_scan_prune(LVRelState *vacrel, prunestate->hastup = true; /* page makes rel truncation unsafe */ /* Tuple with storage -- consider need to freeze */ - if (heap_prepare_freeze_tuple(tuple.t_data, - vacrel->relfrozenxid, - vacrel->relminmxid, - vacrel->FreezeLimit, - vacrel->MultiXactCutoff, - &frozen[tuples_frozen], - &tuple_totally_frozen, + if (heap_prepare_freeze_tuple(tuple.t_data, &vacrel->cutoffs, + &frozen[tuples_frozen], &totally_frozen, &NewRelfrozenXid, &NewRelminMxid)) { /* Save prepared freeze plan for later */ @@ -1791,7 +1758,7 @@ lazy_scan_prune(LVRelState *vacrel, * If tuple is not frozen (and not about to become frozen) then caller * had better not go on to set this page's VM bit */ - if (!tuple_totally_frozen) + if (!totally_frozen) prunestate->all_frozen = false; } @@ -1817,7 +1784,8 @@ lazy_scan_prune(LVRelState *vacrel, vacrel->frozen_pages++; /* Execute all freeze plans for page as a single atomic action */ - heap_freeze_execute_prepared(vacrel->rel, buf, vacrel->FreezeLimit, + heap_freeze_execute_prepared(vacrel->rel, buf, + vacrel->cutoffs.FreezeLimit, frozen, tuples_frozen); } @@ -1972,9 +1940,7 @@ lazy_scan_noprune(LVRelState *vacrel, *hastup = true; /* page prevents rel truncation */ tupleheader = (HeapTupleHeader) PageGetItem(page, itemid); - if (heap_tuple_would_freeze(tupleheader, - vacrel->FreezeLimit, - vacrel->MultiXactCutoff, + if (heap_tuple_would_freeze(tupleheader, &vacrel->cutoffs, &NewRelfrozenXid, &NewRelminMxid)) { /* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */ @@ -2010,7 +1976,8 @@ lazy_scan_noprune(LVRelState *vacrel, tuple.t_len = ItemIdGetLength(itemid); tuple.t_tableOid = RelationGetRelid(vacrel->rel); - switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf)) + switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->cutoffs.OldestXmin, + buf)) { case HEAPTUPLE_DELETE_IN_PROGRESS: case HEAPTUPLE_LIVE: @@ -2274,6 +2241,7 @@ static bool lazy_vacuum_all_indexes(LVRelState *vacrel) { bool allindexes = true; + double old_live_tuples = vacrel->rel->rd_rel->reltuples; Assert(vacrel->nindexes > 0); Assert(vacrel->do_index_vacuuming); @@ -2297,9 +2265,9 @@ lazy_vacuum_all_indexes(LVRelState *vacrel) Relation indrel = vacrel->indrels[idx]; IndexBulkDeleteResult *istat = vacrel->indstats[idx]; - vacrel->indstats[idx] = - lazy_vacuum_one_index(indrel, istat, vacrel->old_live_tuples, - vacrel); + vacrel->indstats[idx] = lazy_vacuum_one_index(indrel, istat, + old_live_tuples, + vacrel); if (lazy_check_wraparound_failsafe(vacrel)) { @@ -2312,7 +2280,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel) else { /* Outsource everything to parallel variant */ - parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, vacrel->old_live_tuples, + parallel_vacuum_bulkdel_all_indexes(vacrel->pvs, old_live_tuples, vacrel->num_index_scans); /* @@ -2581,15 +2549,11 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer, static bool lazy_check_wraparound_failsafe(LVRelState *vacrel) { - Assert(TransactionIdIsNormal(vacrel->relfrozenxid)); - Assert(MultiXactIdIsValid(vacrel->relminmxid)); - /* Don't warn more than once per VACUUM */ if (vacrel->failsafe_active) return true; - if (unlikely(vacuum_xid_failsafe_check(vacrel->relfrozenxid, - vacrel->relminmxid))) + if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs))) { vacrel->failsafe_active = true; @@ -3246,7 +3210,8 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf, tuple.t_len = ItemIdGetLength(itemid); tuple.t_tableOid = RelationGetRelid(vacrel->rel); - switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf)) + switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->cutoffs.OldestXmin, + buf)) { case HEAPTUPLE_LIVE: { @@ -3265,7 +3230,8 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf, * that everyone sees it as committed? */ xmin = HeapTupleHeaderGetXmin(tuple.t_data); - if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin)) + if (!TransactionIdPrecedes(xmin, + vacrel->cutoffs.OldestXmin)) { all_visible = false; *all_frozen = false; diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 19b95b8241283..e3b634280949e 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2813,14 +2813,11 @@ ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members) * As the fraction of the member space currently in use grows, we become * more aggressive in clamping this value. That not only causes autovacuum * to ramp up, but also makes any manual vacuums the user issues more - * aggressive. This happens because vacuum_set_xid_limits() clamps the - * freeze table and the minimum freeze age based on the effective + * aggressive. This happens because vacuum_get_cutoffs() will clamp the + * freeze table and the minimum freeze age cutoffs based on the effective * autovacuum_multixact_freeze_max_age this function returns. In the worst * case, we'll claim the freeze_max_age to zero, and every vacuum of any - * table will try to freeze every multixact. - * - * It's possible that these thresholds should be user-tunable, but for now - * we keep it simple. + * table will freeze every multixact. */ int MultiXactMemberFreezeThreshold(void) diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 8966b75bd11e2..2a5fc2c280572 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -826,10 +826,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY; TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY; VacuumParams params; - TransactionId OldestXmin, - FreezeXid; - MultiXactId OldestMxact, - MultiXactCutoff; + struct VacuumCutoffs cutoffs; bool use_sort; double num_tuples = 0, tups_vacuumed = 0, @@ -918,23 +915,24 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, * not to be aggressive about this. */ memset(¶ms, 0, sizeof(VacuumParams)); - vacuum_set_xid_limits(OldHeap, ¶ms, &OldestXmin, &OldestMxact, - &FreezeXid, &MultiXactCutoff); + vacuum_get_cutoffs(OldHeap, ¶ms, &cutoffs); /* * FreezeXid will become the table's new relfrozenxid, and that mustn't go * backwards, so take the max. */ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && - TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) - FreezeXid = OldHeap->rd_rel->relfrozenxid; + TransactionIdPrecedes(cutoffs.FreezeLimit, + OldHeap->rd_rel->relfrozenxid)) + cutoffs.FreezeLimit = OldHeap->rd_rel->relfrozenxid; /* * MultiXactCutoff, similarly, shouldn't go backwards either. */ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && - MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) - MultiXactCutoff = OldHeap->rd_rel->relminmxid; + MultiXactIdPrecedes(cutoffs.MultiXactCutoff, + OldHeap->rd_rel->relminmxid)) + cutoffs.MultiXactCutoff = OldHeap->rd_rel->relminmxid; /* * Decide whether to use an indexscan or seqscan-and-optional-sort to scan @@ -973,13 +971,14 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, * values (e.g. because the AM doesn't use freezing). */ table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, - OldestXmin, &FreezeXid, &MultiXactCutoff, + cutoffs.OldestXmin, &cutoffs.FreezeLimit, + &cutoffs.MultiXactCutoff, &num_tuples, &tups_vacuumed, &tups_recently_dead); /* return selected values to caller, get set as relfrozenxid/minmxid */ - *pFreezeXid = FreezeXid; - *pCutoffMulti = MultiXactCutoff; + *pFreezeXid = cutoffs.FreezeLimit; + *pCutoffMulti = cutoffs.MultiXactCutoff; /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */ NewHeap->rd_toastoid = InvalidOid; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 293b84bbca89b..ba965b8c7b4b2 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -907,34 +907,20 @@ get_all_vacuum_rels(int options) } /* - * vacuum_set_xid_limits() -- compute OldestXmin and freeze cutoff points + * vacuum_get_cutoffs() -- compute OldestXmin and freeze cutoff points * * The target relation and VACUUM parameters are our inputs. * - * Our output parameters are: - * - OldestXmin is the Xid below which tuples deleted by any xact (that - * committed) should be considered DEAD, not just RECENTLY_DEAD. - * - OldestMxact is the Mxid below which MultiXacts are definitely not - * seen as visible by any running transaction. - * - FreezeLimit is the Xid below which all Xids are definitely frozen or - * removed during aggressive vacuums. - * - MultiXactCutoff is the value below which all MultiXactIds are definitely - * removed from Xmax during aggressive vacuums. + * Output parameters are the cutoffs that VACUUM caller should use. * * Return value indicates if vacuumlazy.c caller should make its VACUUM * operation aggressive. An aggressive VACUUM must advance relfrozenxid up to * FreezeLimit (at a minimum), and relminmxid up to MultiXactCutoff (at a * minimum). - * - * OldestXmin and OldestMxact are the most recent values that can ever be - * passed to vac_update_relstats() as frozenxid and minmulti arguments by our - * vacuumlazy.c caller later on. These values should be passed when it turns - * out that VACUUM will leave no unfrozen XIDs/MXIDs behind in the table. */ bool -vacuum_set_xid_limits(Relation rel, const VacuumParams *params, - TransactionId *OldestXmin, MultiXactId *OldestMxact, - TransactionId *FreezeLimit, MultiXactId *MultiXactCutoff) +vacuum_get_cutoffs(Relation rel, const VacuumParams *params, + struct VacuumCutoffs *cutoffs) { int freeze_min_age, multixact_freeze_min_age, @@ -954,6 +940,10 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, freeze_table_age = params->freeze_table_age; multixact_freeze_table_age = params->multixact_freeze_table_age; + /* Set pg_class fields in cutoffs */ + cutoffs->relfrozenxid = rel->rd_rel->relfrozenxid; + cutoffs->relminmxid = rel->rd_rel->relminmxid; + /* * Acquire OldestXmin. * @@ -965,14 +955,14 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, * that only one vacuum process can be working on a particular table at * any time, and that each vacuum is always an independent transaction. */ - *OldestXmin = GetOldestNonRemovableTransactionId(rel); + cutoffs->OldestXmin = GetOldestNonRemovableTransactionId(rel); if (OldSnapshotThresholdActive()) { TransactionId limit_xmin; TimestampTz limit_ts; - if (TransactionIdLimitedForOldSnapshots(*OldestXmin, rel, + if (TransactionIdLimitedForOldSnapshots(cutoffs->OldestXmin, rel, &limit_xmin, &limit_ts)) { /* @@ -982,20 +972,48 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, * frequency), but would still be a significant improvement. */ SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin); - *OldestXmin = limit_xmin; + cutoffs->OldestXmin = limit_xmin; } } - Assert(TransactionIdIsNormal(*OldestXmin)); + Assert(TransactionIdIsNormal(cutoffs->OldestXmin)); /* Acquire OldestMxact */ - *OldestMxact = GetOldestMultiXactId(); - Assert(MultiXactIdIsValid(*OldestMxact)); + cutoffs->OldestMxact = GetOldestMultiXactId(); + Assert(MultiXactIdIsValid(cutoffs->OldestMxact)); /* Acquire next XID/next MXID values used to apply age-based settings */ nextXID = ReadNextTransactionId(); nextMXID = ReadNextMultiXactId(); + /* + * Also compute the multixact age for which freezing is urgent. This is + * normally autovacuum_multixact_freeze_max_age, but may be less if we are + * short of multixact member space. + */ + effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold(); + + /* + * Almost ready to set freeze output parameters; check if OldestXmin or + * OldestMxact are held back to an unsafe degree before we start on that + */ + safeOldestXmin = nextXID - autovacuum_freeze_max_age; + if (!TransactionIdIsNormal(safeOldestXmin)) + safeOldestXmin = FirstNormalTransactionId; + safeOldestMxact = nextMXID - effective_multixact_freeze_max_age; + if (safeOldestMxact < FirstMultiXactId) + safeOldestMxact = FirstMultiXactId; + if (TransactionIdPrecedes(cutoffs->OldestXmin, safeOldestXmin)) + ereport(WARNING, + (errmsg("cutoff for removing and freezing tuples is far in the past"), + errhint("Close open transactions soon to avoid wraparound problems.\n" + "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + if (MultiXactIdPrecedes(cutoffs->OldestMxact, safeOldestMxact)) + ereport(WARNING, + (errmsg("cutoff for freezing multixacts is far in the past"), + errhint("Close open transactions soon to avoid wraparound problems.\n" + "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + /* * Determine the minimum freeze age to use: as specified by the caller, or * vacuum_freeze_min_age, but in any case not more than half @@ -1008,19 +1026,12 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, Assert(freeze_min_age >= 0); /* Compute FreezeLimit, being careful to generate a normal XID */ - *FreezeLimit = nextXID - freeze_min_age; - if (!TransactionIdIsNormal(*FreezeLimit)) - *FreezeLimit = FirstNormalTransactionId; + cutoffs->FreezeLimit = nextXID - freeze_min_age; + if (!TransactionIdIsNormal(cutoffs->FreezeLimit)) + cutoffs->FreezeLimit = FirstNormalTransactionId; /* FreezeLimit must always be <= OldestXmin */ - if (TransactionIdPrecedes(*OldestXmin, *FreezeLimit)) - *FreezeLimit = *OldestXmin; - - /* - * Compute the multixact age for which freezing is urgent. This is - * normally autovacuum_multixact_freeze_max_age, but may be less if we are - * short of multixact member space. - */ - effective_multixact_freeze_max_age = MultiXactMemberFreezeThreshold(); + if (TransactionIdPrecedes(cutoffs->OldestXmin, cutoffs->FreezeLimit)) + cutoffs->FreezeLimit = cutoffs->OldestXmin; /* * Determine the minimum multixact freeze age to use: as specified by @@ -1035,33 +1046,12 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, Assert(multixact_freeze_min_age >= 0); /* Compute MultiXactCutoff, being careful to generate a valid value */ - *MultiXactCutoff = nextMXID - multixact_freeze_min_age; - if (*MultiXactCutoff < FirstMultiXactId) - *MultiXactCutoff = FirstMultiXactId; + cutoffs->MultiXactCutoff = nextMXID - multixact_freeze_min_age; + if (cutoffs->MultiXactCutoff < FirstMultiXactId) + cutoffs->MultiXactCutoff = FirstMultiXactId; /* MultiXactCutoff must always be <= OldestMxact */ - if (MultiXactIdPrecedes(*OldestMxact, *MultiXactCutoff)) - *MultiXactCutoff = *OldestMxact; - - /* - * Done setting output parameters; check if OldestXmin or OldestMxact are - * held back to an unsafe degree in passing - */ - safeOldestXmin = nextXID - autovacuum_freeze_max_age; - if (!TransactionIdIsNormal(safeOldestXmin)) - safeOldestXmin = FirstNormalTransactionId; - safeOldestMxact = nextMXID - effective_multixact_freeze_max_age; - if (safeOldestMxact < FirstMultiXactId) - safeOldestMxact = FirstMultiXactId; - if (TransactionIdPrecedes(*OldestXmin, safeOldestXmin)) - ereport(WARNING, - (errmsg("cutoff for removing and freezing tuples is far in the past"), - errhint("Close open transactions soon to avoid wraparound problems.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); - if (MultiXactIdPrecedes(*OldestMxact, safeOldestMxact)) - ereport(WARNING, - (errmsg("cutoff for freezing multixacts is far in the past"), - errhint("Close open transactions soon to avoid wraparound problems.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + if (MultiXactIdPrecedes(cutoffs->OldestMxact, cutoffs->MultiXactCutoff)) + cutoffs->MultiXactCutoff = cutoffs->OldestMxact; /* * Finally, figure out if caller needs to do an aggressive VACUUM or not. @@ -1113,13 +1103,13 @@ vacuum_set_xid_limits(Relation rel, const VacuumParams *params, * mechanism to determine if its table's relfrozenxid and relminmxid are now * dangerously far in the past. * - * Input parameters are the target relation's relfrozenxid and relminmxid. - * * When we return true, VACUUM caller triggers the failsafe. */ bool -vacuum_xid_failsafe_check(TransactionId relfrozenxid, MultiXactId relminmxid) +vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs) { + TransactionId relfrozenxid = cutoffs->relfrozenxid; + MultiXactId relminmxid = cutoffs->relminmxid; TransactionId xid_skip_limit; MultiXactId multi_skip_limit; int skip_index_vacuum; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 810baaf9d0833..53eb011766b50 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -38,6 +38,7 @@ typedef struct BulkInsertStateData *BulkInsertState; struct TupleTableSlot; +struct VacuumCutoffs; #define MaxLockTupleMode LockTupleExclusive @@ -178,8 +179,7 @@ extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple, - TransactionId relfrozenxid, TransactionId relminmxid, - TransactionId cutoff_xid, TransactionId cutoff_multi, + const struct VacuumCutoffs *cutoffs, HeapTupleFreeze *frz, bool *totally_frozen, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out); @@ -188,9 +188,9 @@ extern void heap_freeze_execute_prepared(Relation rel, Buffer buffer, HeapTupleFreeze *tuples, int ntuples); extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId relfrozenxid, TransactionId relminmxid, - TransactionId cutoff_xid, TransactionId cutoff_multi); -extern bool heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid, - MultiXactId cutoff_multi, + TransactionId FreezeLimit, TransactionId MultiXactCutoff); +extern bool heap_tuple_would_freeze(HeapTupleHeader tuple, + const struct VacuumCutoffs *cutoffs, TransactionId *relfrozenxid_out, MultiXactId *relminmxid_out); extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 4d1ef405c22d7..1320ee6db66ff 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -1634,7 +1634,7 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator) * in that index's order; if false and OldIndex is InvalidOid, no sorting is * performed * - OldIndex - see use_sort - * - OldestXmin - computed by vacuum_set_xid_limits(), even when + * - OldestXmin - computed by vacuum_get_cutoffs(), even when * not needed for the relation's AM * - *xid_cutoff - ditto * - *multi_cutoff - ditto diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 4e4bc26a8bf43..2f274f2bec3a9 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -235,6 +235,45 @@ typedef struct VacuumParams int nworkers; } VacuumParams; +/* + * VacuumCutoffs is immutable state that describes the cutoffs used by VACUUM. + * Established at the beginning of each VACUUM operation. + */ +struct VacuumCutoffs +{ + /* + * Existing pg_class fields at start of VACUUM + */ + TransactionId relfrozenxid; + MultiXactId relminmxid; + + /* + * OldestXmin is the Xid below which tuples deleted by any xact (that + * committed) should be considered DEAD, not just RECENTLY_DEAD. + * + * OldestMxact is the Mxid below which MultiXacts are definitely not seen + * as visible by any running transaction. + * + * OldestXmin and OldestMxact are also the most recent values that can + * ever be passed to vac_update_relstats() as frozenxid and minmulti + * arguments at the end of VACUUM. These same values should be passed + * when it turns out that VACUUM will leave no unfrozen XIDs/MXIDs behind + * in the table. + */ + TransactionId OldestXmin; + MultiXactId OldestMxact; + + /* + * FreezeLimit is the Xid below which all Xids are definitely frozen or + * removed in pages VACUUM scans and cleanup locks. + * + * MultiXactCutoff is the value below which all MultiXactIds are + * definitely removed from Xmax in pages VACUUM scans and cleanup locks. + */ + TransactionId FreezeLimit; + MultiXactId MultiXactCutoff; +}; + /* * VacDeadItems stores TIDs whose index tuples are deleted by index vacuuming. */ @@ -286,13 +325,9 @@ extern void vac_update_relstats(Relation relation, bool *frozenxid_updated, bool *minmulti_updated, bool in_outer_xact); -extern bool vacuum_set_xid_limits(Relation rel, const VacuumParams *params, - TransactionId *OldestXmin, - MultiXactId *OldestMxact, - TransactionId *FreezeLimit, - MultiXactId *MultiXactCutoff); -extern bool vacuum_xid_failsafe_check(TransactionId relfrozenxid, - MultiXactId relminmxid); +extern bool vacuum_get_cutoffs(Relation rel, const VacuumParams *params, + struct VacuumCutoffs *cutoffs); +extern bool vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs); extern void vac_update_datfrozenxid(void); extern void vacuum_delay_point(void); extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,