Skip to content

Commit c3ffa73

Browse files
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
1 parent 483ac64 commit c3ffa73

File tree

3 files changed

+92
-112
lines changed

3 files changed

+92
-112
lines changed

src/backend/access/heap/vacuumlazy.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ heap_vacuum_rel(Relation rel, VacuumParams *params,
360360
*/
361361
aggressive = vacuum_set_xid_limits(rel,
362362
params->freeze_min_age,
363-
params->freeze_table_age,
364363
params->multixact_freeze_min_age,
364+
params->freeze_table_age,
365365
params->multixact_freeze_table_age,
366366
&OldestXmin, &OldestMxact,
367367
&FreezeLimit, &MultiXactCutoff);

src/backend/commands/vacuum.c

Lines changed: 89 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -956,24 +956,25 @@ get_all_vacuum_rels(int options)
956956
bool
957957
vacuum_set_xid_limits(Relation rel,
958958
int freeze_min_age,
959-
int freeze_table_age,
960959
int multixact_freeze_min_age,
960+
int freeze_table_age,
961961
int multixact_freeze_table_age,
962962
TransactionId *oldestXmin,
963963
MultiXactId *oldestMxact,
964964
TransactionId *freezeLimit,
965965
MultiXactId *multiXactCutoff)
966966
{
967-
int freezemin;
968-
int mxid_freezemin;
967+
TransactionId nextXID,
968+
safeOldestXmin,
969+
aggressiveXIDCutoff;
970+
MultiXactId nextMXID,
971+
safeOldestMxact,
972+
aggressiveMXIDCutoff;
969973
int effective_multixact_freeze_max_age;
970-
TransactionId limit;
971-
TransactionId safeLimit;
972-
MultiXactId mxactLimit;
973-
MultiXactId safeMxactLimit;
974-
int freezetable;
975974

976975
/*
976+
* Acquire oldestXmin.
977+
*
977978
* We can always ignore processes running lazy vacuum. This is because we
978979
* use these values only for deciding which tuples we must keep in the
979980
* tables. Since lazy vacuum doesn't write its XID anywhere (usually no
@@ -1005,44 +1006,32 @@ vacuum_set_xid_limits(Relation rel,
10051006

10061007
Assert(TransactionIdIsNormal(*oldestXmin));
10071008

1009+
/* Acquire oldestMxact */
1010+
*oldestMxact = GetOldestMultiXactId();
1011+
Assert(MultiXactIdIsValid(*oldestMxact));
1012+
1013+
/* Acquire next XID/next MXID values used to apply age-based settings */
1014+
nextXID = ReadNextTransactionId();
1015+
nextMXID = ReadNextMultiXactId();
1016+
10081017
/*
10091018
* Determine the minimum freeze age to use: as specified by the caller, or
10101019
* vacuum_freeze_min_age, but in any case not more than half
10111020
* autovacuum_freeze_max_age, so that autovacuums to prevent XID
10121021
* wraparound won't occur too frequently.
10131022
*/
1014-
freezemin = freeze_min_age;
1015-
if (freezemin < 0)
1016-
freezemin = vacuum_freeze_min_age;
1017-
freezemin = Min(freezemin, autovacuum_freeze_max_age / 2);
1018-
Assert(freezemin >= 0);
1023+
if (freeze_min_age < 0)
1024+
freeze_min_age = vacuum_freeze_min_age;
1025+
freeze_min_age = Min(freeze_min_age, autovacuum_freeze_max_age / 2);
1026+
Assert(freeze_min_age >= 0);
10191027

1020-
/*
1021-
* Compute the cutoff XID, being careful not to generate a "permanent" XID
1022-
*/
1023-
limit = *oldestXmin - freezemin;
1024-
if (!TransactionIdIsNormal(limit))
1025-
limit = FirstNormalTransactionId;
1026-
1027-
/*
1028-
* If oldestXmin is very far back (in practice, more than
1029-
* autovacuum_freeze_max_age / 2 XIDs old), complain and force a minimum
1030-
* freeze age of zero.
1031-
*/
1032-
safeLimit = ReadNextTransactionId() - autovacuum_freeze_max_age;
1033-
if (!TransactionIdIsNormal(safeLimit))
1034-
safeLimit = FirstNormalTransactionId;
1035-
1036-
if (TransactionIdPrecedes(limit, safeLimit))
1037-
{
1038-
ereport(WARNING,
1039-
(errmsg("oldest xmin is far in the past"),
1040-
errhint("Close open transactions soon to avoid wraparound problems.\n"
1041-
"You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
1042-
limit = *oldestXmin;
1043-
}
1044-
1045-
*freezeLimit = limit;
1028+
/* Compute freezeLimit, being careful to generate a normal XID */
1029+
*freezeLimit = nextXID - freeze_min_age;
1030+
if (!TransactionIdIsNormal(*freezeLimit))
1031+
*freezeLimit = FirstNormalTransactionId;
1032+
/* freezeLimit must always be <= oldestXmin */
1033+
if (TransactionIdPrecedes(*oldestXmin, *freezeLimit))
1034+
*freezeLimit = *oldestXmin;
10461035

10471036
/*
10481037
* Compute the multixact age for which freezing is urgent. This is
@@ -1057,93 +1046,83 @@ vacuum_set_xid_limits(Relation rel,
10571046
* than half effective_multixact_freeze_max_age, so that autovacuums to
10581047
* prevent MultiXact wraparound won't occur too frequently.
10591048
*/
1060-
mxid_freezemin = multixact_freeze_min_age;
1061-
if (mxid_freezemin < 0)
1062-
mxid_freezemin = vacuum_multixact_freeze_min_age;
1063-
mxid_freezemin = Min(mxid_freezemin,
1064-
effective_multixact_freeze_max_age / 2);
1065-
Assert(mxid_freezemin >= 0);
1066-
1067-
/* Remember for caller */
1068-
*oldestMxact = GetOldestMultiXactId();
1069-
1070-
/* compute the cutoff multi, being careful to generate a valid value */
1071-
mxactLimit = *oldestMxact - mxid_freezemin;
1072-
if (mxactLimit < FirstMultiXactId)
1073-
mxactLimit = FirstMultiXactId;
1074-
1075-
safeMxactLimit =
1076-
ReadNextMultiXactId() - effective_multixact_freeze_max_age;
1077-
if (safeMxactLimit < FirstMultiXactId)
1078-
safeMxactLimit = FirstMultiXactId;
1079-
1080-
if (MultiXactIdPrecedes(mxactLimit, safeMxactLimit))
1081-
{
1049+
if (multixact_freeze_min_age < 0)
1050+
multixact_freeze_min_age = vacuum_multixact_freeze_min_age;
1051+
multixact_freeze_min_age = Min(multixact_freeze_min_age,
1052+
effective_multixact_freeze_max_age / 2);
1053+
Assert(multixact_freeze_min_age >= 0);
1054+
1055+
/* Compute multiXactCutoff, being careful to generate a valid value */
1056+
*multiXactCutoff = nextMXID - multixact_freeze_min_age;
1057+
if (*multiXactCutoff < FirstMultiXactId)
1058+
*multiXactCutoff = FirstMultiXactId;
1059+
/* multiXactCutoff must always be <= oldestMxact */
1060+
if (MultiXactIdPrecedes(*oldestMxact, *multiXactCutoff))
1061+
*multiXactCutoff = *oldestMxact;
1062+
1063+
/*
1064+
* Done setting output parameters; check if oldestXmin or oldestMxact are
1065+
* held back to an unsafe degree in passing
1066+
*/
1067+
safeOldestXmin = nextXID - autovacuum_freeze_max_age;
1068+
if (!TransactionIdIsNormal(safeOldestXmin))
1069+
safeOldestXmin = FirstNormalTransactionId;
1070+
safeOldestMxact = nextMXID - effective_multixact_freeze_max_age;
1071+
if (safeOldestMxact < FirstMultiXactId)
1072+
safeOldestMxact = FirstMultiXactId;
1073+
if (TransactionIdPrecedes(*oldestXmin, safeOldestXmin))
10821074
ereport(WARNING,
1083-
(errmsg("oldest multixact is far in the past"),
1084-
errhint("Close open transactions with multixacts soon to avoid wraparound problems.")));
1085-
/* Use the safe limit, unless an older mxact is still running */
1086-
if (MultiXactIdPrecedes(*oldestMxact, safeMxactLimit))
1087-
mxactLimit = *oldestMxact;
1088-
else
1089-
mxactLimit = safeMxactLimit;
1090-
}
1091-
1092-
*multiXactCutoff = mxactLimit;
1075+
(errmsg("cutoff for removing and freezing tuples is far in the past"),
1076+
errhint("Close open transactions soon to avoid wraparound problems.\n"
1077+
"You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
1078+
if (MultiXactIdPrecedes(*oldestMxact, safeOldestMxact))
1079+
ereport(WARNING,
1080+
(errmsg("cutoff for freezing multixacts is far in the past"),
1081+
errhint("Close open transactions soon to avoid wraparound problems.\n"
1082+
"You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
10931083

10941084
/*
1095-
* Done setting output parameters; just need to figure out if caller needs
1096-
* to do an aggressive VACUUM or not.
1085+
* Finally, figure out if caller needs to do an aggressive VACUUM or not.
10971086
*
10981087
* Determine the table freeze age to use: as specified by the caller, or
1099-
* vacuum_freeze_table_age, but in any case not more than
1100-
* autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly
1101-
* VACUUM schedule, the nightly VACUUM gets a chance to freeze tuples
1102-
* before anti-wraparound autovacuum is launched.
1103-
*/
1104-
freezetable = freeze_table_age;
1105-
if (freezetable < 0)
1106-
freezetable = vacuum_freeze_table_age;
1107-
freezetable = Min(freezetable, autovacuum_freeze_max_age * 0.95);
1108-
Assert(freezetable >= 0);
1109-
1110-
/*
1111-
* Compute XID limit causing an aggressive vacuum, being careful not to
1112-
* generate a "permanent" XID
1113-
*/
1114-
limit = ReadNextTransactionId() - freezetable;
1115-
if (!TransactionIdIsNormal(limit))
1116-
limit = FirstNormalTransactionId;
1088+
* the value of the vacuum_freeze_table_age GUC, but in any case not more
1089+
* than autovacuum_freeze_max_age * 0.95, so that if you have e.g nightly
1090+
* VACUUM schedule, the nightly VACUUM gets a chance to freeze XIDs before
1091+
* anti-wraparound autovacuum is launched.
1092+
*/
1093+
if (freeze_table_age < 0)
1094+
freeze_table_age = vacuum_freeze_table_age;
1095+
freeze_table_age = Min(freeze_table_age, autovacuum_freeze_max_age * 0.95);
1096+
Assert(freeze_table_age >= 0);
1097+
aggressiveXIDCutoff = nextXID - freeze_table_age;
1098+
if (!TransactionIdIsNormal(aggressiveXIDCutoff))
1099+
aggressiveXIDCutoff = FirstNormalTransactionId;
11171100
if (TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
1118-
limit))
1101+
aggressiveXIDCutoff))
11191102
return true;
11201103

11211104
/*
11221105
* Similar to the above, determine the table freeze age to use for
1123-
* multixacts: as specified by the caller, or
1124-
* vacuum_multixact_freeze_table_age, but in any case not more than
1125-
* autovacuum_multixact_freeze_table_age * 0.95, so that if you have e.g.
1106+
* multixacts: as specified by the caller, or the value of the
1107+
* vacuum_multixact_freeze_table_age GUC, but in any case not more than
1108+
* effective_multixact_freeze_max_age * 0.95, so that if you have e.g.
11261109
* nightly VACUUM schedule, the nightly VACUUM gets a chance to freeze
11271110
* multixacts before anti-wraparound autovacuum is launched.
11281111
*/
1129-
freezetable = multixact_freeze_table_age;
1130-
if (freezetable < 0)
1131-
freezetable = vacuum_multixact_freeze_table_age;
1132-
freezetable = Min(freezetable,
1133-
effective_multixact_freeze_max_age * 0.95);
1134-
Assert(freezetable >= 0);
1135-
1136-
/*
1137-
* Compute MultiXact limit causing an aggressive vacuum, being careful to
1138-
* generate a valid MultiXact value
1139-
*/
1140-
mxactLimit = ReadNextMultiXactId() - freezetable;
1141-
if (mxactLimit < FirstMultiXactId)
1142-
mxactLimit = FirstMultiXactId;
1112+
if (multixact_freeze_table_age < 0)
1113+
multixact_freeze_table_age = vacuum_multixact_freeze_table_age;
1114+
multixact_freeze_table_age =
1115+
Min(multixact_freeze_table_age,
1116+
effective_multixact_freeze_max_age * 0.95);
1117+
Assert(multixact_freeze_table_age >= 0);
1118+
aggressiveMXIDCutoff = nextMXID - multixact_freeze_table_age;
1119+
if (aggressiveMXIDCutoff < FirstMultiXactId)
1120+
aggressiveMXIDCutoff = FirstMultiXactId;
11431121
if (MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
1144-
mxactLimit))
1122+
aggressiveMXIDCutoff))
11451123
return true;
11461124

1125+
/* Non-aggressive VACUUM */
11471126
return false;
11481127
}
11491128

src/include/commands/vacuum.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ extern void vac_update_relstats(Relation relation,
287287
bool *minmulti_updated,
288288
bool in_outer_xact);
289289
extern bool vacuum_set_xid_limits(Relation rel,
290-
int freeze_min_age, int freeze_table_age,
290+
int freeze_min_age,
291291
int multixact_freeze_min_age,
292+
int freeze_table_age,
292293
int multixact_freeze_table_age,
293294
TransactionId *oldestXmin,
294295
MultiXactId *oldestMxact,

0 commit comments

Comments
 (0)