Repository navigation
pg wraparound: Warning arm signals autovacuum losing the race, not routine proximity - #2693
Conversation
…not routine proximity (#2689) The Warning arm fired at 0.9x autovacuum_freeze_max_age unconditionally, and that is a ROUTINE operating point - every healthy database climbs to ~that age on every freeze cycle and is reset, the expected sawtooth. One fleet database re-fired the identical alert every ~5-minute cycle at 9% of the way to actual wraparound, with the read tool's own severity reading ok. The collector and get_pg_wraparound_risk already report freezing_is_keeping_up (whether the age has come down from its own recent peak) but the alert never consulted it. Now: the relative Warning arm moves to 1.0x the setting (the crossing point where autovacuum itself forces a vacuum) and is gated by FreezingIsKeepingUp per counter - a database above its setting but resetting every cycle stays silent, one stuck at or above it, never seen lower in the window, warns. Also adds an absolute early-Warning arm at 50% of the true 2^31 ceiling, mirroring the existing Critical ceiling arm, so a cluster tuned high enough that the relative arm is unreachable still gets an early signal instead of jumping straight to Critical. FreezingIsKeepingUp is plumbed from the collector's own window-peak columns through DarlingPostgresAlertReadAdapter's WraparoundSql (added window_peak_xid_age/window_peak_multixact_age, same MAX()-OVER-partition technique DarlingPgWraparoundReader already uses) into PostgresWraparoundAlertInfo, mirroring DarlingMcpPgWraparoundTools' own derivation (age < window_peak) so the alert and the read tool can never disagree about what "keeping up" means. Critical arms (2x setting, 74.5% of ceiling) are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Reviewed the wraparound Warning-arm change ( Overall: solid. Traced through the boundary math (relative 1.0x/2.0x arms, the new absolute 0.5/0.745 ceiling arms, and the per-counter One minor nit, not blockable, couldn't leave it inline since the line falls outside the diff hunk:
This is now stale — the Warning arm moved from 90% to 100% of the setting in this PR ( |
…ong ceiling math AnUnjudgeableCounterDoesNotSilenceTheJudgeableOne used age=190M, which cleared the OLD 0.9x warnAt (180M) but not the NEW 1.0x warnAt (200M) - the test's own intent (a judgeable counter still alerts when the other is unjudgeable) is unchanged, the fixture just needed to actually cross the new threshold. WarningIsReachableOnAClusterTunedPastHalfTheWall asserted Tuned (2B) >= WraparoundCeiling (2,147,483,648) - false, 2B is UNDER 2^31. Bumped to 2.2B so the "relative Warning is unreachable on this cluster" premise the test is built on is actually true. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| if (age >= ceilingWarnAt) | ||
| { | ||
| return (AlertSeverityLevel.Warning, ceilingWarnAt, true); | ||
| } |
There was a problem hiding this comment.
Correctness: the unconditional ceiling-Warning arm reintroduces the exact bug this PR fixes, for any cluster tuned above ~1.07B.
autovacuum_freeze_max_age's legal range in PostgreSQL is [100000, 2000000000] — the max is 2 billion, which is always below WraparoundCeiling (2,147,483,648). That means the relative Warning arm (age >= setting, now 1.0x) is reachable for every legal setting; unlike the relative Critical arm (2x setting), it never actually becomes unreachable in practice. So the stated rationale for this arm ("a cluster tuned high enough that the relative arm becomes unreachable, this still gives an early signal instead of jumping straight to Critical with none") doesn't hold — the only test exercising it (WarningIsReachableOnAClusterTunedPastHalfTheWall) uses freezeMax: 2_200_000_000, which is itself outside Postgres's legal range and only "proves" the arm fires in a configuration that can't occur.
The actual effect: for any legal tuning where setting > ~1.074B (e.g. 1.2B, 1.5B — the same range the pre-existing WraparoundThresholdsScaleToTheServersOwnFreezeMaxAge test already treats as a realistic tuning), ceilingWarnAt (~1.074B) sits below warnAt (= setting). A healthy database's routine sawtooth climbs to just under its own setting every cycle before autovacuum resets it — exactly the "routine, not a risk" case #2689 is about. On such a cluster the age will cross ceilingWarnAt on every single healthy cycle, long before it ever reaches its own setting, and this branch fires unconditionally (no FreezingIsKeepingUp check). That's the identical "pages on every healthy database" failure mode the PR's headline fix (gating the relative arm on FreezingIsKeepingUp) was written to eliminate — just moved to a different (but real, legal, and already-acknowledged-as-realistic-elsewhere-in-this-test-file) band of configurations.
As a bonus, the generated message text compounds this: for e.g. setting = 1.2B, age = 1.1B, the Warning body says "already well past the routine vacuum-forcing point on this cluster's own autovacuum_freeze_max_age" — but the age hasn't even reached the setting (1.2B) yet.
Worth either gating this arm on FreezingIsKeepingUp too (like the relative arm), or picking a fraction that's actually only reachable when the relative arm is genuinely unreachable (i.e. never, for legal settings, unless the ceiling arm is meant to future-proof against a raised PostgreSQL max — in which case that reasoning belongs in the doc comment).
Review summaryScope: Left one correctness finding inline on Everything else checked out:
|
…ikdarlingdata#2694) My merge-conflict resolution in erikdarlingdata#2694 (merging origin/dev's erikdarlingdata#2693 wraparound fix into my collection-health branch) committed without the .git/info/attributes CHANGELOG.md -text override in place, so git silently renormalized the WHOLE file to LF on staging. Content is byte-identical to what merged (verified with a CR-stripped diff); only line endings are restored to the CRLF this file has always used. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes #2689.
What
The PostgreSQL Wraparound Risk Warning arm fired at
0.9 × autovacuum_freeze_max_ageunconditionally — a routine operating point every healthy database climbs to on every freeze cycle before autovacuum resets it (the expected sawtooth). One fleet database re-fired the identical alert every ~5-minute collection cycle at 9% of the way to actual wraparound, while the read tool's own severity readok.The collector already computes what's needed to tell "routine" from "at risk" apart —
freezing_is_keeping_up(has the age come back down from its own recent peak) — but the alert never consulted it.Fix
autovacuum_freeze_max_age(the crossing point where autovacuum itself forces a vacuum) and is now gated byFreezingIsKeepingUp, computed per-counter (XID and MultiXact independently, since one can be healthy while the other is stuck). A database above its setting but resetting every cycle stays silent; one stuck at or above it, never seen lower in the window, warns.WraparoundWarningFractionOfCeiling), mirroring the existing Critical ceiling arm (0.745) — on a cluster tuned high enough that the relative arm becomes unreachable, this still gives an early signal instead of jumping straight to Critical with none.2× setting,74.5% of ceiling) are unchanged.FreezingIsKeepingUpis plumbed from the collector's own window-peak data:DarlingPostgresAlertReadAdapter.WraparoundSqlnow selectsMAX(frozen_xid_age)/MAX(min_multixid_age) OVER (PARTITION BY database_name)(the same techniqueDarlingPgWraparoundReaderalready uses for the read tool), carried intoPostgresWraparoundAlertInfoasWindowPeakXidAge/WindowPeakMultiXactAge, with the derivation (age < windowPeak) mirroringDarlingMcpPgWraparoundTools' own formula exactly — so the alert andget_pg_wraparound_riskcan never disagree about what "keeping up" means again.Tests
WraparoundGradesAtTheDocumentedBoundariesupdated for the new 1.0× boundary (was 0.9×).WarningIsSuppressedWhileTheCounterIsComingBackDownFromItsPeak/WarningFiresWhenTheCounterHasNeverComeBackDownInTheWindow— the core regression pair proving the gate.WarningIsReachableOnAClusterTunedPastHalfTheWall— the absolute-arm counterpart to the existing Critical-ceiling test.FindingsAreOrderedWorstFirstbumped its Warning fixture from 190M to 200M (the new boundary).Verified compiling (
PerformanceMonitor.Alerting,PerformanceMonitor.Darling.Service,Darling.Testsall build clean on macOS viaEnableWindowsTargeting); the test assembly isnet10.0-windows, so xunit execution runs in CI per house convention.🤖 Generated with Claude Code