Repository navigation
Split the query_store fetch phases into store, target and residual halves - #2812
Conversation
…lves (#2811) plan_fetch: and text_fetch: each time a whole METHOD spanning two databases - a store connection open, a touch/probe round trip against the store, at most two statements against the monitored server, and a store write. Three of the four steps are Postgres, so the blended number cannot say which held the time. On ayr-01 a 189,562ms plan_fetch (85% of a 224s run) was read as SQL Server query time for a full day, and the two measurements of that target statement disagree by two orders of magnitude while both being real: ~57,073ms CPU per execution against the collector's actual workload of cold missing plans, versus ~650ms for 400 ids in a controlled A/B over hot recently-executed plans. Both fetch methods now stamp probe / target / write separately, plus chunk and id counts so per-id cost is computable. other: is a computed residual rather than a fourth measurement, so the parts sum to the parent by construction; a large other: is itself the finding, meaning the cost sits in the collector's own bookkeeping rather than in either database. The sub-split rides its own log line. The existing phase line is byte-identical because tooling outside this repo parses it. Sub-phases clear on the same rule as their parents (before the operation, so a faulting item cannot print the previous database's split as its own) and are stamped in finally blocks, so a timed-out target statement or cancelled store write reports the time it burned rather than 0ms. Instrumentation only: no query, hint or retention change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MX6HyjsuDCs15qGB2rh4Gy
ReviewWent through this as an instrumentation-only change (no query/hint/retention behavior change) and didn't find correctness, security, or Lite/Darling parity issues. What I checked:
One minor naming note, not a bug: No inline comments to add. |
Fixes #2811
What
plan_fetch:Nmsandtext_fetch:Nmseach time a whole method, not a query.FetchAndStorePlansAsyncopens a store connection, round-trips the store to learn what it already holds, issues at most two statements against the monitored server, and writes the results back — three of those four steps are Postgres.FetchAndStoreQueryTextAsyncis identical in shape.Both now stamp
probe:/target:/write:separately, plus chunk and id counts, on their own log line.The measurement this settles
On
omega-01,plan_fetchwas 189,562 ms — 85% of a 224-second run — and was read as SQL Server query time for a day. Two measurements of that target statement disagree by two orders of magnitude and both are real:sp_QuickieStoreon OMEGA#2791 was tuned on the assumption the target statement dominated, measured 0.508 s in isolation, and moved production nothing. That is consistent with the target half being a rounding error but does not prove it — and the blended number cannot say. This is the #2312 argument one seam further down.
Log format
Existing line, byte-identical (tooling outside this repo parses
plan_fetch:(\d+)ms):New, additive, emitted only when the corresponding fetch actually ran:
other:is a computed residual, not a fourth stopwatch, so the parts sum to the parent by construction rather than approximately. A largeother:is itself a finding: the cost would be in the collector's own bookkeeping, in neither database.Design notes
finally, not after the block. A timed-out target statement and a cancelled store write are exactly the events this needs to describe; stamping only on success would reporttarget:0msfor them — "the target was free" is the misreading the whole change exists to end.Verification
Darling.Testsisnet10.0-windowsand cannot run on macOS, so both harnesses ran against the real built assemblies.1. Arithmetic (against the shipped
PerformanceMonitor.Collectors.dll) — 19/19 PASS, including the identityprobe + target + write + other == parent, the zero-clamp on stopwatch skew, and a pair proving the same 189,562 ms parent now yields opposite diagnoses (store-bound vs target-bound).2. IL witness — loads the shipped
PerformanceMonitor.Darling.Service.dlland resolves call tokens through the metadata tables to confirm all ten setters are present in the compiled async state machines. Deliberately not a string scan: the last binary witness in this repo returned "the fix never shipped" on both the box and the artifact because it decoded UTF-16 from offset 0 and only saw even byte offsets, while its positive controls passed inside the broken scan.3. Red-first. Reverting
DarlingCollectorRunner.cstoorigin/devand rebuilding:My first positive control was
Stopwatch.StartNew, which failed on the reverted build too — because the stopwatches are my code, so it proved nothing about the scanner. Replaced with a call that exists onorigin/dev, so on the reverted build both controls pass while all ten setter checks fail. Restored →ALL IL CHECKS PASS.Scope
Instrumentation only. No query change, no hint change (#2806 stays open), no retention change (#2809). Not deployed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01MX6HyjsuDCs15qGB2rh4Gy