Repository navigation
[BUG] N registrations silently resolving to one database get N identities and N copies of every incident: no DB_NAME() cross-check exists #2228
Description
Activity
Design recommendation covering this, #2218 and #2228 together is on #2218: #2218
Short version — these are two problems, not one. Identity is derived from mutable input (this issue, and the reason #2218 cannot just append fields), and identity does not reflect what the connection actually reached (#2228). Those pull in opposite directions, so the recommendation is to split the two jobs: a stable surrogate
server_idallocated once, plus a separate observed-identity fingerprint used for duplicate detection rather than as a key.The migration is cheaper than it looks: seed the surrogate with today's hash value and stop recomputing it, so no
collect.*data moves,server_idstays anint, and only future edits behave differently.Scoping pass posted on #2218 (comment #2218 (comment) for #2158's half; the census itself is #2218 (comment)). The finding that lands on this issue:
The fingerprint is not optional — one existing site needs it.
StoreConfigProvider:203is the #2254 file-vs-store diagnostic, and it compares file-derived ids against stored ids deliberately;FileOnlyServerWarningTests.SameNameButADifferentHost_IsStillReportedAsAbsentpins that a name match would get it wrong. Under a stable surrogate adarling.jsonentry cannot derive the stored id at all, so that comparison has to re-key onto something. Storage name is the cheap answer and reintroduces exactly the confusion the test rules out; the observed-identity fingerprint is the correct one.So the two halves of the #2218 design are not independently shippable in either order — the surrogate breaks a diagnostic that only the fingerprint can fix. Worth knowing before we scope them as separate tickets.
Secondary: whether the fingerprint lives on the config row or in its own table is a real decision, not a detail. Only a separate table can hold history, which is what makes it a detector ("this id has been reached at three different
SERVERPROPERTY('ServerName')values this month") rather than a flag that gets overwritten by whichever connect happened last — and last-writer-wins is close to the current behavior this issue is complaining about.Scope note from #2220, now that its cause is confirmed (#2265): this tripwire would not have caught it, and the reporter argued that correctly before I did.
#2220 turned out to be the Azure per-database sweep enumerating master and collecting every sibling database on the logical server under one registration's
server_id. Every registration's entry connection was genuinely, correctly scoped to its own database — so aDB_NAME()-vs-registered-catalog check at connect or sweep time passes cleanly while the fan-out happens downstream of it.That does not argue against this issue; it narrows what it claims. This tripwire covers a registration whose connection silently resolved elsewhere (bad or missing
Initial Catalog), which is still invisible today and still worth a check. It just should not be described as covering sibling-database contamination, because that came from the enumeration rather than from the connection.- added 4 commits that reference this issue
on Aug 15, 2026 Closing: the cross-check this issue said did not exist now does, shipped in #2277.
Both engine probes return the database the connection actually reached —
DB_NAME()on SQL Server,current_database()on PostgreSQL — and the worker compares it against the registration on every connect.DB_NAME()needs no DMV, so this did not reintroduce theVIEW DATABASE STATEdependency #1535 removed from that probe, and both columns are appended because every read there is positional.Why it had to be at connect rather than in the registry, which is worth recording since #2218 was landing at the same time: identity is now assigned rather than derived (#2158, #2218), and neither of those can touch this defect — the two colliding registrations genuinely differ in configuration, so no amount of care in hashing config can tell that they resolve to one database. Only the server can answer what a connection reached.
Fires at Error on the transition, not per connect: a mismatch is a standing misconfiguration that persists until someone edits the registration, so repeating it every reconnect would bury the one line that matters. The recovery is logged too, so a fix is confirmed rather than merely silent.
Deliberately silent in three cases, each a false positive that would have made the tripwire worthless: a registration naming no database is server-scoped by design and meant to land wherever the login defaults; a null probe answer is unknown rather than mismatched; and comparison is case-insensitive, which is what SQL Server database names are.
Your layer 2 — refusing a new registration whose (host, actual database) pair collides — is now #2280. Two things I found while scoping it that belong on the follow-up rather than here: the three registration paths do not share a code path and the file seed probably should not refuse (a server silently unmonitored is worse than one collected twice with a loud log), and the check must compare on the full identity rather than (host, database), because a read-only-intent registration alongside a read-write one for the same database is legitimate and
read_only_intentis already part of the identity.- added a commit that references this issue
on Aug 19, 2026
What
N registrations whose connection strings silently resolve to the SAME database get N distinct identities and N full copies of every collected row, with no warning anywhere. Surfaced by #2220: six Azure SQL DB registrations under one logical server, six
server_ids each holding byte-identical deadlock graphs all naming the same source database, one real incident alerting six times.Mechanism, verified in source
server_id = ServerIdHelper.GetDeterministicHashCode(server.Config.StorageName). Six registrations carry six identities regardless of where their connections actually land.database_name = DB_NAME()on the Azure per-database paths) but nothing COMPARES that answer to the registered catalog, at--test-connection, at connect, or at sweep time. A missing or wrongInitial Catalog(Azure defaults you into whatever the connection policy resolves) lands the sweep in the wrong database and every collector happily stores the wrong database's rows under the registration's identity.Fix shape
Two layers, both cheap because the data is already in hand:
DB_NAME()against the registered database and, on mismatch, log loudly tocollection_logand raise the existing self-alert path - "registration 'Sibling-A' is connected to database 'Source-DB', not 'Sibling-A': check Initial Catalog". Alert once per registration per mismatch state, not per sweep.Azure SQL DB is the primary exposure (per-database registrations are the normal shape there), but the tripwire is engine-generic.
Relations
actually_connected_torun confirms which side, but this defect is real under either outcome)collect.serversjoins untrustworthy for diagnosing exactly this - the reason [BUG] Blocking/Deadlock alerts cross-talk between Azure SQL databases sharing the same logical server (Darling) #2220's diagnostics label by eye)