Derive freeze cutoff from nextXID, not OldestXmin.

Before now, the cutoffs that VACUUM used to determine which XIDs/MXIDs
to freeze were determined at the start of each VACUUM by taking related
cutoffs that represent which XIDs/MXIDs VACUUM should treat as still
running, and subtracting an XID/MXID age based value controlled by GUCs
like vacuum_freeze_min_age.  The FreezeLimit cutoff (XID freeze cutoff)
was derived by subtracting an XID age value from OldestXmin, while the
MultiXactCutoff cutoff (MXID freeze cutoff) was derived by subtracting
an MXID age value from OldestMxact.  This approach didn't match the
approach used nearby to determine whether this VACUUM operation should
be an aggressive VACUUM or not.

VACUUM now uses the standard approach instead: it subtracts the same
age-based values from next XID/next MXID (rather than subtracting from
OldestXmin/OldestMxact).  This approach is simpler and more uniform.
Most of the time it will have only a negligible impact on how and when
VACUUM freezes.  It will occasionally make VACUUM more robust in the
event of problems caused by long running transaction.  These are cases
where OldestXmin and OldestMxact are held back by so much that they
attain an age that is a significant fraction of the value of age-based
settings like vacuum_freeze_min_age.

There is no principled reason why freezing should be affected in any way
by the presence of a long-running transaction -- at least not before the
point that the OldestXmin and OldestMxact limits used by each VACUUM
operation attain an age that makes it unsafe to freeze some of the
XIDs/MXIDs whose age exceeds the value of the relevant age-based
settings.  The new approach should at least make freezing degrade more
gracefully than before, even in the most extreme cases.

Author: Peter Geoghegan <pg@bowt.ie>
Reviewed-By: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/CAH2-WzkOv5CEeyOO=c91XnT5WBR_0gii0Wn5UbZhJ=4TTykDYg@mail.gmail.com
This commit is contained in:
Peter Geoghegan 2022-08-31 11:37:35 -07:00
parent 483ac64761
commit c3ffa731a5
3 changed files with 91 additions and 111 deletions

View File

@ -360,8 +360,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
*/ */
aggressive = vacuum_set_xid_limits(rel, aggressive = vacuum_set_xid_limits(rel,
params->freeze_min_age, params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age, params->multixact_freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_table_age, params->multixact_freeze_table_age,
&OldestXmin, &OldestMxact, &OldestXmin, &OldestMxact,
&FreezeLimit, &MultiXactCutoff); &FreezeLimit, &MultiXactCutoff);

View File

