Repository navigation
Query Store reads can come from a readable replica (#6254) - #6342
Merged
Merged
Conversation
…nother name (#6285) The connection test reads SERVERPROPERTY('ServerName') on the same connection, and it is compared with the server_name collected for each registration, beside the host-text match.
… collection_log.read_source (#6254)
…inding, replica connection builder (#6254)
…fix/6254-qs-replica-reads-darling-setting
…fix/6254-qs-replica-reads-core2
…, live statement test (#6254)
…b admin, MCP add/edit), read_source in the collection log tool (#6254)
…fix/6254-qs-replica-reads-darling-setting-ui
…fix/6254-qs-replica-reads-core-verify
…fix/6254-qs-replica-reads-lite-runner
…fix/6254-qs-replica-reads-core-verify
…fix/6254-drift-test
4 of 6 tasks
…rmark-commit behaviour test, drop re-spelled deadlock source pin (#6254)
…plica rung V212 -> V213
…fix/6254-qs-replica-reads-cleanup-darling
This was referenced Oct 21, 2026
16 of 20 tasks
…rt from the primary (#6254)
…ads-testconn # Conflicts: # Darling/PerformanceMonitor.Darling.Viewer/AddServerDialog.xaml.cs # Lite/Windows/AddServerDialog.xaml.cs
…on goes through TcpDataSource (#6254)
… the one prefix the setting allows) (#6254)
…fix/6254-qs-replica-reads-testconn # Conflicts: # Darling/Darling.Tests/ServerEditLiveTests.cs # Darling/PerformanceMonitor.Darling.Viewer/AddServerDialog.xaml.cs
…put on the data source (#6254)
erikdarlingdata
marked this pull request as ready for review
October 22, 2026 00:07
erikdarlingdata
enabled auto-merge (squash)
October 22, 2026 00:07
…eeding the V213 column; deadlock cursor pin follows the read helper (#6254)
This was referenced Oct 22, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #6254.
Closes #6279.
Claude posting for Erik Darling.
Draft. Built over several lanes on one branch; this is the consolidated body. Final-tree results are in
## Test plan.Why
#6254: with an Availability Group, Query Store is read from the primary, which is the replica that can least afford it. A readable secondary holds the same Query Store data, so the collectors can read it there, as long as the read stays correct: a secondary lags the primary by the redo, and the flush interval decides how old a row can be when it first becomes visible. Reading a replica naively loses rows (a row that was not yet visible at the watermark is never read again) or duplicates them. #6279 is the half of that problem that exists with no replica set: a capturing secondary (
FOR SECONDARY SET QUERY_STORE = ON) writes rows into the primary's Query Store late, so the watermark can step past rows that arrive afterwards.What changes
QS replica(Lite: servers.jsonQueryStoreReplicaand an Add/Edit field; Darling:queryStoreReplicain darling.json, store columnconfig.config_monitored_servers.query_store_replica, the Viewer Add/Edit dialog, the web admin edit form, MCPadd_serversandedit_server). A host,host,portor listener name; empty keeps today's behaviour exactly. SQL Server only; an Azure SQL Database never reads a replica. Protocol prefixes (np:,lpc:,admin:) and UNC or path values are refused;tcp:host,portis allowed.QueryStoreReplicaRouting,QueryStoreShipGuard,QueryStoreCapturingSecondaryTrackerinPerformanceMonitor.Collectors, used byDarlingCollectorRunnerand Lite'sRemoteCollectorService). Per database and per run it decides primary or replica. The replica is used only when it is the readable non-capturing secondary of the same group, the database's redo state is known and not behind, the versions match, and the flush interval is 900 s or less. Otherwise the database is read from the primary, with a stated reason (replica_is_primary,not_mirror,flush_too_slow,no_redo_state,wrong_database,version_mismatch,redo_behind,replica_unavailable). A replica read re-reads a look-back window of the watermark minus L (L is sized from the flush interval; 25 minutes is the measured bound at 900 s), a ship guard drops rows already shipped so the overlap never double-stores, and a replica read never advances the Query Store backfill contends with the live path on busy replicas — yield the slice when live collection is failing #2111 shrink counter or stages the open-interval stamp. A replica enumeration, version probe, redo read or per-database read that fails falls back to the primary the same run (replica_unavailable), never to a run error. A read cut by the row cap or byte budget records a hole and the next read starts at the watermark. Backfill always reads the primary.replica_role); a database with one gets the same look-back and ship guard on the primary, so late rows are no longer lost. The tracker lapses after 24 hours and is seeded from the store at start (Darling) or on the first Query Store run (Lite).ServerConnectionIdentity;PasswordBinding.ForServerbinds it as an 11th field only when a replica is set, so every existing seal opens unchanged. A password sealed for one replica does not open for another or with the replica removed, so a stored login is never sent to a host nobody approved. Every seal and identity site (service, MCP edit core, Viewer seal, Viewer dialog, test-connect) carries it. A storedenv:/file:reference goes to a replica only when darling.json declares it. The builder takes the replica from the identity the seal opens under, and returns null for PostgreSQL and Azure SQL Database. The MCP agent-route guard (MCP tag tools can change which servers an agent route covers #6281) counts a replica change like a host change, andadd_serversruns the replica host through the same held-machine check as the host. The diagnostics bundle aliases a store-set replica host.read_sourcesays where a Query Store run read from, for examplereplica 12, primary 2 (redo_behind: Sales)(names capped at 3, non-AG databases left out, NULL when no replica is set and for every older row). Columncollect.collection_log.read_source(Darling) andcollection_log.read_source(Lite DuckDB v78). It shows in: Darling MCPget_collection_healthandget_collection_log(per server and fleet; a run with none carries no key, so a no-replica page is byte-for-byte unchanged), the web Collectors table, the Viewer collection health and its two collection-log grids, Lite collection health, Liteget_collection_logand the Lite log grids (appended to the message column as<message>; read source: <source>).query-store-replica; dev's V212 is MCP add_servers and edit_server recognise one machine under another name (#6285) #6340):ALTER TABLE ... ADD COLUMN IF NOT EXISTSfor the two nullable columns (catalog-only, no backfill) andCREATE OR REPLACE VIEW collect.v_collection_log(the view freezes its column list).config.edit_monitored_serverbecomes 18 arguments (p_query_store_replica); the 17- and 15-argument forms stay as wrappers. A replica change counts as a connection change, so a replica-only edit asks for the password again; the password-rules trigger counts it as a move. Store-migration obligations done: probe sentinel andMapProbedSchemaVersionarm,GetBooleanlist (index 188) andViewerSchemaVersionGateTests,RungRewindArtifacts.Ladder, theScripts[^1]tests, the README rollback line for V213 (an older build ignores both columns; nothing to do before rolling back), andquery_store_replicain the Viewer's non-secret column list.Darling/tools/provision-roles.sqlafter the upgrade. The service replaces the threeedit_monitored_serverfunctions itself when its login owns them, but the 18-argument one is a new function that only the script grants to theviewerandmcproles, and only the script grants them the newquery_store_replicacolumn. Until then a web or MCP server edit fails withpermission denied. Compose and managed stores do it on their own. The README says so beside the V203 line.union_by_name, so older parquet reads NULL), and its own health, log and grid display. Lite docs:docs/lite-guide.md. Darling docs: README server-settings table.actual_state0 or 1 withreadonly_reason & 8 = 8(a non-capturing secondary reports 0 on SQL Server 2025 CU9, 1 on 2022), never 4 (a capturing secondary, routednot_mirror); on SQL Server 2022 a non-capturing secondary reports its own default flush row, so the larger of the primary's and the replica's flush value gates and sizes L.Test plan
Final-tree results (rig 55504, Postgres runtime zip, merged with origin/dev; this lane)
origin/dev(7d10470 and later). One conflict,DarlingEmptyEnumerationNoteTests: dev (Darling runner: check whether a collector note is dropped before the success return #6356) deleted the runner-note source pin and added a behaviour test; took dev's side. No migration collision (dev's newest is V212, this branch stays V213). Restored the UTF-8 BOM that an earlier edit had dropped fromPgMigrations.cs.0 Warning(s),0 Error(s)(checked on the build's own summary line, not a pipe's exit code).*QueryStoreReplica*,*PasswordBinding*,*Census*,*Ratchet*,*Migration*,*ViewerSchemaVersionGate*,*RungRewind*,*DarlingMcpServerAdmin*,*McpMachineIdentity*,*ServerIdentityFromStore*,*PostgresMajorVersionRegistry*,*ViewerDataServiceTests*,*CollectionCycleRow*,*DarlingRunnerCollectionNote*):Total: 1014, Errors: 0, Failed: 0, Skipped: 3(the 3 are the AG-fixture live tests).*QueryStoreReplica*,*Census*,*Ratchet*plus the local-classes list:Total: 192, Failed: 0.ci-select.py local-classeslist (260 Darling classes, 40 Lite classes): the Lite 40 ran, 0 failed. The Darling 260 ran for 40 minutes and I stopped it at the lane's wall clock before it finished (killed by PID). In the part that ran it logged 2 failures, both this branch's and both fixed in commit 3892954:PostgresTargetConfigTests.EveryRoundTripCriticalServerPropertyHasAStoreColumn(theQueryStoreReplicaproperty had no entry in the round-trip list) andAlertNotebookTableCatalogTests.EveryFieldKeyInTheCatalog_IsAFieldTheReadsOwnPayloadBuilds(read_sourceis written into the serialized payload by two helpers, so the scan now names them). Re-run after the fix withQueryStoreReplicaSettingTests:Total: 94, Failed: 0. 56 tests skipped in that partial run (live tests that need SQL Server or a second PostgreSQL).-trait Stage=Guard), both suites:[guard-darling] Darling.Tests Total: 4442, Errors: 0, Failed: 0, Skipped: 0;[guard-lite] Lite.Tests Total: 1196, Errors: 0, Failed: 0, Skipped: 0.bash .github/scripts/pre-push.shwas NOT run on the final tree (its build plus Guard plus class selection did not fit the lane's 60 minutes). Earlier tips ran it; see Gates below. CI decides the rest.Measurements behind the design (SQL Server 2025 CU9 and 2022 CU26 docker AG fixture, flush 60 s)
actual_state = 0(2025 CU9) or1(2022 CU26) withreadonly_reason8; withFOR SECONDARY SET QUERY_STORE = ONit reports 4, reason 8.sys.query_store_replicascannot detect a capturing secondary (a static four-row roster), so detection uses the rows already read.replica_group_id = 2and a negative interval id. Redo rows (sys.dm_hadr_database_replica_states) read on both sides: samegroup_database_id,last_commit_timewithin about 0.01 s, redo queue 0. The 25-minute L for capturing databases is a measured bound (18.5 minutes at F = 900), not a proof.QueryStoreReplicaReadsLiveSqlTests, gated onDARLING_TEST_SQL*).QueryStoreReplica= ag2 givesread_sourcereplica 1, primary 0with rows under ag1's server id; DarlingQueryStoreReplicaAgFixtureLiveTests(the real runner, real connector) the same. Both passed on the fixture in earlier lanes; the fixture is torn down and they skip without it.query_storeCollectors row shows "Read source: replica 12, primary 2".Behaviour tests and revert proofs (each break made once, test failed, restored)
QueryStoreReplicaRoutingTests,QueryStoreShipGuardTests): no L subtraction inComputeCutofffails T1 and T1c; probe admitting state 4 failsNotAReadableNonCapturingSecondary; eviction at W instead of W - L fails 6 guard tests;WasCutignored fails T4;FailureCountsTowardShrinktrue fails 2; tracker never lapsing fails its test; replica flush used alone failsPrimaryFlushOver900....QueryStoreReplicaRunnerTests, throughRunCollectorAsync): zero look-back fails T5 and the capturing test; skipping the guard filter fails the same 2; T6 (setting empty: 0 replica connections, cutoff equals the watermark,read_sourceNULL) and enumeration failure fail 2 and 1 when reverted; per-database fallback (catch disabled), the clamp hole (record dropped), the row-cap cut (record dropped,WasCutignored) each fail. The Azure check is dead code behindRunsPerDatabase, so no revert fails it; it is a guard only.AddedColumnsentry and bump removed plus builder keeping the primary host fails 5 of 8.A seal for replica A does not open for B,does not open once a replica is set).MovesConnection => AddressChangedplus no replica check fails both live MCP tests; stored-reference check removed failsA_stored_reference_goes_to_a_replica_only_when_the_file_declares_it; free property instead of identity and no Azure gate fail 2;Refusalreturning null fails all 9 refusal cases; replica read returning no rows fails the bundle alias test.TheRunnerCommitsTheDatabaseWatermark_OnlyAfterTheFlush_AndDoesCommit).Health_ShowsTheNewestQueryStoreRunsReadSource_OnThatRowOnly; per-server and fleet log page read sources nulled each failLog_CarriesTheReadSourceOnTheRunsThatHaveOne...; Viewer reader andMessageFormattedreverted each fail their test; Lite health, per-server log, fleet log, fleet reader, drill reader, grid reader andMessageFormattedreverted each fail theirs.AnEditThatMovesTheAddress_ClearsTheStoredIdentity.QueryStoreReplicaStoreLiveTests); a replica-only edit answerspassword_neededthrough the viewer role, managed and self-managed text; a store holding the old edit function gets the current one at the service start.Tests deleted or re-spelled (#5833; each had a spelling rule, not a behaviour one)
QueryStoreDatabaseWatermarkWiringPinTests.TheCommit_IsCalledOnce_...(source positions; behaviour covered byDatabaseWatermarkCacheRunnerLiveTestsand the commit-order test above);DeadlocksTelemetryCursorTests.BothHosts_LandStagedItemState_...(source order;DeadlocksAzureLoopStateLiveTestsruns the loop); the substring block ofQueryStoreAzureTextAndPlanFetchTests.BothReadSitesShareOneFetchMethod_...;DarlingEmptyEnumerationNoteTests.Runner_Carries_The_Note_Onto_The_Success_Return_Too(dev replaced it with the behaviour testDarlingRunnerCollectionNoteLiveTestsin Darling runner: check whether a collector note is dropped before the success return #6356; the merge took dev's side).PasswordBindingCensusTestsallowlistsConnectionIdentity(the binding itself, reason in the test);CollectionLogPhaseSplitStoreTeststripwire 32 to 33 parameters;PostgresTargetConfigTests.EveryRoundTripCriticalServerPropertyHasAStoreColumngainedQueryStoreReplica;AlertNotebookTableCatalogTestsnames the two helpers that writeread_sourceinto the serialized payload;UnpooledDropConnectionCensusTestsallows the live test's scratch-database drop.Gates (earlier tips; the final tree's gate is in Final-tree results)
[guard-darling] Total: 4441, Failed: 0,[guard-lite] Total: 1196, Failed: 0,GATE STOP: selection is the whole suite (darling 2247, lite 707).[guard-darling] Total: 4441, Failed: 0,[guard-lite] Total: 1196, Failed: 0,GATE STOP(whole-suite selection);*CollectionHealthReadSource*,*ViewerSchemaVersionGate*,*CollectionLog*,*Census*: 651 run, 0 failed, 1 skipped; Lite*CollectionHealthReadSource*,*CollectionLog*0 failed.[guard-darling] Total: 4442, Failed: 0,[guard-lite] Total: 1196, Failed: 0,GATE STOP.Not run, parked, or still open
Darling.Tests.QueryStoreReplicaAgFixtureLiveTests,Lite.Tests.QueryStoreReplicaAgFixtureLiveTests,QueryStoreReplicaReadsLiveSqlTests. They passed on the fixture in earlier lanes, before the merge with dev and the later edits.bash .github/scripts/pre-push.shon the final tree.local-classeslist for Darling (the part that ran is above), and the full suite once.ViewerQueryStoreReplicaTests(the old code passed null, so the first two facts fail by construction; not run). Revert proofs for the V213 live tests (a) drop theADD COLUMN, (b) drop the replica term fromv_connection_changed: not run (the R9-18 test is its own before and after).np:pipe path is accepted as a host over MCP, the web and the Viewer. A different field with wider reach; needs its own change. No issue filed tonight.Security round fixes (L1-L3) and #6357 alignment
Claude posting for Erik Darling. Final security round on this PR: no Blocking, High or Medium; three Lows and a nit, plus aligning the replica with the host rule that #6357 (#6352) merged to dev. Merged
origin/dev(one conflict, in the Viewer'sAddServerDialog.xaml.cs; kept both the host refusal and the replica refusal).DarlingMcpServerAdminTools.Edit.cs). The in-transaction recheck of a scoped agent route readplan.AddressChanged && ..., so a replica-only edit skipped it. It now readsplan.MovesConnection(address or replica). Test:ServerEditLiveTests.ARouteSetUpDuringTheConnectionTest_RefusesTheEditAndTheAdd_AndWritesNothinggained a replica-only edit during whose probe a scoped route is inserted for the server; the edit is refused and no replica is stored.QueryStoreReplicaSetting.Refusal). The host part is now judged byServerHostSetting.Refusal, which trims the protocol word the way SqlClient does, sonp :...andnp<tab>:...are refused (alsotcp:np<tab>:x). Kept the replica-specific checks (empty aftertcp:, a secondtcp:) and the replica sentences. A bare IPv6 literal is now accepted as the host rule accepts it. Tests:QueryStoreReplicaSettingTests.A_replica_with_a_protocol_prefix_or_a_path_is_refusedgainednp :,np<tab>:andtcp:np<tab>:.MonitoredServerConnection.BuildQueryStoreReplicaConnectionStringsets the Data Source throughServerHostSetting.TcpDataSource: a remote replica getstcp:, a local one and one that already names a protocol keep what they have. Tests:A_remote_replica_gets_tcp_in_front_and_a_local_one_does_not; the two older tests that read the Data Source now expecttcp:.ServerManager.ReplicaRefusalholds the replica to the host rule.AddServerrefuses a bad replica;UpdateServerrefuses a changed one and lets an unchanged one through, using a new_savedReplicassnapshot kept beside_savedHosts(loaded, added, updated, imported and removed together), so it also holds when the edit dialog has changed the live object before the call;ImportServersFromFileskips the entry and logs the refusal, counted as skipped like a host refusal. The Add/Edit dialog refuses on Test and on Save (an edit that keeps its replica is not held).ServerConnection.WithQueryStoreReplicaHosttakes its Data Source throughTcpDataSource. Tests inLite.Tests/ServerHostRefusalTests.csfor add, update (parameter object and live object), a legacy replica staying editable, and an imported Windows-auth entry withnp:\\attacker\pipe\sql\query;QueryStoreReplicaSettingTests(Lite) expecttcp:.PgMigrations.csmoved from between V209's summary andV209Sqlto sit aboveV213Sql. Comment move only; no migration change.ServerEditLiveTests.AsTheMcpRole_AddServers_HoldsTheReplicaHost_AndRefusesAProtocolPrefixlistedtcp:attackeras a refused replica, but the Query Store collectors: a setting to read from a readable replica that mirrors the primary #6254 ruling allowstcp:; it now liststcp:tcp:attacker(a double prefix, which is refused).Not changed: the Lite dialog wiring itself has no UI test (it calls
ServerManager.ReplicaRefusal, which the behaviour tests cover).Test lines
TcpDataSourcechange reverted to38929546f,ARouteSetUpDuringTheConnectionTest...fails andQueryStoreReplicaSettingTestsshows 8 failures (thenp :,np<tab>:,tcp:np<tab>:cases and thetcp:Data Source cases); with Lite'sWithQueryStoreReplicaHostreverted, 3 LiteQueryStoreReplicaConnectionStringTestsfail. The LiteServerManagerrefusals were not reverted separately (their tests do not compile withoutReplicaRefusal).bash .github/scripts/pre-push.sh:[guard-darling] Total: 4442, Errors: 0, Failed: 0, Skipped: 0;[guard-lite] Total: 1196, Errors: 0, Failed: 0, Skipped: 0;classes: darling 204, lite 65; endedGATE STOP: selection is the whole suite (CI's map answers FULL), so no selected class ran from the gate.*QueryStoreReplica* *ServerHost* *ServerEdit* *Census* *Ratchet* *DarlingMcpServerAdminTools* *Migration*: Total 1603, Failed 0, Skipped 4. Lite*ServerHostRefusal* *QueryStoreReplica* *ServerManager* *Census* *Ratchet*: Total 88, Failed 0, Skipped 1.Test Connection checks the replica; runner read-source test
Claude posting for Erik Darling. Two gaps the finish lane parked.
Gap 1: Test Connection now opens the Query Store replica
A server that names a replica is tested on two connections. The replica one is built by the same builder collection uses (
MonitoredServerConnection.BuildQueryStoreReplicaConnectionStringfor Darling,ServerConnection.WithQueryStoreReplicaHostfor Lite: replica host,ApplicationIntent=ReadOnly, the server's own login and auth) and opened with aSqlConnection, the connection the SQL Server target provider gives collection. A replica that will not open is a warning with its error. The test, the save and the add still succeed, because collection reads the primary.DarlingServerConnector.ProbeAsync): after the primary connects it calls the newProbeQueryStoreReplicaAsync;ConnectionProbeResultgets trailingQueryStoreReplicaandQueryStoreReplicaError. No replica, PostgreSQL targets and Azure SQL Database open nothing.DescribeQueryStoreReplicawords the line once.test_connectcommand (the Viewer's dialogs and the MCPtest_connectiontool, which already queues the storedqueryStoreReplica): the reply addsqueryStoreReplica,queryStoreReplicaErrorandqueryStoreReplicaNote; status staysconnected. Viewer: both Add-server dialogs append the note (ViewerDataService.ProbeReplicaNote), so the result reads "Connected - ... . Query Store replica h: connected." or "... could not connect (error). Collection reads Query Store from the primary until it can."--test-connectionCLI: the PASS line is followed by "Primary: connected. Query Store replica h: ..." and a failing replica is[WARN], never[FAIL](exit code unchanged).add_servers/edit_server, and the web admin edit: the add detail and a newquery_store_replica_testfield on the edit reply carry the replica result. Over MCP and web the reply has a fixed sentence and the driver text goes to the service log (redacted, bounded) asConnectFailureReplydoes; the--add-serververb shows the text.admin.jsinterpretEditappends it to the "Saved" notice.QueryStoreReplicaConnectionTest, device-code rendezvous handled). Only the Test button handler in the dialog changed, plus one method.Gap 2: ReadSource through the real runner's success return
QueryStoreReplicaRunnerLiveTestsalready runsDarlingCollectorRunner.RunAsync(QueryStoreCollector.Instance, ...)on a fake target and assertsresult.ReadSource:"replica 1, primary 0"for a replica-routed database, and"replica 0, primary 1 (replica_unavailable: <db>)"for a replica that cannot be opened or whose read fails;nullwith no setting. So no new test was needed. Revert proof: changing the success return toReadSource: nullfails 3 of the 7 (AReplicaRead_StoresALateRow...,AReplicaThatCannotBeOpened_...,AReplicaReadThatFails_...); restored, 7 of 7 pass (rig 55517).Tests
Darling.Tests.QueryStoreReplicaConnectionTestTests(12): the probe opens the string the builder gives (replica host, read-only intent), an error is returned and not thrown, a real closed port gives an error, none/PostgreSQL/Azure open nothing, cancellation propagates, the CLI line, thetest_connectreply read by the Viewer helper, add and edit replies (fixed sentence, driver text and password only in the log or, for--add-server, redacted).AdminServerEditBehaviourTestsgets theinterpretEditnotice fact (Node harness).Lite.Tests.QueryStoreReplicaConnectionTestTests(4).bash .github/scripts/pre-push.sh, before the last merge of the issue branch):[guard-darling] Total: 4442, Failed: 0,[guard-lite] Total: 1196, Failed: 0, thenGATE STOP(whole-suite selection, CI's map answers FULL).*QueryStoreReplica* *Census* *Ratchet* *AdminServerEdit* *DarlingCommandExecutor* *ConnectionTestReply* *DarlingCliCommands* *DarlingMcpServerAdmin* *ServerEdit* *AddServer*: 1432 run, 1 failed (ServerEditLiveTests...HoldsTheReplicaHost, because the setting now allowstcp:; the other lane fixed the same line, taken at merge). After the merge: the new classes (Darling 12, Lite 4),ServerEditLiveTests, the runner live tests and the executor/CLI/reply classes pass. Two assertions that the data source equals the host were changed to expect thetcp:prefix the builders add.SqlConnectionpath is covered with a closed local port only). A test with a replica adds up to one more connect timeout to the test (about 15 s), so a slow replica can push the Viewer's 30 s per-row bulk limit.CI fix (run 39697777426)
AgentLevel2StoreGrantLiveTests.TheBringYourOwnScript_GrantsTheSameMatrix_AndSkipsOnAStoreBehindTheRung, 42703s.query_store_replica): theconfig.edit_monitored_server18-argument function reads.query_store_replicaand wrotequery_store_replica = ...unguarded, so a viewer edit on a store without the column failed. It now reads the column throughto_jsonb(s) ->> 'query_store_replica'(the way the V203 columns are read) and picks one of two UPDATEs by apg_attributecheck, so the replica is left out of the write on a store without the column. Same change inDarlingManagedRoles.cs. Thetest_connectrequest function now reads it throughto_jsonbin the managed SQL, and the script's copy now carriesqueryStoreReplicatoo (it was missing from the script).DeadlocksTelemetryCursorLiteTests.BothHosts_LandStagedItemState_OnlyAfterTheItemsWrite_AndDropItBeforeTheRead: the rule still holds, only the matcher missed the new shape.ReadQueryStoreItemAsync(the replica attempt and the primary read a failed replica read falls back to) callscontext.DropStagedItemState()as its first statement (DarlingCollectorRunner.cs~3867), beforeBuildPerItemQuery(item, context), andreadItem(~4042) reaches it on both the replica path and the fallback path, so every attempt drops first. The pin now starts its search at the helper (and assertsreadItemcalls the helper beforeonItemComplete); Lite's host keeps the old shape. Not a spelling pin, so it was moved, not deleted.ViewerJobHistoryColumnBehaviourTests);git diff --stat origin/dev...HEAD -- '*JobHistory*' '*StaTestThread*'is empty.*AgentLevel2Store* *ProvisionRoles* *ManagedRoles*Total 92, Failed 0, Skipped 0;*ServerEdit* *ServerAddViewer* *StoreServerPasswordRules* *AwsPerServerRole* *ProvisioningSerialization* *QueryStoreReplica*Total 758, Failed 0, Skipped 4; Lite*DeadlocksTelemetryCursor*Total 10, Failed 0.[guard-darling] Total: 4442, Failed: 0,[guard-lite] Total: 1196, Failed: 0,classes: darling 222, lite 57, thenGATE STOP(CI's map answers FULL); CI decides the rest.CHANGELOG entry
SECTION: Added
ENTRY: - Read Query Store from a readable replica, and stop losing late rows from a capturing secondary ([#6254], [#6279]) - A SQL Server target can name a readable Availability Group secondary (a host,
host,portor a listener name) as its Query Store replica, in darling.json asqueryStoreReplica, in the Viewer and web admin edit forms and the MCPadd_serversandedit_servertools, and in Lite's Add Server dialog as "QS replica". The collector then reads Query Store from that replica for each database that is safe to read there (a readable non-capturing secondary of the same group, redo not behind, same version, flush interval 900 seconds or less) and from the primary for the rest, or when the replica cannot be reached. A replica read re-reads a short look-back window and drops rows it already stored, so the replica's lag cannot lose or duplicate rows. The collection log records where each Query Store run read from (read_source, for examplereplica 12, primary 2 (redo_behind: Sales)), shown inget_collection_health,get_collection_log, the web Collectors table, the Viewer and Lite collection health and log grids. The replica host is part of the saved password's binding, so a password saved for one replica is never sent to another; a replica change asks for the password again. A capturing secondary (FOR SECONDARY SET QUERY_STORE = ON) is now detected even with no replica set, and its late rows are no longer skipped. Azure SQL Database ignores the setting. Darling store version 213 and Lite's DuckDB version 78 add one nullable column each.SECTION: Important
ENTRY: - Upgrading Darling to the Query Store replica release runs store migration V213 on the first start, and a bring-your-own store needs one step after it ([#6254]) - V213 adds two nullable text columns,
config.config_monitored_servers.query_store_replicaandcollect.collection_log.read_source, with no default and no backfill, so each is a catalog-only change that rewrites no table, and it rebuilds thecollect.v_collection_logview so readers see the new column. An older build ignores both columns, so there is nothing to do before rolling back. A store whose roles you provisioned yourself ("bring your own") must runDarling/tools/provision-roles.sqlagain after the upgrade. The service replaces the threeconfig.edit_monitored_serverfunctions at each start when its login owns them and warns once, naming the script, when it does not; but the new 18-argument function and theviewerandmcpgrant onquery_store_replicacome only from the script. Until it is re-run, editing a server from the web dashboard or the MCPedit_servertool fails withpermission denied; collection and reads keep working. The compose store and managed provisioning do this on their own.REF: [#6254]: #6254
REF: [#6279]: #6279