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,
params->freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_min_age,
params->freeze_table_age,
params->multixact_freeze_table_age,
&OldestXmin, &OldestMxact,
&FreezeLimit, &MultiXactCutoff);

View File

@ -956,24 +956,25 @@ get_all_vacuum_rels(int options)
bool
vacuum_set_xid_limits(Relation rel,
int freeze_min_age,
int freeze_table_age,
int multixact_freeze_min_age,
int freeze_table_age,
int multixact_freeze_table_age,
TransactionId *oldestXmin,
MultiXactId *oldestMxact,
TransactionId *freezeLimit,
MultiXactId *multiXactCutoff)
{
int freezemin;
int mxid_freezemin;
TransactionId nextXID,
safeOldestXmin,
aggressiveXIDCutoff;
MultiXactId nextMXID,
safeOldestMxact,
aggressiveMXIDCutoff;
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
* 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
@ -1005,44 +1006,32 @@ vacuum_set_xid_limits(Relation rel,
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
* vacuum_freeze_min_age, but in any case not more than half
* autovacuum_freeze_max_age, so that autovacuums to prevent XID
* wraparound won't occur too frequently.
*/
freezemin = freeze_min_age;
if (freezemin < 0)
freezemin = vacuum_freeze_min_age;
freezemin = Min(freezemin, autovacuum_freeze_max_age / 2);
Assert(freezemin >= 0);
if (freeze_min_age < 0)
freeze_min_age = vacuum_freeze_min_age;
freeze_min_age = Min(freeze_min_age, autovacuum_freeze_max_age / 2);
Assert(freeze_min_age >= 0);
/*
* Compute the cutoff XID, being careful not to generate a "permanent" XID
*/
limit = *oldestXmin - freezemin;
if (!TransactionIdIsNormal(limit))
limit = FirstNormalTransactionId;
/*
* 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 freezeLimit, being careful to generate a normal XID */
*freezeLimit = nextXID - freeze_min_age;
if (!TransactionIdIsNormal(*freezeLimit))
*freezeLimit = FirstNormalTransactionId;
/* freezeLimit must always be <= oldestXmin */
if (TransactionIdPrecedes(*oldestXmin, *freezeLimit))
*freezeLimit = *oldestXmin;
/*
* 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
* prevent MultiXact wraparound won't occur too frequently.
*/
mxid_freezemin = multixact_freeze_min_age;
if (mxid_freezemin < 0)
mxid_freezemin = vacuum_multixact_freeze_min_age;
mxid_freezemin = Min(mxid_freezemin,
effective_multixact_freeze_max_age / 2);
Assert(mxid_freezemin >= 0);
if (multixact_freeze_min_age < 0)
multixact_freeze_min_age = vacuum_multixact_freeze_min_age;
multixact_freeze_min_age = Min(multixact_freeze_min_age,
effective_multixact_freeze_max_age / 2);
Assert(multixact_freeze_min_age >= 0);
/* Remember for caller */
*oldestMxact = GetOldestMultiXactId();
/* compute the cutoff multi, being careful to generate a valid value */
mxactLimit = *oldestMxact - mxid_freezemin;
if (mxactLimit < FirstMultiXactId)
mxactLimit = FirstMultiXactId;
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;
/* Compute multiXactCutoff, being careful to generate a valid value */
*multiXactCutoff = nextMXID - multixact_freeze_min_age;
if (*multiXactCutoff < FirstMultiXactId)
*multiXactCutoff = FirstMultiXactId;
/* multiXactCutoff must always be <= oldestMxact */
if (MultiXactIdPrecedes(*oldestMxact, *multiXactCutoff))
*multiXactCutoff = *oldestMxact;
/*
* Done setting output parameters; just need to figure out if caller needs
* to do an aggressive VACUUM or not.
* 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.
*
* Determine the table freeze age to use: as specified by the caller, or
* vacuum_freeze_table_age, but in any case not more 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
* before anti-wraparound autovacuum is launched.
* the value of the vacuum_freeze_table_age GUC, but in any case not more
* 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 XIDs before
* anti-wraparound autovacuum is launched.
*/
freezetable = freeze_table_age;
if (freezetable < 0)
freezetable = vacuum_freeze_table_age;
freezetable = Min(freezetable, autovacuum_freeze_max_age * 0.95);
Assert(freezetable >= 0);
/*
* Compute XID limit causing an aggressive vacuum, being careful not to
* generate a "permanent" XID
*/
limit = ReadNextTransactionId() - freezetable;
if (!TransactionIdIsNormal(limit))
limit = FirstNormalTransactionId;
if (freeze_table_age < 0)
freeze_table_age = vacuum_freeze_table_age;
freeze_table_age = Min(freeze_table_age, autovacuum_freeze_max_age * 0.95);
Assert(freeze_table_age >= 0);
aggressiveXIDCutoff = nextXID - freeze_table_age;
if (!TransactionIdIsNormal(aggressiveXIDCutoff))
aggressiveXIDCutoff = FirstNormalTransactionId;
if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
limit))
aggressiveXIDCutoff))
return true;
/*
* Similar to the above, determine the table freeze age to use for
* multixacts: as specified by the caller, or
* vacuum_multixact_freeze_table_age, but in any case not more than
* autovacuum_multixact_freeze_table_age * 0.95, so that if you have e.g.
* multixacts: as specified by the caller, or the value of the
* vacuum_multixact_freeze_table_age GUC, but in any case not more than
* 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
* multixacts before anti-wraparound autovacuum is launched.
*/
freezetable = multixact_freeze_table_age;
if (freezetable < 0)
freezetable = vacuum_multixact_freeze_table_age;
freezetable = Min(freezetable,
effective_multixact_freeze_max_age * 0.95);
Assert(freezetable >= 0);
/*
* Compute MultiXact limit causing an aggressive vacuum, being careful to
* generate a valid MultiXact value
*/
mxactLimit = ReadNextMultiXactId() - freezetable;
if (mxactLimit < FirstMultiXactId)
mxactLimit = FirstMultiXactId;
if (multixact_freeze_table_age < 0)
multixact_freeze_table_age = vacuum_multixact_freeze_table_age;
multixact_freeze_table_age =
Min(multixact_freeze_table_age,
effective_multixact_freeze_max_age * 0.95);
Assert(multixact_freeze_table_age >= 0);
aggressiveMXIDCutoff = nextMXID - multixact_freeze_table_age;
if (aggressiveMXIDCutoff < FirstMultiXactId)
aggressiveMXIDCutoff = FirstMultiXactId;
if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
mxactLimit))
aggressiveMXIDCutoff))
return true;
/* Non-aggressive VACUUM */
return false;
}

View File

@ -287,8 +287,9 @@ extern void vac_update_relstats(Relation relation,
bool *minmulti_updated,
bool in_outer_xact);
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 freeze_table_age,
int multixact_freeze_table_age,
TransactionId *oldestXmin,
MultiXactId *oldestMxact,