From 58e015ce08109165ddf871fc963f9026733205a8 Mon Sep 17 00:00:00 2001 From: Baji Shaik Date: Sat, 7 Nov 2026 09:46:47 -0600 Subject: [PATCH v1 1/2] Fix wraparound of the ages used for autovacuum freeze scores. relation_needs_vacanalyze() computes the freeze scores from recentXid - relfrozenxid and recentMulti - relminmxid. recentXid and recentMulti are read before pg_class is scanned: by an autovacuum worker before do_autovacuum() starts, and by pg_stat_get_autovacuum_scores() at its start. A table's relfrozenxid or relminmxid can advance past them in between, for example by a concurrent VACUUM or a table rewrite, and the unsigned subtraction then wraps around to about 4 billion. That also exceeds the failsafe age, so the score is raised to a large power: a table that was just vacuumed gets a score around 1e44, and autovacuum may put it at the front of its list. The window grows with the number of relations in the database. Treat the age as zero when the stored value doesn't precede the one read earlier. Oversight in commit d7965d65fc. Author: Baji Shaik Backpatch-through: 19 --- src/backend/postmaster/autovacuum.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index c4c44c9187..546cb3899b 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -3202,8 +3202,18 @@ relation_needs_vacanalyze(Oid relid, * variable might be 0 here (i.e., a division-by-zero hazard), so in that * case we use the mxid_age as the MXID score. */ - xid_age = TransactionIdIsNormal(relfrozenxid) ? recentXid - relfrozenxid : 0; - mxid_age = MultiXactIdIsValid(relminmxid) ? recentMulti - relminmxid : 0; + /* + * recentXid and recentMulti were read before the pg_class scan, so the + * table's relfrozenxid or relminmxid may have advanced past them + * meanwhile, for example by a concurrent VACUUM or a table rewrite. + * Treat the age as zero in that case. + */ + xid_age = (TransactionIdIsNormal(relfrozenxid) && + TransactionIdPrecedes(relfrozenxid, recentXid)) ? + recentXid - relfrozenxid : 0; + mxid_age = (MultiXactIdIsValid(relminmxid) && + MultiXactIdPrecedes(relminmxid, recentMulti)) ? + recentMulti - relminmxid : 0; scores->xid = (double) xid_age / freeze_max_age; scores->mxid = (double) mxid_age / Max(1, multixact_freeze_max_age); -- 2.54.0 (Apple Git-157)