Refactor how VACUUM passes around its XID cutoffs.
Use a dedicated struct for the XID/MXID cutoffs used by VACUUM, such as FreezeLimit and OldestXmin. This state is initialized in vacuum.c, and then passed around by code from vacuumlazy.c to heapam.c freezing related routines. The new convention is that everybody works off of the same cutoff state, which is passed around via pointers to const. Also simplify some of the logic for dealing with frozen xmin in heap_prepare_freeze_tuple: add dedicated "xmin_already_frozen" state to clearly distinguish xmin XIDs that we're going to freeze from those that were already frozen from before. That way the routine's xmin handling code is symmetrical with the existing xmax handling code. This is preparation for an upcoming commit that will add page level freezing. Also refactor the control flow within FreezeMultiXactId(), while adding stricter sanity checks. We now test OldestXmin directly, instead of using FreezeLimit as an inexact proxy for OldestXmin. This is further preparation for the page level freezing work, which will make the function's caller cede control of page level freezing to the function where appropriate (where heap_prepare_freeze_tuple sees a tuple that happens to contain a MultiXactId in its xmax). Author: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Jeff Davis <pgsql@j-davis.com> Discussion: https://postgr.es/m/CAH2-WznS9TxXmz2_=SY+SyJyDFbiOftKofM9=aDo68BbXNBUMA@mail.gmail.com
This commit is contained in:
parent
e42e312430
commit
4ce3afb82e
@ -52,6 +52,7 @@
|
|||||||
#include "access/xloginsert.h"
|
#include "access/xloginsert.h"
|
||||||
#include "access/xlogutils.h"
|
#include "access/xlogutils.h"
|
||||||
#include "catalog/catalog.h"
|
#include "catalog/catalog.h"
|
||||||
|
#include "commands/vacuum.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
#include "port/atomics.h"
|
#include "port/atomics.h"
|
||||||
@ -6121,12 +6122,10 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
|
|||||||
*/
|
*/
|
||||||
static TransactionId
|
static TransactionId
|
||||||
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
||||||
TransactionId relfrozenxid, TransactionId relminmxid,
|
const struct VacuumCutoffs *cutoffs, uint16 *flags,
|
||||||
TransactionId cutoff_xid, MultiXactId cutoff_multi,
|
TransactionId *mxid_oldest_xid_out)
|
||||||
uint16 *flags, TransactionId *mxid_oldest_xid_out)
|
|
||||||
{
|
{
|
||||||
TransactionId xid = InvalidTransactionId;
|
TransactionId newxmax = InvalidTransactionId;
|
||||||
int i;
|
|
||||||
MultiXactMember *members;
|
MultiXactMember *members;
|
||||||
int nmembers;
|
int nmembers;
|
||||||
bool need_replace;
|
bool need_replace;
|
||||||
@ -6149,12 +6148,12 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
*flags |= FRM_INVALIDATE_XMAX;
|
*flags |= FRM_INVALIDATE_XMAX;
|
||||||
return InvalidTransactionId;
|
return InvalidTransactionId;
|
||||||
}
|
}
|
||||||
else if (MultiXactIdPrecedes(multi, relminmxid))
|
else if (MultiXactIdPrecedes(multi, cutoffs->relminmxid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("found multixact %u from before relminmxid %u",
|
errmsg_internal("found multixact %u from before relminmxid %u",
|
||||||
multi, relminmxid)));
|
multi, cutoffs->relminmxid)));
|
||||||
else if (MultiXactIdPrecedes(multi, cutoff_multi))
|
else if (MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This old multi cannot possibly have members still running, but
|
* This old multi cannot possibly have members still running, but
|
||||||
@ -6167,39 +6166,39 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("multixact %u from before cutoff %u found to be still running",
|
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))
|
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
|
||||||
{
|
{
|
||||||
*flags |= FRM_INVALIDATE_XMAX;
|
*flags |= FRM_INVALIDATE_XMAX;
|
||||||
xid = InvalidTransactionId;
|
newxmax = InvalidTransactionId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* replace multi by update xid */
|
/* replace multi with single XID for its updater */
|
||||||
xid = MultiXactIdGetUpdateXid(multi, t_infomask);
|
newxmax = MultiXactIdGetUpdateXid(multi, t_infomask);
|
||||||
|
|
||||||
/* wasn't only a lock, xid needs to be valid */
|
/* 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,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("found update xid %u from before relfrozenxid %u",
|
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,
|
* If the new xmax xid is older than OldestXmin, it has to have
|
||||||
* otherwise the tuple would have gotten pruned away.
|
* 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,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(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;
|
*flags |= FRM_INVALIDATE_XMAX;
|
||||||
xid = InvalidTransactionId;
|
newxmax = InvalidTransactionId;
|
||||||
}
|
}
|
||||||
else
|
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
|
* Don't push back mxid_oldest_xid_out using FRM_RETURN_IS_XID Xid, or
|
||||||
* when no Xids will remain
|
* when no Xids will remain
|
||||||
*/
|
*/
|
||||||
return xid;
|
return newxmax;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This multixact might have or might not have members still running, but
|
* Some member(s) of this Multi may be below FreezeLimit xid cutoff, so we
|
||||||
* 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
|
|
||||||
* need to walk the whole members array to figure out what to do, if
|
* need to walk the whole members array to figure out what to do, if
|
||||||
* anything.
|
* anything.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
nmembers =
|
nmembers =
|
||||||
GetMultiXactIdMembers(multi, &members, false,
|
GetMultiXactIdMembers(multi, &members, false,
|
||||||
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask));
|
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask));
|
||||||
@ -6232,12 +6228,15 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
return InvalidTransactionId;
|
return InvalidTransactionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* is there anything older than the cutoff? */
|
|
||||||
need_replace = false;
|
need_replace = false;
|
||||||
temp_xid_out = *mxid_oldest_xid_out; /* init for FRM_NOOP */
|
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;
|
need_replace = true;
|
||||||
break;
|
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
|
* keep the existing MultiXactId as-is, avoiding a more expensive second
|
||||||
* pass over the multi
|
* pass over the multi
|
||||||
*/
|
*/
|
||||||
@ -6275,110 +6274,97 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
update_committed = false;
|
update_committed = false;
|
||||||
temp_xid_out = *mxid_oldest_xid_out; /* init for FRM_RETURN_IS_MULTI */
|
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++)
|
||||||
{
|
{
|
||||||
/*
|
TransactionId xid = members[i].xid;
|
||||||
* Determine whether to keep this member or ignore it.
|
MultiXactStatus mstatus = members[i].status;
|
||||||
*/
|
|
||||||
if (ISUPDATE_from_mxstatus(members[i].status))
|
Assert(!TransactionIdPrecedes(xid, cutoffs->relfrozenxid));
|
||||||
|
|
||||||
|
if (!ISUPDATE_from_mxstatus(mstatus))
|
||||||
{
|
{
|
||||||
TransactionId txid = members[i].xid;
|
|
||||||
|
|
||||||
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)));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It's an update; should we keep it? If the transaction is known
|
* Locker XID (not updater XID). We only keep lockers that are
|
||||||
* aborted or crashed then it's okay to ignore it, otherwise not.
|
* still running.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
if (TransactionIdIsCurrentTransactionId(txid) ||
|
if (TransactionIdIsCurrentTransactionId(xid) ||
|
||||||
TransactionIdIsInProgress(txid))
|
TransactionIdIsInProgress(xid))
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Not in progress, not committed -- must be aborted or
|
|
||||||
* crashed; we can ignore it.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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)));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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).
|
|
||||||
*/
|
|
||||||
if (TransactionIdIsValid(update_xid))
|
|
||||||
{
|
{
|
||||||
newmembers[nnewmembers++] = members[i];
|
newmembers[nnewmembers++] = members[i];
|
||||||
if (TransactionIdPrecedes(members[i].xid, temp_xid_out))
|
has_lockers = true;
|
||||||
temp_xid_out = members[i].xid;
|
|
||||||
|
/*
|
||||||
|
* Cannot possibly be older than VACUUM's OldestXmin, so we
|
||||||
|
* don't need a NewRelfrozenXid step here
|
||||||
|
*/
|
||||||
|
Assert(TransactionIdPrecedesOrEquals(cutoffs->OldestXmin, 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))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 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.)
|
||||||
|
*/
|
||||||
|
update_committed = true;
|
||||||
|
update_xid = xid;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* We only keep lockers if they are still running */
|
/*
|
||||||
if (TransactionIdIsCurrentTransactionId(members[i].xid) ||
|
* Not in progress, not committed -- must be aborted or crashed;
|
||||||
TransactionIdIsInProgress(members[i].xid))
|
* we can ignore it.
|
||||||
{
|
*/
|
||||||
/*
|
continue;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
pfree(members);
|
||||||
@ -6391,7 +6377,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
{
|
{
|
||||||
/* nothing worth keeping!? Tell caller to remove the whole thing */
|
/* nothing worth keeping!? Tell caller to remove the whole thing */
|
||||||
*flags |= FRM_INVALIDATE_XMAX;
|
*flags |= FRM_INVALIDATE_XMAX;
|
||||||
xid = InvalidTransactionId;
|
newxmax = InvalidTransactionId;
|
||||||
/* Don't push back mxid_oldest_xid_out -- no Xids will remain */
|
/* Don't push back mxid_oldest_xid_out -- no Xids will remain */
|
||||||
}
|
}
|
||||||
else if (TransactionIdIsValid(update_xid) && !has_lockers)
|
else if (TransactionIdIsValid(update_xid) && !has_lockers)
|
||||||
@ -6407,7 +6393,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
*flags |= FRM_RETURN_IS_XID;
|
*flags |= FRM_RETURN_IS_XID;
|
||||||
if (update_committed)
|
if (update_committed)
|
||||||
*flags |= FRM_MARK_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 */
|
/* Don't push back mxid_oldest_xid_out using FRM_RETURN_IS_XID Xid */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -6417,26 +6403,29 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
|
|||||||
* one, to set as new Xmax in the tuple. The oldest surviving member
|
* one, to set as new Xmax in the tuple. The oldest surviving member
|
||||||
* might push back mxid_oldest_xid_out.
|
* might push back mxid_oldest_xid_out.
|
||||||
*/
|
*/
|
||||||
xid = MultiXactIdCreateFromMembers(nnewmembers, newmembers);
|
newxmax = MultiXactIdCreateFromMembers(nnewmembers, newmembers);
|
||||||
*flags |= FRM_RETURN_IS_MULTI;
|
*flags |= FRM_RETURN_IS_MULTI;
|
||||||
*mxid_oldest_xid_out = temp_xid_out;
|
*mxid_oldest_xid_out = temp_xid_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(newmembers);
|
pfree(newmembers);
|
||||||
|
|
||||||
return xid;
|
return newxmax;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* heap_prepare_freeze_tuple
|
* heap_prepare_freeze_tuple
|
||||||
*
|
*
|
||||||
* Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
|
* 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
|
* 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
|
* WAL-log what caller needs to do for the tuple, and return true. Return
|
||||||
* is to be changed. In addition, set *totally_frozen to true if the tuple
|
* false if nothing can be changed about the tuple right now.
|
||||||
* will be totally frozen after these operations are performed and false if
|
*
|
||||||
* more freezing will eventually be required.
|
* 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
|
* VACUUM caller must assemble HeapTupleFreeze entries for every tuple that we
|
||||||
* returned true for when called. A later heap_freeze_execute_prepared call
|
* 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
|
* 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.
|
* 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.
|
* 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
|
* 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
|
* 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
|
bool
|
||||||
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
||||||
TransactionId relfrozenxid, TransactionId relminmxid,
|
const struct VacuumCutoffs *cutoffs,
|
||||||
TransactionId cutoff_xid, TransactionId cutoff_multi,
|
|
||||||
HeapTupleFreeze *frz, bool *totally_frozen,
|
HeapTupleFreeze *frz, bool *totally_frozen,
|
||||||
TransactionId *relfrozenxid_out,
|
TransactionId *relfrozenxid_out,
|
||||||
MultiXactId *relminmxid_out)
|
MultiXactId *relminmxid_out)
|
||||||
{
|
{
|
||||||
bool changed = false;
|
bool xmin_already_frozen = false,
|
||||||
bool xmax_already_frozen = false;
|
xmax_already_frozen = false;
|
||||||
bool xmin_frozen;
|
bool freeze_xmin = false,
|
||||||
bool freeze_xmax;
|
replace_xvac = false,
|
||||||
|
replace_xmax = false,
|
||||||
|
freeze_xmax = false;
|
||||||
TransactionId xid;
|
TransactionId xid;
|
||||||
|
|
||||||
frz->frzflags = 0;
|
frz->frzflags = 0;
|
||||||
@ -6485,37 +6469,29 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
frz->xmax = HeapTupleHeaderGetRawXmax(tuple);
|
frz->xmax = HeapTupleHeaderGetRawXmax(tuple);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Process xmin. xmin_frozen has two slightly different meanings: in the
|
* Process xmin, while keeping track of whether it's already frozen, or
|
||||||
* !XidIsNormal case, it means "the xmin doesn't need any freezing" (it's
|
* will become frozen when our freeze plan is executed by caller (could be
|
||||||
* already a permanent value), while in the block below it is set true to
|
* neither).
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
xid = HeapTupleHeaderGetXmin(tuple);
|
xid = HeapTupleHeaderGetXmin(tuple);
|
||||||
if (!TransactionIdIsNormal(xid))
|
if (!TransactionIdIsNormal(xid))
|
||||||
xmin_frozen = true;
|
xmin_already_frozen = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (TransactionIdPrecedes(xid, relfrozenxid))
|
if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("found xmin %u from before relfrozenxid %u",
|
errmsg_internal("found xmin %u from before relfrozenxid %u",
|
||||||
xid, relfrozenxid)));
|
xid, cutoffs->relfrozenxid)));
|
||||||
|
|
||||||
xmin_frozen = TransactionIdPrecedes(xid, cutoff_xid);
|
freeze_xmin = TransactionIdPrecedes(xid, cutoffs->FreezeLimit);
|
||||||
if (xmin_frozen)
|
if (freeze_xmin)
|
||||||
{
|
{
|
||||||
if (!TransactionIdDidCommit(xid))
|
if (!TransactionIdDidCommit(xid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
|
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
|
||||||
xid, cutoff_xid)));
|
xid, cutoffs->FreezeLimit)));
|
||||||
|
|
||||||
frz->t_infomask |= HEAP_XMIN_FROZEN;
|
|
||||||
changed = true;
|
|
||||||
}
|
}
|
||||||
else
|
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
|
* 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
|
* 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
|
* 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.
|
* 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;
|
uint16 flags;
|
||||||
TransactionId mxid_oldest_xid_out = *relfrozenxid_out;
|
TransactionId mxid_oldest_xid_out = *relfrozenxid_out;
|
||||||
|
|
||||||
newxmax = FreezeMultiXactId(xid, tuple->t_infomask,
|
newxmax = FreezeMultiXactId(xid, tuple->t_infomask, cutoffs,
|
||||||
relfrozenxid, relminmxid,
|
|
||||||
cutoff_xid, cutoff_multi,
|
|
||||||
&flags, &mxid_oldest_xid_out);
|
&flags, &mxid_oldest_xid_out);
|
||||||
|
|
||||||
freeze_xmax = (flags & FRM_INVALIDATE_XMAX);
|
|
||||||
|
|
||||||
if (flags & FRM_RETURN_IS_XID)
|
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
|
* Might have to ratchet back relfrozenxid_out here, though never
|
||||||
* relminmxid_out.
|
* relminmxid_out.
|
||||||
*/
|
*/
|
||||||
Assert(!freeze_xmax);
|
Assert(!TransactionIdPrecedes(newxmax, cutoffs->OldestXmin));
|
||||||
Assert(TransactionIdIsValid(newxmax));
|
|
||||||
if (TransactionIdPrecedes(newxmax, *relfrozenxid_out))
|
if (TransactionIdPrecedes(newxmax, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = newxmax;
|
*relfrozenxid_out = newxmax;
|
||||||
|
|
||||||
@ -6574,7 +6562,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
frz->xmax = newxmax;
|
frz->xmax = newxmax;
|
||||||
if (flags & FRM_MARK_COMMITTED)
|
if (flags & FRM_MARK_COMMITTED)
|
||||||
frz->t_infomask |= HEAP_XMAX_COMMITTED;
|
frz->t_infomask |= HEAP_XMAX_COMMITTED;
|
||||||
changed = true;
|
replace_xmax = true;
|
||||||
}
|
}
|
||||||
else if (flags & FRM_RETURN_IS_MULTI)
|
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
|
* Might have to ratchet back relfrozenxid_out here, though never
|
||||||
* relminmxid_out.
|
* relminmxid_out.
|
||||||
*/
|
*/
|
||||||
Assert(!freeze_xmax);
|
Assert(!MultiXactIdPrecedes(newxmax, cutoffs->OldestMxact));
|
||||||
Assert(MultiXactIdIsValid(newxmax));
|
|
||||||
Assert(!MultiXactIdPrecedes(newxmax, *relminmxid_out));
|
|
||||||
Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out,
|
Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out,
|
||||||
*relfrozenxid_out));
|
*relfrozenxid_out));
|
||||||
*relfrozenxid_out = mxid_oldest_xid_out;
|
*relfrozenxid_out = mxid_oldest_xid_out;
|
||||||
@ -6605,10 +6591,8 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
GetMultiXactIdHintBits(newxmax, &newbits, &newbits2);
|
GetMultiXactIdHintBits(newxmax, &newbits, &newbits2);
|
||||||
frz->t_infomask |= newbits;
|
frz->t_infomask |= newbits;
|
||||||
frz->t_infomask2 |= newbits2;
|
frz->t_infomask2 |= newbits2;
|
||||||
|
|
||||||
frz->xmax = newxmax;
|
frz->xmax = newxmax;
|
||||||
|
replace_xmax = true;
|
||||||
changed = true;
|
|
||||||
}
|
}
|
||||||
else if (flags & FRM_NOOP)
|
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
|
* Might have to ratchet back relminmxid_out, relfrozenxid_out, or
|
||||||
* both together.
|
* both together.
|
||||||
*/
|
*/
|
||||||
Assert(!freeze_xmax);
|
|
||||||
Assert(MultiXactIdIsValid(newxmax) && xid == newxmax);
|
Assert(MultiXactIdIsValid(newxmax) && xid == newxmax);
|
||||||
Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out,
|
Assert(TransactionIdPrecedesOrEquals(mxid_oldest_xid_out,
|
||||||
*relfrozenxid_out));
|
*relfrozenxid_out));
|
||||||
@ -6628,23 +6611,27 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Keeping nothing (neither an Xid nor a MultiXactId) in xmax.
|
* Freeze plan for tuple "freezes xmax" in the strictest sense:
|
||||||
* Won't have to ratchet back relminmxid_out or relfrozenxid_out.
|
* 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));
|
Assert(!TransactionIdIsValid(newxmax));
|
||||||
|
|
||||||
|
/* Will set t_infomask/t_infomask2 flags in freeze plan below */
|
||||||
|
freeze_xmax = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (TransactionIdIsNormal(xid))
|
else if (TransactionIdIsNormal(xid))
|
||||||
{
|
{
|
||||||
/* Raw xmax is normal XID */
|
/* Raw xmax is normal XID */
|
||||||
if (TransactionIdPrecedes(xid, relfrozenxid))
|
if (TransactionIdPrecedes(xid, cutoffs->relfrozenxid))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(errcode(ERRCODE_DATA_CORRUPTED),
|
||||||
errmsg_internal("found xmax %u from before relfrozenxid %u",
|
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
|
* If we freeze xmax, make absolutely sure that it's not an XID
|
||||||
@ -6663,7 +6650,6 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
freeze_xmax = false;
|
|
||||||
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = xid;
|
*relfrozenxid_out = xid;
|
||||||
}
|
}
|
||||||
@ -6672,19 +6658,41 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
{
|
{
|
||||||
/* Raw xmax is InvalidTransactionId XID */
|
/* Raw xmax is InvalidTransactionId XID */
|
||||||
Assert((tuple->t_infomask & HEAP_XMAX_IS_MULTI) == 0);
|
Assert((tuple->t_infomask & HEAP_XMAX_IS_MULTI) == 0);
|
||||||
freeze_xmax = false;
|
|
||||||
xmax_already_frozen = true;
|
xmax_already_frozen = true;
|
||||||
/* No need for relfrozenxid_out handling for already-frozen xmax */
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DATA_CORRUPTED),
|
(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)));
|
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)
|
if (freeze_xmax)
|
||||||
{
|
{
|
||||||
Assert(!xmax_already_frozen);
|
Assert(!xmax_already_frozen && !replace_xmax);
|
||||||
|
|
||||||
frz->xmax = InvalidTransactionId;
|
frz->xmax = InvalidTransactionId;
|
||||||
|
|
||||||
@ -6697,52 +6705,20 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
|||||||
frz->t_infomask |= HEAP_XMAX_INVALID;
|
frz->t_infomask |= HEAP_XMAX_INVALID;
|
||||||
frz->t_infomask2 &= ~HEAP_HOT_UPDATED;
|
frz->t_infomask2 &= ~HEAP_HOT_UPDATED;
|
||||||
frz->t_infomask2 &= ~HEAP_KEYS_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
|
* Determine if this tuple is already totally frozen, or will become
|
||||||
* we support having MOVED_OFF/MOVED_IN tuples in the database.
|
* totally frozen
|
||||||
*/
|
*/
|
||||||
if (tuple->t_infomask & HEAP_MOVED)
|
*totally_frozen = ((freeze_xmin || xmin_already_frozen) &&
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*totally_frozen = (xmin_frozen &&
|
|
||||||
(freeze_xmax || xmax_already_frozen));
|
(freeze_xmax || xmax_already_frozen));
|
||||||
return changed;
|
|
||||||
|
/* A "totally_frozen" tuple must not leave anything behind in xmax */
|
||||||
|
Assert(!*totally_frozen || !replace_xmax);
|
||||||
|
|
||||||
|
/* 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
|
bool
|
||||||
heap_freeze_tuple(HeapTupleHeader tuple,
|
heap_freeze_tuple(HeapTupleHeader tuple,
|
||||||
TransactionId relfrozenxid, TransactionId relminmxid,
|
TransactionId relfrozenxid, TransactionId relminmxid,
|
||||||
TransactionId cutoff_xid, TransactionId cutoff_multi)
|
TransactionId FreezeLimit, TransactionId MultiXactCutoff)
|
||||||
{
|
{
|
||||||
HeapTupleFreeze frz;
|
HeapTupleFreeze frz;
|
||||||
bool do_freeze;
|
bool do_freeze;
|
||||||
bool tuple_totally_frozen;
|
bool totally_frozen;
|
||||||
TransactionId relfrozenxid_out = cutoff_xid;
|
struct VacuumCutoffs cutoffs;
|
||||||
MultiXactId relminmxid_out = cutoff_multi;
|
TransactionId NewRelfrozenXid = FreezeLimit;
|
||||||
|
MultiXactId NewRelminMxid = MultiXactCutoff;
|
||||||
|
|
||||||
do_freeze = heap_prepare_freeze_tuple(tuple,
|
cutoffs.relfrozenxid = relfrozenxid;
|
||||||
relfrozenxid, relminmxid,
|
cutoffs.relminmxid = relminmxid;
|
||||||
cutoff_xid, cutoff_multi,
|
cutoffs.OldestXmin = FreezeLimit;
|
||||||
&frz, &tuple_totally_frozen,
|
cutoffs.OldestMxact = MultiXactCutoff;
|
||||||
&relfrozenxid_out, &relminmxid_out);
|
cutoffs.FreezeLimit = FreezeLimit;
|
||||||
|
cutoffs.MultiXactCutoff = MultiXactCutoff;
|
||||||
|
|
||||||
|
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
|
* 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.
|
* never freeze here, which makes tracking the oldest extant XID/MXID simple.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
heap_tuple_would_freeze(HeapTupleHeader tuple,
|
||||||
MultiXactId cutoff_multi,
|
const struct VacuumCutoffs *cutoffs,
|
||||||
TransactionId *relfrozenxid_out,
|
TransactionId *relfrozenxid_out,
|
||||||
MultiXactId *relminmxid_out)
|
MultiXactId *relminmxid_out)
|
||||||
{
|
{
|
||||||
TransactionId xid;
|
TransactionId xid;
|
||||||
MultiXactId multi;
|
MultiXactId multi;
|
||||||
bool would_freeze = false;
|
bool freeze = false;
|
||||||
|
|
||||||
/* First deal with xmin */
|
/* First deal with xmin */
|
||||||
xid = HeapTupleHeaderGetXmin(tuple);
|
xid = HeapTupleHeaderGetXmin(tuple);
|
||||||
if (TransactionIdIsNormal(xid))
|
if (TransactionIdIsNormal(xid))
|
||||||
{
|
{
|
||||||
|
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
|
||||||
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = xid;
|
*relfrozenxid_out = xid;
|
||||||
if (TransactionIdPrecedes(xid, cutoff_xid))
|
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now deal with xmax */
|
/* Now deal with xmax */
|
||||||
@ -7337,11 +7320,12 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
|||||||
|
|
||||||
if (TransactionIdIsNormal(xid))
|
if (TransactionIdIsNormal(xid))
|
||||||
{
|
{
|
||||||
|
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
|
||||||
/* xmax is a non-permanent XID */
|
/* xmax is a non-permanent XID */
|
||||||
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = xid;
|
*relfrozenxid_out = xid;
|
||||||
if (TransactionIdPrecedes(xid, cutoff_xid))
|
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
}
|
}
|
||||||
else if (!MultiXactIdIsValid(multi))
|
else if (!MultiXactIdIsValid(multi))
|
||||||
{
|
{
|
||||||
@ -7353,7 +7337,7 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
|||||||
if (MultiXactIdPrecedes(multi, *relminmxid_out))
|
if (MultiXactIdPrecedes(multi, *relminmxid_out))
|
||||||
*relminmxid_out = multi;
|
*relminmxid_out = multi;
|
||||||
/* heap_prepare_freeze_tuple always freezes pg_upgrade'd xmax */
|
/* heap_prepare_freeze_tuple always freezes pg_upgrade'd xmax */
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -7361,10 +7345,11 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
|||||||
MultiXactMember *members;
|
MultiXactMember *members;
|
||||||
int nmembers;
|
int nmembers;
|
||||||
|
|
||||||
|
Assert(MultiXactIdPrecedesOrEquals(cutoffs->relminmxid, multi));
|
||||||
if (MultiXactIdPrecedes(multi, *relminmxid_out))
|
if (MultiXactIdPrecedes(multi, *relminmxid_out))
|
||||||
*relminmxid_out = multi;
|
*relminmxid_out = multi;
|
||||||
if (MultiXactIdPrecedes(multi, cutoff_multi))
|
if (MultiXactIdPrecedes(multi, cutoffs->MultiXactCutoff))
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
|
|
||||||
/* need to check whether any member of the mxact is old */
|
/* need to check whether any member of the mxact is old */
|
||||||
nmembers = GetMultiXactIdMembers(multi, &members, false,
|
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++)
|
for (int i = 0; i < nmembers; i++)
|
||||||
{
|
{
|
||||||
xid = members[i].xid;
|
xid = members[i].xid;
|
||||||
Assert(TransactionIdIsNormal(xid));
|
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
|
||||||
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = xid;
|
*relfrozenxid_out = xid;
|
||||||
if (TransactionIdPrecedes(xid, cutoff_xid))
|
if (TransactionIdPrecedes(xid, cutoffs->FreezeLimit))
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
}
|
}
|
||||||
if (nmembers > 0)
|
if (nmembers > 0)
|
||||||
pfree(members);
|
pfree(members);
|
||||||
@ -7388,14 +7373,15 @@ heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
|||||||
xid = HeapTupleHeaderGetXvac(tuple);
|
xid = HeapTupleHeaderGetXvac(tuple);
|
||||||
if (TransactionIdIsNormal(xid))
|
if (TransactionIdIsNormal(xid))
|
||||||
{
|
{
|
||||||
|
Assert(TransactionIdPrecedesOrEquals(cutoffs->relfrozenxid, xid));
|
||||||
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
if (TransactionIdPrecedes(xid, *relfrozenxid_out))
|
||||||
*relfrozenxid_out = xid;
|
*relfrozenxid_out = xid;
|
||||||
/* heap_prepare_freeze_tuple always freezes xvac */
|
/* heap_prepare_freeze_tuple always freezes xvac */
|
||||||
would_freeze = true;
|
freeze = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return would_freeze;
|
return freeze;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -144,6 +144,10 @@ typedef struct LVRelState
|
|||||||
Relation *indrels;
|
Relation *indrels;
|
||||||
int nindexes;
|
int nindexes;
|
||||||
|
|
||||||
|
/* Buffer access strategy and parallel vacuum state */
|
||||||
|
BufferAccessStrategy bstrategy;
|
||||||
|
ParallelVacuumState *pvs;
|
||||||
|
|
||||||
/* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */
|
/* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */
|
||||||
bool aggressive;
|
bool aggressive;
|
||||||
/* Use visibility map to skip? (disabled by DISABLE_PAGE_SKIPPING) */
|
/* Use visibility map to skip? (disabled by DISABLE_PAGE_SKIPPING) */
|
||||||
@ -158,21 +162,9 @@ typedef struct LVRelState
|
|||||||
bool do_index_cleanup;
|
bool do_index_cleanup;
|
||||||
bool do_rel_truncate;
|
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 */
|
/* VACUUM operation's cutoffs for freezing and pruning */
|
||||||
TransactionId OldestXmin;
|
struct VacuumCutoffs cutoffs;
|
||||||
GlobalVisState *vistest;
|
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 */
|
/* Tracks oldest extant XID/MXID for setting relfrozenxid/relminmxid */
|
||||||
TransactionId NewRelfrozenXid;
|
TransactionId NewRelfrozenXid;
|
||||||
MultiXactId NewRelminMxid;
|
MultiXactId NewRelminMxid;
|
||||||
@ -314,14 +306,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
|
|||||||
LVRelState *vacrel;
|
LVRelState *vacrel;
|
||||||
bool verbose,
|
bool verbose,
|
||||||
instrument,
|
instrument,
|
||||||
aggressive,
|
|
||||||
skipwithvm,
|
skipwithvm,
|
||||||
frozenxid_updated,
|
frozenxid_updated,
|
||||||
minmulti_updated;
|
minmulti_updated;
|
||||||
TransactionId OldestXmin,
|
|
||||||
FreezeLimit;
|
|
||||||
MultiXactId OldestMxact,
|
|
||||||
MultiXactCutoff;
|
|
||||||
BlockNumber orig_rel_pages,
|
BlockNumber orig_rel_pages,
|
||||||
new_rel_pages,
|
new_rel_pages,
|
||||||
new_rel_allvisible;
|
new_rel_allvisible;
|
||||||
@ -353,27 +340,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
|
|||||||
pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
|
pgstat_progress_start_command(PROGRESS_COMMAND_VACUUM,
|
||||||
RelationGetRelid(rel));
|
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
|
* Setup error traceback support for ereport() first. The idea is to set
|
||||||
* up an error context callback to display additional information on any
|
* 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.arg = vacrel;
|
||||||
errcallback.previous = error_context_stack;
|
errcallback.previous = error_context_stack;
|
||||||
error_context_stack = &errcallback;
|
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 */
|
/* Set up high level stuff about rel and its indexes */
|
||||||
vacrel->rel = rel;
|
vacrel->rel = rel;
|
||||||
vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
|
vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
|
||||||
&vacrel->indrels);
|
&vacrel->indrels);
|
||||||
|
vacrel->bstrategy = bstrategy;
|
||||||
if (instrument && vacrel->nindexes > 0)
|
if (instrument && vacrel->nindexes > 0)
|
||||||
{
|
{
|
||||||
/* Copy index names used by instrumentation (not error reporting) */
|
/* 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->index_cleanup != VACOPTVALUE_UNSPECIFIED);
|
||||||
Assert(params->truncate != VACOPTVALUE_UNSPECIFIED &&
|
Assert(params->truncate != VACOPTVALUE_UNSPECIFIED &&
|
||||||
params->truncate != VACOPTVALUE_AUTO);
|
params->truncate != VACOPTVALUE_AUTO);
|
||||||
vacrel->aggressive = aggressive;
|
|
||||||
vacrel->skipwithvm = skipwithvm;
|
|
||||||
vacrel->failsafe_active = false;
|
vacrel->failsafe_active = false;
|
||||||
vacrel->consider_bypass_optimization = true;
|
vacrel->consider_bypass_optimization = true;
|
||||||
vacrel->do_index_vacuuming = true;
|
vacrel->do_index_vacuuming = true;
|
||||||
@ -459,11 +410,6 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
|
|||||||
Assert(params->index_cleanup == VACOPTVALUE_AUTO);
|
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) */
|
/* Initialize page counters explicitly (be tidy) */
|
||||||
vacrel->scanned_pages = 0;
|
vacrel->scanned_pages = 0;
|
||||||
vacrel->removed_pages = 0;
|
vacrel->removed_pages = 0;
|
||||||
@ -489,32 +435,53 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
|
|||||||
vacrel->missed_dead_tuples = 0;
|
vacrel->missed_dead_tuples = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine the extent of the blocks that we'll scan in lazy_scan_heap,
|
* Get cutoffs that determine which deleted tuples are considered DEAD,
|
||||||
* and finalize cutoffs used for freezing and pruning in lazy_scan_prune.
|
* 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
|
* We expect vistest will always make heap_page_prune remove any deleted
|
||||||
* tuple whose xmax is < OldestXmin. lazy_scan_prune must never become
|
* tuple whose xmax is < OldestXmin. lazy_scan_prune must never become
|
||||||
* confused about whether a tuple should be frozen or removed. (In the
|
* 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
|
* 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.)
|
* 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->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel);
|
||||||
vacrel->OldestXmin = OldestXmin;
|
|
||||||
vacrel->vistest = GlobalVisTestFor(rel);
|
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 */
|
/* Initialize state used to track oldest extant XID/MXID */
|
||||||
vacrel->NewRelfrozenXid = OldestXmin;
|
vacrel->NewRelfrozenXid = vacrel->cutoffs.OldestXmin;
|
||||||
vacrel->NewRelminMxid = OldestMxact;
|
vacrel->NewRelminMxid = vacrel->cutoffs.OldestMxact;
|
||||||
vacrel->skippedallvis = false;
|
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
|
* 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.
|
* value >= FreezeLimit, and relminmxid to a value >= MultiXactCutoff.
|
||||||
* Non-aggressive VACUUMs may advance them by any amount, or not at all.
|
* Non-aggressive VACUUMs may advance them by any amount, or not at all.
|
||||||
*/
|
*/
|
||||||
Assert(vacrel->NewRelfrozenXid == OldestXmin ||
|
Assert(vacrel->NewRelfrozenXid == vacrel->cutoffs.OldestXmin ||
|
||||||
TransactionIdPrecedesOrEquals(aggressive ? FreezeLimit :
|
TransactionIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.FreezeLimit :
|
||||||
vacrel->relfrozenxid,
|
vacrel->cutoffs.relfrozenxid,
|
||||||
vacrel->NewRelfrozenXid));
|
vacrel->NewRelfrozenXid));
|
||||||
Assert(vacrel->NewRelminMxid == OldestMxact ||
|
Assert(vacrel->NewRelminMxid == vacrel->cutoffs.OldestMxact ||
|
||||||
MultiXactIdPrecedesOrEquals(aggressive ? MultiXactCutoff :
|
MultiXactIdPrecedesOrEquals(vacrel->aggressive ? vacrel->cutoffs.MultiXactCutoff :
|
||||||
vacrel->relminmxid,
|
vacrel->cutoffs.relminmxid,
|
||||||
vacrel->NewRelminMxid));
|
vacrel->NewRelminMxid));
|
||||||
if (vacrel->skippedallvis)
|
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
|
* chose to skip an all-visible page range. The state that tracks new
|
||||||
* values will have missed unfrozen XIDs from the pages we skipped.
|
* values will have missed unfrozen XIDs from the pages we skipped.
|
||||||
*/
|
*/
|
||||||
Assert(!aggressive);
|
Assert(!vacrel->aggressive);
|
||||||
vacrel->NewRelfrozenXid = InvalidTransactionId;
|
vacrel->NewRelfrozenXid = InvalidTransactionId;
|
||||||
vacrel->NewRelminMxid = InvalidMultiXactId;
|
vacrel->NewRelminMxid = InvalidMultiXactId;
|
||||||
}
|
}
|
||||||
@ -669,14 +636,14 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
|
|||||||
* implies aggressive. Produce distinct output for the corner
|
* implies aggressive. Produce distinct output for the corner
|
||||||
* case all the same, just in case.
|
* 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");
|
msgfmt = _("automatic aggressive vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
|
||||||
else
|
else
|
||||||
msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
|
msgfmt = _("automatic vacuum to prevent wraparound of table \"%s.%s.%s\": index scans: %d\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (aggressive)
|
if (vacrel->aggressive)
|
||||||
msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
|
msgfmt = _("automatic aggressive vacuum of table \"%s.%s.%s\": index scans: %d\n");
|
||||||
else
|
else
|
||||||
msgfmt = _("automatic vacuum of table \"%s.%s.%s\": index scans: %d\n");
|
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"),
|
_("tuples missed: %lld dead from %u pages not removed due to cleanup lock contention\n"),
|
||||||
(long long) vacrel->missed_dead_tuples,
|
(long long) vacrel->missed_dead_tuples,
|
||||||
vacrel->missed_dead_pages);
|
vacrel->missed_dead_pages);
|
||||||
diff = (int32) (ReadNextTransactionId() - OldestXmin);
|
diff = (int32) (ReadNextTransactionId() -
|
||||||
|
vacrel->cutoffs.OldestXmin);
|
||||||
appendStringInfo(&buf,
|
appendStringInfo(&buf,
|
||||||
_("removable cutoff: %u, which was %d XIDs old when operation ended\n"),
|
_("removable cutoff: %u, which was %d XIDs old when operation ended\n"),
|
||||||
OldestXmin, diff);
|
vacrel->cutoffs.OldestXmin, diff);
|
||||||
if (frozenxid_updated)
|
if (frozenxid_updated)
|
||||||
{
|
{
|
||||||
diff = (int32) (vacrel->NewRelfrozenXid - vacrel->relfrozenxid);
|
diff = (int32) (vacrel->NewRelfrozenXid -
|
||||||
|
vacrel->cutoffs.relfrozenxid);
|
||||||
appendStringInfo(&buf,
|
appendStringInfo(&buf,
|
||||||
_("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"),
|
_("new relfrozenxid: %u, which is %d XIDs ahead of previous value\n"),
|
||||||
vacrel->NewRelfrozenXid, diff);
|
vacrel->NewRelfrozenXid, diff);
|
||||||
}
|
}
|
||||||
if (minmulti_updated)
|
if (minmulti_updated)
|
||||||
{
|
{
|
||||||
diff = (int32) (vacrel->NewRelminMxid - vacrel->relminmxid);
|
diff = (int32) (vacrel->NewRelminMxid -
|
||||||
|
vacrel->cutoffs.relminmxid);
|
||||||
appendStringInfo(&buf,
|
appendStringInfo(&buf,
|
||||||
_("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"),
|
_("new relminmxid: %u, which is %d MXIDs ahead of previous value\n"),
|
||||||
vacrel->NewRelminMxid, diff);
|
vacrel->NewRelminMxid, diff);
|
||||||
@ -1610,7 +1580,7 @@ retry:
|
|||||||
offnum <= maxoff;
|
offnum <= maxoff;
|
||||||
offnum = OffsetNumberNext(offnum))
|
offnum = OffsetNumberNext(offnum))
|
||||||
{
|
{
|
||||||
bool tuple_totally_frozen;
|
bool totally_frozen;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set the offset number so that we can display it along with any
|
* Set the offset number so that we can display it along with any
|
||||||
@ -1666,7 +1636,8 @@ retry:
|
|||||||
* since heap_page_prune() looked. Handle that here by restarting.
|
* since heap_page_prune() looked. Handle that here by restarting.
|
||||||
* (See comments at the top of function for a full explanation.)
|
* (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))
|
if (unlikely(res == HEAPTUPLE_DEAD))
|
||||||
goto retry;
|
goto retry;
|
||||||
@ -1723,7 +1694,8 @@ retry:
|
|||||||
* that everyone sees it as committed?
|
* that everyone sees it as committed?
|
||||||
*/
|
*/
|
||||||
xmin = HeapTupleHeaderGetXmin(tuple.t_data);
|
xmin = HeapTupleHeaderGetXmin(tuple.t_data);
|
||||||
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
|
if (!TransactionIdPrecedes(xmin,
|
||||||
|
vacrel->cutoffs.OldestXmin))
|
||||||
{
|
{
|
||||||
prunestate->all_visible = false;
|
prunestate->all_visible = false;
|
||||||
break;
|
break;
|
||||||
@ -1774,13 +1746,8 @@ retry:
|
|||||||
prunestate->hastup = true; /* page makes rel truncation unsafe */
|
prunestate->hastup = true; /* page makes rel truncation unsafe */
|
||||||
|
|
||||||
/* Tuple with storage -- consider need to freeze */
|
/* Tuple with storage -- consider need to freeze */
|
||||||
if (heap_prepare_freeze_tuple(tuple.t_data,
|
if (heap_prepare_freeze_tuple(tuple.t_data, &vacrel->cutoffs,
|
||||||
vacrel->relfrozenxid,
|
&frozen[tuples_frozen], &totally_frozen,
|
||||||
vacrel->relminmxid,
|
|
||||||
vacrel->FreezeLimit,
|
|
||||||
vacrel->MultiXactCutoff,
|
|
||||||
&frozen[tuples_frozen],
|
|
||||||
&tuple_totally_frozen,
|
|
||||||
&NewRelfrozenXid, &NewRelminMxid))
|
&NewRelfrozenXid, &NewRelminMxid))
|
||||||
{
|
{
|
||||||
/* Save prepared freeze plan for later */
|
/* Save prepared freeze plan for later */
|
||||||
@ -1791,7 +1758,7 @@ retry:
|
|||||||
* If tuple is not frozen (and not about to become frozen) then caller
|
* 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
|
* had better not go on to set this page's VM bit
|
||||||
*/
|
*/
|
||||||
if (!tuple_totally_frozen)
|
if (!totally_frozen)
|
||||||
prunestate->all_frozen = false;
|
prunestate->all_frozen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1817,7 +1784,8 @@ retry:
|
|||||||
vacrel->frozen_pages++;
|
vacrel->frozen_pages++;
|
||||||
|
|
||||||
/* Execute all freeze plans for page as a single atomic action */
|
/* 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);
|
frozen, tuples_frozen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1972,9 +1940,7 @@ lazy_scan_noprune(LVRelState *vacrel,
|
|||||||
|
|
||||||
*hastup = true; /* page prevents rel truncation */
|
*hastup = true; /* page prevents rel truncation */
|
||||||
tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
|
tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
|
||||||
if (heap_tuple_would_freeze(tupleheader,
|
if (heap_tuple_would_freeze(tupleheader, &vacrel->cutoffs,
|
||||||
vacrel->FreezeLimit,
|
|
||||||
vacrel->MultiXactCutoff,
|
|
||||||
&NewRelfrozenXid, &NewRelminMxid))
|
&NewRelfrozenXid, &NewRelminMxid))
|
||||||
{
|
{
|
||||||
/* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */
|
/* Tuple with XID < FreezeLimit (or MXID < MultiXactCutoff) */
|
||||||
@ -2010,7 +1976,8 @@ lazy_scan_noprune(LVRelState *vacrel,
|
|||||||
tuple.t_len = ItemIdGetLength(itemid);
|
tuple.t_len = ItemIdGetLength(itemid);
|
||||||
tuple.t_tableOid = RelationGetRelid(vacrel->rel);
|
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_DELETE_IN_PROGRESS:
|
||||||
case HEAPTUPLE_LIVE:
|
case HEAPTUPLE_LIVE:
|
||||||
@ -2274,6 +2241,7 @@ static bool
|
|||||||
lazy_vacuum_all_indexes(LVRelState *vacrel)
|
lazy_vacuum_all_indexes(LVRelState *vacrel)
|
||||||
{
|
{
|
||||||
bool allindexes = true;
|
bool allindexes = true;
|
||||||
|
double old_live_tuples = vacrel->rel->rd_rel->reltuples;
|
||||||
|
|
||||||
Assert(vacrel->nindexes > 0);
|
Assert(vacrel->nindexes > 0);
|
||||||
Assert(vacrel->do_index_vacuuming);
|
Assert(vacrel->do_index_vacuuming);
|
||||||
@ -2297,9 +2265,9 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
|
|||||||
Relation indrel = vacrel->indrels[idx];
|
Relation indrel = vacrel->indrels[idx];
|
||||||
IndexBulkDeleteResult *istat = vacrel->indstats[idx];
|
IndexBulkDeleteResult *istat = vacrel->indstats[idx];
|
||||||
|
|
||||||
vacrel->indstats[idx] =
|
vacrel->indstats[idx] = lazy_vacuum_one_index(indrel, istat,
|
||||||
lazy_vacuum_one_index(indrel, istat, vacrel->old_live_tuples,
|
old_live_tuples,
|
||||||
vacrel);
|
vacrel);
|
||||||
|
|
||||||
if (lazy_check_wraparound_failsafe(vacrel))
|
if (lazy_check_wraparound_failsafe(vacrel))
|
||||||
{
|
{
|
||||||
@ -2312,7 +2280,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Outsource everything to parallel variant */
|
/* 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);
|
vacrel->num_index_scans);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2581,15 +2549,11 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
|
|||||||
static bool
|
static bool
|
||||||
lazy_check_wraparound_failsafe(LVRelState *vacrel)
|
lazy_check_wraparound_failsafe(LVRelState *vacrel)
|
||||||
{
|
{
|
||||||
Assert(TransactionIdIsNormal(vacrel->relfrozenxid));
|
|
||||||
Assert(MultiXactIdIsValid(vacrel->relminmxid));
|
|
||||||
|
|
||||||
/* Don't warn more than once per VACUUM */
|
/* Don't warn more than once per VACUUM */
|
||||||
if (vacrel->failsafe_active)
|
if (vacrel->failsafe_active)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (unlikely(vacuum_xid_failsafe_check(vacrel->relfrozenxid,
|
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
|
||||||
vacrel->relminmxid)))
|
|
||||||
{
|
{
|
||||||
vacrel->failsafe_active = true;
|
vacrel->failsafe_active = true;
|
||||||
|
|
||||||
@ -3246,7 +3210,8 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
|
|||||||
tuple.t_len = ItemIdGetLength(itemid);
|
tuple.t_len = ItemIdGetLength(itemid);
|
||||||
tuple.t_tableOid = RelationGetRelid(vacrel->rel);
|
tuple.t_tableOid = RelationGetRelid(vacrel->rel);
|
||||||
|
|
||||||
switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->OldestXmin, buf))
|
switch (HeapTupleSatisfiesVacuum(&tuple, vacrel->cutoffs.OldestXmin,
|
||||||
|
buf))
|
||||||
{
|
{
|
||||||
case HEAPTUPLE_LIVE:
|
case HEAPTUPLE_LIVE:
|
||||||
{
|
{
|
||||||
@ -3265,7 +3230,8 @@ heap_page_is_all_visible(LVRelState *vacrel, Buffer buf,
|
|||||||
* that everyone sees it as committed?
|
* that everyone sees it as committed?
|
||||||
*/
|
*/
|
||||||
xmin = HeapTupleHeaderGetXmin(tuple.t_data);
|
xmin = HeapTupleHeaderGetXmin(tuple.t_data);
|
||||||
if (!TransactionIdPrecedes(xmin, vacrel->OldestXmin))
|
if (!TransactionIdPrecedes(xmin,
|
||||||
|
vacrel->cutoffs.OldestXmin))
|
||||||
{
|
{
|
||||||
all_visible = false;
|
all_visible = false;
|
||||||
*all_frozen = false;
|
*all_frozen = false;
|
||||||
|
@ -2813,14 +2813,11 @@ ReadMultiXactCounts(uint32 *multixacts, MultiXactOffset *members)
|
|||||||
* As the fraction of the member space currently in use grows, we become
|
* As the fraction of the member space currently in use grows, we become
|
||||||
* more aggressive in clamping this value. That not only causes autovacuum
|
* more aggressive in clamping this value. That not only causes autovacuum
|
||||||
* to ramp up, but also makes any manual vacuums the user issues more
|
* to ramp up, but also makes any manual vacuums the user issues more
|
||||||
* aggressive. This happens because vacuum_set_xid_limits() clamps the
|
* aggressive. This happens because vacuum_get_cutoffs() will clamp the
|
||||||
* freeze table and the minimum freeze age based on the effective
|
* freeze table and the minimum freeze age cutoffs based on the effective
|
||||||
* autovacuum_multixact_freeze_max_age this function returns. In the worst
|
* 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
|
* case, we'll claim the freeze_max_age to zero, and every vacuum of any
|
||||||
* table will try to freeze every multixact.
|
* table will freeze every multixact.
|
||||||
*
|
|
||||||
* It's possible that these thresholds should be user-tunable, but for now
|
|
||||||
* we keep it simple.
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
MultiXactMemberFreezeThreshold(void)
|
MultiXactMemberFreezeThreshold(void)
|
||||||
|
@ -826,10 +826,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
|
|||||||
TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
|
TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
|
||||||
TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY;
|
TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY;
|
||||||
VacuumParams params;
|
VacuumParams params;
|
||||||
TransactionId OldestXmin,
|
struct VacuumCutoffs cutoffs;
|
||||||
FreezeXid;
|
|
||||||
MultiXactId OldestMxact,
|
|
||||||
MultiXactCutoff;
|
|
||||||
bool use_sort;
|
bool use_sort;
|
||||||
double num_tuples = 0,
|
double num_tuples = 0,
|
||||||
tups_vacuumed = 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.
|
* not to be aggressive about this.
|
||||||
*/
|
*/
|
||||||
memset(¶ms, 0, sizeof(VacuumParams));
|
memset(¶ms, 0, sizeof(VacuumParams));
|
||||||
vacuum_set_xid_limits(OldHeap, ¶ms, &OldestXmin, &OldestMxact,
|
vacuum_get_cutoffs(OldHeap, ¶ms, &cutoffs);
|
||||||
&FreezeXid, &MultiXactCutoff);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FreezeXid will become the table's new relfrozenxid, and that mustn't go
|
* FreezeXid will become the table's new relfrozenxid, and that mustn't go
|
||||||
* backwards, so take the max.
|
* backwards, so take the max.
|
||||||
*/
|
*/
|
||||||
if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
|
if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
|
||||||
TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
|
TransactionIdPrecedes(cutoffs.FreezeLimit,
|
||||||
FreezeXid = OldHeap->rd_rel->relfrozenxid;
|
OldHeap->rd_rel->relfrozenxid))
|
||||||
|
cutoffs.FreezeLimit = OldHeap->rd_rel->relfrozenxid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MultiXactCutoff, similarly, shouldn't go backwards either.
|
* MultiXactCutoff, similarly, shouldn't go backwards either.
|
||||||
*/
|
*/
|
||||||
if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
|
if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
|
||||||
MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
|
MultiXactIdPrecedes(cutoffs.MultiXactCutoff,
|
||||||
MultiXactCutoff = OldHeap->rd_rel->relminmxid;
|
OldHeap->rd_rel->relminmxid))
|
||||||
|
cutoffs.MultiXactCutoff = OldHeap->rd_rel->relminmxid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
|
* 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).
|
* values (e.g. because the AM doesn't use freezing).
|
||||||
*/
|
*/
|
||||||
table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
|
table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
|
||||||
OldestXmin, &FreezeXid, &MultiXactCutoff,
|
cutoffs.OldestXmin, &cutoffs.FreezeLimit,
|
||||||
|
&cutoffs.MultiXactCutoff,
|
||||||
&num_tuples, &tups_vacuumed,
|
&num_tuples, &tups_vacuumed,
|
||||||
&tups_recently_dead);
|
&tups_recently_dead);
|
||||||
|
|
||||||
/* return selected values to caller, get set as relfrozenxid/minmxid */
|
/* return selected values to caller, get set as relfrozenxid/minmxid */
|
||||||
*pFreezeXid = FreezeXid;
|
*pFreezeXid = cutoffs.FreezeLimit;
|
||||||
*pCutoffMulti = MultiXactCutoff;
|
*pCutoffMulti = cutoffs.MultiXactCutoff;
|
||||||
|
|
||||||
/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
|
/* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
|
||||||
NewHeap->rd_toastoid = InvalidOid;
|
NewHeap->rd_toastoid = InvalidOid;
|
||||||
|
@ -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.
|
* The target relation and VACUUM parameters are our inputs.
|
||||||
*
|
*
|
||||||
* Our output parameters are:
|
* Output parameters are the cutoffs that VACUUM caller should use.
|
||||||
* - 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.
|
|
||||||
*
|
*
|
||||||
* Return value indicates if vacuumlazy.c caller should make its VACUUM
|
* Return value indicates if vacuumlazy.c caller should make its VACUUM
|
||||||
* operation aggressive. An aggressive VACUUM must advance relfrozenxid up to
|
* operation aggressive. An aggressive VACUUM must advance relfrozenxid up to
|
||||||
* FreezeLimit (at a minimum), and relminmxid up to MultiXactCutoff (at a
|
* FreezeLimit (at a minimum), and relminmxid up to MultiXactCutoff (at a
|
||||||
* minimum).
|
* 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
|
bool
|
||||||
vacuum_set_xid_limits(Relation rel, const VacuumParams *params,
|
vacuum_get_cutoffs(Relation rel, const VacuumParams *params,
|
||||||
TransactionId *OldestXmin, MultiXactId *OldestMxact,
|
struct VacuumCutoffs *cutoffs)
|
||||||
TransactionId *FreezeLimit, MultiXactId *MultiXactCutoff)
|
|
||||||
{
|
{
|
||||||
int freeze_min_age,
|
int freeze_min_age,
|
||||||
multixact_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;
|
freeze_table_age = params->freeze_table_age;
|
||||||
multixact_freeze_table_age = params->multixact_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.
|
* 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
|
* that only one vacuum process can be working on a particular table at
|
||||||
* any time, and that each vacuum is always an independent transaction.
|
* any time, and that each vacuum is always an independent transaction.
|
||||||
*/
|
*/
|
||||||
*OldestXmin = GetOldestNonRemovableTransactionId(rel);
|
cutoffs->OldestXmin = GetOldestNonRemovableTransactionId(rel);
|
||||||
|
|
||||||
if (OldSnapshotThresholdActive())
|
if (OldSnapshotThresholdActive())
|
||||||
{
|
{
|
||||||
TransactionId limit_xmin;
|
TransactionId limit_xmin;
|
||||||
TimestampTz limit_ts;
|
TimestampTz limit_ts;
|
||||||
|
|
||||||
if (TransactionIdLimitedForOldSnapshots(*OldestXmin, rel,
|
if (TransactionIdLimitedForOldSnapshots(cutoffs->OldestXmin, rel,
|
||||||
&limit_xmin, &limit_ts))
|
&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.
|
* frequency), but would still be a significant improvement.
|
||||||
*/
|
*/
|
||||||
SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
|
SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
|
||||||
*OldestXmin = limit_xmin;
|
cutoffs->OldestXmin = limit_xmin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(TransactionIdIsNormal(*OldestXmin));
|
Assert(TransactionIdIsNormal(cutoffs->OldestXmin));
|
||||||
|
|
||||||
/* Acquire OldestMxact */
|
/* Acquire OldestMxact */
|
||||||
*OldestMxact = GetOldestMultiXactId();
|
cutoffs->OldestMxact = GetOldestMultiXactId();
|
||||||
Assert(MultiXactIdIsValid(*OldestMxact));
|
Assert(MultiXactIdIsValid(cutoffs->OldestMxact));
|
||||||
|
|
||||||
/* Acquire next XID/next MXID values used to apply age-based settings */
|
/* Acquire next XID/next MXID values used to apply age-based settings */
|
||||||
nextXID = ReadNextTransactionId();
|
nextXID = ReadNextTransactionId();
|
||||||
nextMXID = ReadNextMultiXactId();
|
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
|
* 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
|
* 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);
|
Assert(freeze_min_age >= 0);
|
||||||
|
|
||||||
/* Compute FreezeLimit, being careful to generate a normal XID */
|
/* Compute FreezeLimit, being careful to generate a normal XID */
|
||||||
*FreezeLimit = nextXID - freeze_min_age;
|
cutoffs->FreezeLimit = nextXID - freeze_min_age;
|
||||||
if (!TransactionIdIsNormal(*FreezeLimit))
|
if (!TransactionIdIsNormal(cutoffs->FreezeLimit))
|
||||||
*FreezeLimit = FirstNormalTransactionId;
|
cutoffs->FreezeLimit = FirstNormalTransactionId;
|
||||||
/* FreezeLimit must always be <= OldestXmin */
|
/* FreezeLimit must always be <= OldestXmin */
|
||||||
if (TransactionIdPrecedes(*OldestXmin, *FreezeLimit))
|
if (TransactionIdPrecedes(cutoffs->OldestXmin, cutoffs->FreezeLimit))
|
||||||
*FreezeLimit = *OldestXmin;
|
cutoffs->FreezeLimit = cutoffs->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();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine the minimum multixact freeze age to use: as specified by
|
* 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);
|
Assert(multixact_freeze_min_age >= 0);
|
||||||
|
|
||||||
/* Compute MultiXactCutoff, being careful to generate a valid value */
|
/* Compute MultiXactCutoff, being careful to generate a valid value */
|
||||||
*MultiXactCutoff = nextMXID - multixact_freeze_min_age;
|
cutoffs->MultiXactCutoff = nextMXID - multixact_freeze_min_age;
|
||||||
if (*MultiXactCutoff < FirstMultiXactId)
|
if (cutoffs->MultiXactCutoff < FirstMultiXactId)
|
||||||
*MultiXactCutoff = FirstMultiXactId;
|
cutoffs->MultiXactCutoff = FirstMultiXactId;
|
||||||
/* MultiXactCutoff must always be <= OldestMxact */
|
/* MultiXactCutoff must always be <= OldestMxact */
|
||||||
if (MultiXactIdPrecedes(*OldestMxact, *MultiXactCutoff))
|
if (MultiXactIdPrecedes(cutoffs->OldestMxact, cutoffs->MultiXactCutoff))
|
||||||
*MultiXactCutoff = *OldestMxact;
|
cutoffs->MultiXactCutoff = cutoffs->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.")));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Finally, figure out if caller needs to do an aggressive VACUUM or not.
|
* 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
|
* mechanism to determine if its table's relfrozenxid and relminmxid are now
|
||||||
* dangerously far in the past.
|
* dangerously far in the past.
|
||||||
*
|
*
|
||||||
* Input parameters are the target relation's relfrozenxid and relminmxid.
|
|
||||||
*
|
|
||||||
* When we return true, VACUUM caller triggers the failsafe.
|
* When we return true, VACUUM caller triggers the failsafe.
|
||||||
*/
|
*/
|
||||||
bool
|
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;
|
TransactionId xid_skip_limit;
|
||||||
MultiXactId multi_skip_limit;
|
MultiXactId multi_skip_limit;
|
||||||
int skip_index_vacuum;
|
int skip_index_vacuum;
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
|
|
||||||
typedef struct BulkInsertStateData *BulkInsertState;
|
typedef struct BulkInsertStateData *BulkInsertState;
|
||||||
struct TupleTableSlot;
|
struct TupleTableSlot;
|
||||||
|
struct VacuumCutoffs;
|
||||||
|
|
||||||
#define MaxLockTupleMode LockTupleExclusive
|
#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 void heap_inplace_update(Relation relation, HeapTuple tuple);
|
||||||
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
|
||||||
TransactionId relfrozenxid, TransactionId relminmxid,
|
const struct VacuumCutoffs *cutoffs,
|
||||||
TransactionId cutoff_xid, TransactionId cutoff_multi,
|
|
||||||
HeapTupleFreeze *frz, bool *totally_frozen,
|
HeapTupleFreeze *frz, bool *totally_frozen,
|
||||||
TransactionId *relfrozenxid_out,
|
TransactionId *relfrozenxid_out,
|
||||||
MultiXactId *relminmxid_out);
|
MultiXactId *relminmxid_out);
|
||||||
@ -188,9 +188,9 @@ extern void heap_freeze_execute_prepared(Relation rel, Buffer buffer,
|
|||||||
HeapTupleFreeze *tuples, int ntuples);
|
HeapTupleFreeze *tuples, int ntuples);
|
||||||
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
|
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
|
||||||
TransactionId relfrozenxid, TransactionId relminmxid,
|
TransactionId relfrozenxid, TransactionId relminmxid,
|
||||||
TransactionId cutoff_xid, TransactionId cutoff_multi);
|
TransactionId FreezeLimit, TransactionId MultiXactCutoff);
|
||||||
extern bool heap_tuple_would_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
|
extern bool heap_tuple_would_freeze(HeapTupleHeader tuple,
|
||||||
MultiXactId cutoff_multi,
|
const struct VacuumCutoffs *cutoffs,
|
||||||
TransactionId *relfrozenxid_out,
|
TransactionId *relfrozenxid_out,
|
||||||
MultiXactId *relminmxid_out);
|
MultiXactId *relminmxid_out);
|
||||||
extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
|
extern bool heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple);
|
||||||
|
@ -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
|
* in that index's order; if false and OldIndex is InvalidOid, no sorting is
|
||||||
* performed
|
* performed
|
||||||
* - OldIndex - see use_sort
|
* - 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
|
* not needed for the relation's AM
|
||||||
* - *xid_cutoff - ditto
|
* - *xid_cutoff - ditto
|
||||||
* - *multi_cutoff - ditto
|
* - *multi_cutoff - ditto
|
||||||
|
@ -235,6 +235,45 @@ typedef struct VacuumParams
|
|||||||
int nworkers;
|
int nworkers;
|
||||||
} VacuumParams;
|
} 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.
|
* 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 *frozenxid_updated,
|
||||||
bool *minmulti_updated,
|
bool *minmulti_updated,
|
||||||
bool in_outer_xact);
|
bool in_outer_xact);
|
||||||
extern bool vacuum_set_xid_limits(Relation rel, const VacuumParams *params,
|
extern bool vacuum_get_cutoffs(Relation rel, const VacuumParams *params,
|
||||||
TransactionId *OldestXmin,
|
struct VacuumCutoffs *cutoffs);
|
||||||
MultiXactId *OldestMxact,
|
extern bool vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs);
|
||||||
TransactionId *FreezeLimit,
|
|
||||||
MultiXactId *MultiXactCutoff);
|
|
||||||
extern bool vacuum_xid_failsafe_check(TransactionId relfrozenxid,
|
|
||||||
MultiXactId relminmxid);
|
|
||||||
extern void vac_update_datfrozenxid(void);
|
extern void vac_update_datfrozenxid(void);
|
||||||
extern void vacuum_delay_point(void);
|
extern void vacuum_delay_point(void);
|
||||||
extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
|
extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user