Skip to content

Lite: an Azure SQL Database master target is scoped from the first alert sweep after a start - #4933

Merged
erikdarlingdata merged 2 commits into
devfrom
fix/lite-master-scope-stored-edition
Oct 2, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
fix/lite-master-scope-stored-edition

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

What changed

Lite's Azure SQL Database master scope (#4894) now uses the stored engine edition when the live connection status has none.

  • KnownEngineEditions is new. It holds one engine edition per server, keyed by the storage server id. A live edition from the connection status wins, and the class keeps it. With no live edition, the class answers the edition that it last knew: an earlier live one in this run, else the stored one. With neither, the edition is unknown and the server stays unscoped, as before.
  • The alert sweep (MainWindow.AlertEngine.cs) and the provider behind analysis, the overview card, the daily summary and the MCP tools (MainWindow.xaml.cs) both call KnownEngineEditions. Before, each site read SqlEngineEdition == 5 from the live status and built the target list itself. The IsAzureSqlDb flag of the alert snapshot uses the same edition.
  • LocalDataService.GetStoredEngineEditionsAsync is new. It reads the stored edition of every server in one grouped read. Each edition comes from the newest v_server_properties row, the row that GetSqlEngineEditionAsync reads for one server.
  • MainWindow_Loaded awaits the seed right after the database is initialized. It waits at most 10 seconds for the read, in KnownEngineEditions.SeedFromStoreAsync.

Why

On an Azure SQL Database master target, the Blocking and Deadlocks alerts fired in the first sweep after every start. They counted the events of the databases that are monitored as their own targets.

The first alert sweep after a start runs before any connection check has read the engine edition. Without the edition, AzureMasterScope.SeparatelyMonitoredDatabases returns an empty list, so the master was unscoped in that sweep. A failed connection check or an edit also writes a blank status in ServerManager, with the same result. Analysis, the card, the daily summary and the MCP tools read the same live status through the provider.

The Lite master scope has not shipped yet. PerformanceMonitor.Alerting/AzureMasterScope.cs is absent in v3.8.0. With this change, the #4894 CHANGELOG entry is true from the first sweep after a start.

The startup order

