Repository navigation
[BUG] RESTORING gets baselined as a database's expected state, alerting forever once it goes ONLINE #2189
Description
Component
Both
Describe the Bug
Found by dogfooding on the 52-server production monitoring box. In the last 24 hours it fired 636 Database State alerts from only 5 distinct (server, database) pairs — roughly 127 identical repeats each:
636 fires / 5 distinct (server,detail) pairs
multi-22 Database: db-A Expected: RESTORING Current: ONLINE -> x134
psi Database: db-B Expected: RESTORING Current: ONLINE -> x130
multi-53 Database: db-C Expected: RESTORING Current: ONLINE -> x128
multi-44 Database: db-D Expected: RESTORING Current: ONLINE -> x125
multi-49 Database: db-E Expected: RESTORING Current: ONLINE -> x119
(Database names are relabelled db-A..db-E — this repo is public and the originals are client-identifying. Server names, counts and every number are unchanged.)
Every one is a database that was observed mid-restore during a consolidation, had RESTORING learned as its accepted baseline, and has been "deviating" ever since by being healthy.
SeedDatabaseStateExpectedSql refuses to baseline a critical first observation, but its exclusion list is only SUSPECT, RECOVERY_PENDING, EMERGENCY:
AND (CASE WHEN ds.is_in_standby THEN 'STANDBY' ELSE ds.state_desc END)
NOT IN ('SUSPECT', 'RECOVERY_PENDING', 'EMERGENCY')RESTORING is every bit as transient as RECOVERY_PENDING — it is a database in the middle of an operation, not a steady state anyone would choose as "expected." Baselining it inverts the alert permanently: the database is now paged about for being ONLINE, forever, and the only escape is an operator noticing and re-baselining by hand.
The guard's own doc says onboarding a server mid-outage "must not learn the bad state as expected." A restore in progress is that same situation; the list just doesn't include it.
Steps to Reproduce
- Add a server, or let one be swept, while a database is
RESTORING(a restore, a log-shipping cutover, a consolidation). - Let the seed baseline it.
- Restore completes; database goes
ONLINE. - It now deviates from
RESTORINGforever and alerts every cooldown.
Expected Behavior
Don't baseline transient operational states. At minimum add RESTORING (and I'd argue RECOVERING) to the seed exclusion so those databases stay pending until they settle. Worth also considering a re-baseline when a pending database first reaches ONLINE, so the steady state gets learned rather than the transition.
Actual Behavior
RESTORING is learned as the expected state, and every healthy observation afterwards is a deviation. 636 alerts in 24 hours from 5 databases on one box.
Additional Context
#2166 does NOT cover this, which is why this is filed separately. I checked before assuming: DatabaseStateTokens.RepeatsAreNoise keys off the CURRENT state, and the current state in this flood is ONLINE, which returns false. So the edge-trigger in #2166 leaves alreadyAnnounced false here and these keep repeating every cooldown even after that ships. #2166 silences a database parked in a quiet state; this is a database parked against a bogus baseline.
That distinction is worth carrying into #2166's design discussion: an edge trigger keyed on "is the current state one of the quiet ones" covers the reported case but not the more general one, which is "this exact deviation is unchanged since the last time we announced it." The general form would have caught both. Not proposing to widen #2166 mid-review — noting it so the narrower choice is deliberate rather than accidental.
Also note the pairing: fix the seed and these 5 databases still need their existing bad baselines corrected, so a fix probably wants to handle already-poisoned rows rather than only new ones.
Activity
Stop learning a mid-restore state as a database's expected one (#2189)
Stop learning a mid-restore state as a database's expected one (#2189)
Close the #2189 fix in the Viewer too, plus changelog link and Lite t…
Merge pull request #2202 from erikdarlingdata/fix/2189-restoring-base…
erikdarlingdata commentedon Aug 11, 2026
Shipped in #2202. Two implementations of this fix collided today - #2196 from the fleet-side session, #2202 from a repo-side lane - and #2196 was closed in #2202's favor on four grounds now on both records: the exclusion list lives in ONE shared constant (DatabaseStateTokens.NeverBaselinedSqlList) that all five seed/heal surfaces interpolate, pinned by test, instead of three hand-copied SQL strings (the reviewer had already caught a third-copy divergence in #2196 - the instance was fixed, the mechanism remained); the heal UPDATEs the poisoned row to the observed steady state in place rather than deleting and re-seeding, so there is no window where the baseline is absent and no ordering dependency (idempotency and no-intermediate-fire both asserted live against real Postgres); the reset-to-current path - which records whatever it sees with no state filter and would have re-poisoned mid-restore forever - is closed by the same constant; and the heal deliberately spares what it must not touch, with the broad ONLINE-is-never-alertable rule REJECTED after review caught two real regressions it causes (healing a STANDBY secondary that recovered out of standby announces the repair instead of the break; healing an auto-baselined OFFLINE database parked after maintenance re-fires forever in Lite) - both pinned watched-red. A database mid-RESTORE/RECOVERING now stays pending and silent until it settles; the five poisoned production rows self-correct on the next sweep with no migration and no operator step. By-product: #2203 tracks #2166's untracked Lite parity gap (no persisted alerted-state memory), which gained teeth here.
Stop asserting a best-effort maintenance cycle always runs (#2266 ite…
Stop asserting a best-effort maintenance cycle always runs (#2266 ite…
Report a skipped database-state maintenance cycle (#2266 follow-on) (#…
Never learn a transient state as a database's expected state (erikdar…
Database-state writes take the WRITE lock (erikdarlingdata#2208)
Wait for the baseline to land instead of assuming one sweep seeded it (…