@ -956,24 +956,25 @@ get_all_vacuum_rels(int options)
bool bool
vacuum_set_xid_limits(Relation rel, vacuum_set_xid_limits(Relation rel,
int freeze_min_age, int freeze_min_age,
int freeze_table_age,
int multixact_freeze_min_age, int multixact_freeze_min_age,
int freeze_table_age,
int multixact_freeze_table_age, int multixact_freeze_table_age,
TransactionId *oldestXmin, TransactionId *oldestXmin,
MultiXactId *oldestMxact, MultiXactId *oldestMxact,
TransactionId *freezeLimit, TransactionId *freezeLimit,
MultiXactId *multiXactCutoff) MultiXactId *multiXactCutoff)
{ {
int freezemin; TransactionId nextXID,
int mxid_freezemin; safeOldestXmin,
aggressiveXIDCutoff;
MultiXactId nextMXID,
safeOldestMxact,
aggressiveMXIDCutoff;
int effective_multixact_freeze_max_age; int effective_multixact_freeze_max_age;
TransactionId limit;
TransactionId safeLimit;
MultiXactId mxactLimit;
MultiXactId safeMxactLimit;
int freezetable;
/* /*
* Acquire oldestXmin.
*
* We can always ignore processes running lazy vacuum. This is because we * We can always ignore processes running lazy vacuum. This is because we
* use these values only for deciding which tuples we must keep in the * use these values only for deciding which tuples we must keep in the
* tables. Since lazy vacuum doesn't write its XID anywhere (usually no * tables. Since lazy vacuum doesn't write its XID anywhere (usually no
@ -1005,44 +1006,32 @@ vacuum_set_xid_limits(Relation rel,
Assert(TransactionIdIsNormal(*oldestXmin)); Assert(TransactionIdIsNormal(*oldestXmin));
/* Acquire oldestMxact */
*oldestMxact = GetOldestMultiXactId();
Assert(MultiXactIdIsValid(*oldestMxact));
/* Acquire next XID/next MXID values used to apply age-based settings */
nextXID = ReadNextTransactionId();
nextMXID = ReadNextMultiXactId();
/* /*
* 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
* autovacuum_freeze_max_age, so that autovacuums to prevent XID * autovacuum_freeze_max_age, so that autovacuums to prevent XID
* wraparound won't occur too frequently. * wraparound won't occur too frequently.
*/ */
freezemin = freeze_min_age; if (freeze_min_age < 0)
if (freezemin < 0) freeze_min_age = vacuum_freeze_min_age;
freezemin = vacuum_freeze_min_age; freeze_min_age = Min(freeze_min_age, autovacuum_freeze_max_age / 2);
freezemin = Min(freezemin, autovacuum_freeze_max_age / 2); Assert(freeze_min_age >= 0);
Assert(freezemin >= 0);
/* /* Compute freezeLimit, being careful to generate a normal XID */
* Compute the cutoff XID, being careful not to generate a "permanent" XID *freezeLimit = nextXID - freeze_min_age;
*/ if (!TransactionIdIsNormal(*freezeLimit))
limit = *oldestXmin - freezemin; *freezeLimit = FirstNormalTransactionId;
if (!TransactionIdIsNormal(limit)) /* freezeLimit must always be <= oldestXmin */
limit = FirstNormalTransactionId; if (TransactionIdPrecedes(*oldestXmin, *freezeLimit))
*freezeLimit = *oldestXmin;
/*
* If oldestXmin is very far back (in practice, more than
* autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
* freeze age of zero.
*/
safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age;
if (!TransactionIdIsNormal(safeLimit))
safeLimit = FirstNormalTransactionId;
if (TransactionIdPrecedes(limit, safeLimit))
{
ereport(WARNING,
(errmsg("oldest xmin 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.")));
limit = *oldestXmin;
}
*freezeLimit = limit;
/* /*
* Compute the multixact age for which freezing is urgent. This is * Compute the multixact age for which freezing is urgent. This is
@ -1057,93 +1046,83 @@ vacuum_set_xid_limits(Relation rel,
* than half effective_multixact_freeze_max_age, so that autovacuums to * than half effective_multixact_freeze_max_age, so that autovacuums to
* prevent MultiXact wraparound won't occur too frequently. * prevent MultiXact wraparound won't occur too frequently.
*/ */
mxid_freezemin = multixact_freeze_min_age; if (multixact_freeze_min_age < 0)
if (mxid_freezemin < 0) multixact_freeze_min_age = vacuum_multixact_freeze_min_age;
mxid_freezemin = vacuum_multixact_freeze_min_age; multixact_freeze_min_age = Min(multixact_freeze_min_age,
mxid_freezemin = Min(mxid_freezemin, effective_multixact_freeze_max_age / 2);
effective_multixact_freeze_max_age / 2); Assert(multixact_freeze_min_age >= 0);
Assert(mxid_freezemin >= 0);
/* Remember for caller */ /* Compute multiXactCutoff, being careful to generate a valid value */
*oldestMxact = GetOldestMultiXactId(); *multiXactCutoff = nextMXID - multixact_freeze_min_age;
if (*multiXactCutoff < FirstMultiXactId)
/* compute the cutoff multi, being careful to generate a valid value */ *multiXactCutoff = FirstMultiXactId;
mxactLimit = *oldestMxact - mxid_freezemin; /* multiXactCutoff must always be <= oldestMxact */
if (mxactLimit < FirstMultiXactId) if (MultiXactIdPrecedes(*oldestMxact, *multiXactCutoff))
mxactLimit = FirstMultiXactId; *multiXactCutoff = *oldestMxact;
safeMxactLimit =
ReadNextMultiXactId() - effective_multixact_freeze_max_age;
if (safeMxactLimit < FirstMultiXactId)
safeMxactLimit = FirstMultiXactId;
if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit))
{
ereport(WARNING,
(errmsg("oldest multixact is far in the past"),
errhint("Close open transactions with multixacts soon to avoid wraparound problems.")));
/* Use the safe limit, unless an older mxact is still running */
if (MultiXactIdPrecedes(*oldestMxact, safeMxactLimit))
mxactLimit = *oldestMxact;
else
mxactLimit = safeMxactLimit;
}
*multiXactCutoff = mxactLimit;
/* /*
* Done setting output parameters; just need to figure out if caller needs * Done setting output parameters; check if oldestXmin or oldestMxact are
* to do an aggressive VACUUM or not. * 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.
* *
* Determine the table freeze age to use: as specified by the caller, or * Determine the table freeze age to use: as specified by the caller, or
* vacuum_freeze_table_age, but in any case not more than * the value of the vacuum_freeze_table_age GUC, but in any case not more
* autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly * than autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly
* VACUUM schedule, the nightly VACUUM gets a chance to freeze tuples * VACUUM schedule, the nightly VACUUM gets a chance to freeze XIDs before
* before anti-wraparound autovacuum is launched. * anti-wraparound autovacuum is launched.
*/ */
freezetable = freeze_table_age; if (freeze_table_age < 0)
if (freezetable < 0) freeze_table_age = vacuum_freeze_table_age;
freezetable = vacuum_freeze_table_age; freeze_table_age = Min(freeze_table_age, autovacuum_freeze_max_age * 0.95);
freezetable = Min(freezetable, autovacuum_freeze_max_age * 0.95); Assert(freeze_table_age >= 0);
Assert(freezetable >= 0); aggressiveXIDCutoff = nextXID - freeze_table_age;
if (!TransactionIdIsNormal(aggressiveXIDCutoff))
/* aggressiveXIDCutoff = FirstNormalTransactionId;
* Compute XID limit causing an aggressive vacuum, being careful not to
* generate a "permanent" XID
*/
limit = ReadNextTransactionId() - freezetable;
if (!TransactionIdIsNormal(limit))
limit = FirstNormalTransactionId;
if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid, if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
limit)) aggressiveXIDCutoff))
return true; return true;
/* /*
* Similar to the above, determine the table freeze age to use for * Similar to the above, determine the table freeze age to use for
* multixacts: as specified by the caller, or * multixacts: as specified by the caller, or the value of the
* vacuum_multixact_freeze_table_age, but in any case not more than * vacuum_multixact_freeze_table_age GUC, but in any case not more than
* autovacuum_multixact_freeze_table_age * 0.95, so that if you have e.g. * effective_multixact_freeze_max_age * 0.95, so that if you have e.g.
* nightly VACUUM schedule, the nightly VACUUM gets a chance to freeze * nightly VACUUM schedule, the nightly VACUUM gets a chance to freeze
* multixacts before anti-wraparound autovacuum is launched. * multixacts before anti-wraparound autovacuum is launched.
*/ */
freezetable = multixact_freeze_table_age; if (multixact_freeze_table_age < 0)
if (freezetable < 0) multixact_freeze_table_age = vacuum_multixact_freeze_table_age;
freezetable = vacuum_multixact_freeze_table_age; multixact_freeze_table_age =
freezetable = Min(freezetable, Min(multixact_freeze_table_age,
effective_multixact_freeze_max_age * 0.95); effective_multixact_freeze_max_age * 0.95);
Assert(freezetable >= 0); Assert(multixact_freeze_table_age >= 0);
aggressiveMXIDCutoff = nextMXID - multixact_freeze_table_age;
/* if (aggressiveMXIDCutoff < FirstMultiXactId)
* Compute MultiXact limit causing an aggressive vacuum, being careful to aggressiveMXIDCutoff = FirstMultiXactId;
* generate a valid MultiXact value
*/
mxactLimit = ReadNextMultiXactId() - freezetable;
if (mxactLimit < FirstMultiXactId)
mxactLimit = FirstMultiXactId;
if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid, if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
mxactLimit)) aggressiveMXIDCutoff))
return true; return true;
/* Non-aggressive VACUUM */
return false; return false;
} }

View File

@ -287,8 +287,9 @@ extern void vac_update_relstats(Relation relation,
bool *minmulti_updated, bool *minmulti_updated,
bool in_outer_xact); bool in_outer_xact);
extern bool vacuum_set_xid_limits(Relation rel, extern bool vacuum_set_xid_limits(Relation rel,
int freeze_min_age, int freeze_table_age, int freeze_min_age,
int multixact_freeze_min_age, int multixact_freeze_min_age,
int freeze_table_age,
int multixact_freeze_table_age, int multixact_freeze_table_age,
TransactionId *oldestXmin, TransactionId *oldestXmin,
MultiXactId *oldestMxact, MultiXactId *oldestMxact,