#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"
*/
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;
*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
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
{
* 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));
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;
}
/*
- * 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
*/
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);
{
/* 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)
*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
* 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
* 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
*/
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;
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
{
}
}
+ /*
+ * 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.
*
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)
{
/*
* 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;
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)
{
* 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;
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)
{
* 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));
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
}
else
{
- freeze_xmax = false;
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
*relfrozenxid_out = xid;
}
{
/* 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;
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;
}
/*
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
* 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 */
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))
{
if (MultiXactIdPrecedes(multi, *relminmxid_out))
*relminmxid_out = multi;
/* heap_prepare_freeze_tuple always freezes pg_upgrade'd xmax */
- would_freeze = true;
+ freeze = true;
}
else
{
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,
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);
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;
}
/*
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) */
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;
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;
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
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) */
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;
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;
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
* 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)
{
* 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;
}
* 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");
_("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);
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
* 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;
* 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;
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 */
* 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;
}
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);
}
*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) */
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:
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);
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))
{
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);
/*
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;
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:
{
* 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;
* 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)
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,
* 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
* 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;
}
/*
- * 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,
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.
*
* 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))
{
/*
* 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
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
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.
* 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;
typedef struct BulkInsertStateData *BulkInsertState;
struct TupleTableSlot;
+struct VacuumCutoffs;
#define MaxLockTupleMode LockTupleExclusive
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);
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);
* 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
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.
*/
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,