MainWindow_Loaded awaits SeedKnownEngineEditionsAsync() right after _databaseInitializer.InitializeAsync(). Everything that reads the scope starts later in the same method, in this order:

  1. new CollectionBackgroundService(, and its start. Its scheduled analysis reads the provider.
  2. _alertEngine = new AlertEngine(. CheckPerformanceAlerts returns at once while _alertEngine is null, so no sweep can run before this line.
  3. StartMcpServerAsync().
  4. RefreshServerList(), _statusTimer.Start() and the first RefreshOverviewAsync().

Before MainWindow_Loaded runs, nothing reads the provider. The constructor only assigns it. AzureMasterLiteWiringTests pins this order. So when the stored editions are read within the limit below, no sweep runs before them.

A server that is added while Lite runs is not in the seed. Its first connection check reads its edition, as before.

The startup wait is bounded

Everything in the list above waits for the seed. So the seed waits at most 10 seconds for the read (KnownEngineEditions.StartupSeedLimit). A held lock or a slow scan of the Parquet archive can delay the start by no more than that. The start covers collection, the alert engine, the MCP server and the server list.

  • If the read has not finished at the limit, Lite logs a warning and the start goes on. Until a connection check reads a server's edition, a master target is unscoped, as it was before this change.
  • A read that finishes after the limit still seeds. It never replaces an edition that a live status reported in the meantime.
  • A read that fails, before or after the limit, is logged as a warning, so its exception is always observed. The seed never throws.

KnownEngineEditions.SeedFromStoreAsync holds this wait, so the tests can drive it without a window. It waits on Task.WhenAny of the read and a Task.Delay of the limit. A read that finishes at the same moment as the limit is still seeded or logged.

Timing

The seed is one grouped read, not one read per server, because v_server_properties reads the Parquet archive and each read scans it again. The table below is from DuckDB 1.5.5, over a copy of a store's archive folder with three monthly server_properties Parquet files. The view returned 137 rows for 9 servers. The server_properties table came from an earlier copy of the same store, because Lite held the current file open.

Read Median First run Slowest
One grouped read 16.5 ms 12.2 ms 34.9 ms
One read per server (9 reads) 135.5 ms 125.4 ms 144.1 ms

Both reads returned the same edition for every server.

The grouped read uses arg_max_null, not arg_max. On DuckDB 1.5.5, arg_max skips a NULL edition and returns the edition of an older row. A row that was archived before the column existed reads as NULL through the view. GetSqlEngineEditionAsync returns the newest row, so the grouped read must do the same.

Tests

New and changed tests:

  • KnownEngineEditionsTests is new, with 9 tests. They cover the edition rule, the target list, the provider, the grouped read over a real store, and the overview card through the provider.
  • KnownEngineEditionsSeedLimitTests is new, with 7 tests. A read within the limit seeds before the start goes on. A read that stalls returns at the limit and logs a warning. A late result seeds and keeps a live edition. A late failure and a failed read are each logged as a warning. The limit stays between 1 and 30 seconds.
  • LiteAlertForwardingTests.AzureMasterEdition.cs is new, with 2 tests. Each builds the snapshot as CheckPerformanceAlerts builds it in a first sweep. The configured server has a blank status, and the store holds edition 5 under its storage id. Through the shared alert engine, blocking and deadlocks in the sibling databases do not fire. The events of the master's own database count 1 each.
  • AzureMasterLiteWiringTests is rewritten, with 4 tests. Both sites call KnownEngineEditions, and neither reads SqlEngineEdition == itself. MainWindow_Loaded seeds before each start listed above, and the seed waits at most StartupSeedLimit.

The pins:

  1. A master with stored edition 5 and no live edition is scoped. This holds for the class and for the alert path through the engine. It also holds for the overview card, through the provider over a real store.
  2. A live edition 5 stays in force after a later blank status.
  3. Some servers must not change. A SQL Server target with stored edition 3 stays unscoped. So do a master with no stored edition and an Azure SQL Database user database target. A live edition other than 5 replaces a stored 5.
  4. A stalled read of the stored editions does not hold up the start past the limit. Lite logs a warning.

Each pin can fail:

  • The dev rule (the live edition only), planted in KnownEngineEditions.Resolve, fails 9 tests. They are pins 1 and 2, the provider test, the seed test, both alert-path tests, and the two seed-limit tests that seed an edition. Pin 3 passes under it, as a guard must.
  • The dev copy of MainWindow.AlertEngine.cs fails the wiring test for the alert site. The dev copy of MainWindow.xaml.cs fails the wiring test for the provider and the order test.
  • Each of these defects, planted in the new code, fails at least one test:
    • a live edition that is not kept
    • a blank live edition that replaces the kept one
    • a seed that replaces a live edition
    • any known edition read as Azure SQL Database
    • the enabled flag and the read-only intent swapped in the target list
    • arg_max or arg_min_null in the grouped read
    • the seed moved after new AlertEngine(
    • an unbounded wait for the read, which fails pin 4 and both tests of a late read
    • no warning at the limit
    • a late result that is not seeded
    • a late failure that is not logged
    • the startup seed without the limit, in MainWindow.AlertEngine.cs

Runs, on the branch:

  • The new tests and the neighboring classes: 71 tests, 0 failed. The classes are KnownEngineEditionsSeedLimitTests, KnownEngineEditionsTests, LiteAlertForwardingTests, AzureMasterLiteWiringTests and SeparatelyMonitoredListNoteTests.
  • Lite.Tests, full suite: 7,099 tests, 0 failed, 0 skipped.
  • Darling.Tests, full suite: 19,766 tests, 0 failed, 1,292 skipped, 1 not run. The skipped tests need a live PostgreSQL store, and this run had none.
  • The build has 0 warnings and 0 errors.

CHANGELOG

None of its own. The #4894 entry describes the master scope, and this change makes it true from the first sweep after a start.

…ert sweep after a start

The master scope read the engine edition from the live connection status only. The first sweep after a start runs before any connection check has read it, and a failed check or an edit blanks the status, so in those sweeps a master target counted the blocking and deadlocks of the databases that alert on their own targets.

KnownEngineEditions now gives the alert sweep and the analysis provider one rule: a live edition wins and is remembered; without one, the stored edition, seeded in one grouped read before anything that reads the scope starts; with neither, unscoped as before.
…ts at most 10 seconds

Collection, the alert engine, MCP and the server list start after the seed, so a read that stalls
(a held lock, a slow Parquet scan) no longer holds them up. A read past the limit is logged as a
warning and the start goes on. A late result still seeds, and never replaces an edition that a live
status reported meanwhile. A late failure is logged, so its exception is always observed.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review October 2, 2026 02:10
@erikdarlingdata
erikdarlingdata merged commit 031d205 into dev Oct 2, 2026
17 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/lite-master-scope-stored-edition branch October 2, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant