query_stats and procedure_stats rank before they render (#1959) - #1961
Conversation
Field investigation on a 52-replica RDS fleet: both collectors spliced their expensive applies below the TOP, and the optimizer does not defer them - a captured plan showed dm_exec_text_query_plan executing 2,434 times to keep 200 rows (81% of the sweep), with 30-second timeout MISSES on big-cache boxes. Rank on the cheap DMV columns in a derived table first; run text/plan applies and the self-filter against survivors only. Field-validated 6.0x (7.7s -> 1.3s median, 99.2% row parity). The procedure_stats fix is tail insurance - free under the TOP, bounded past it - with the plan handle round-tripped from the varchar the payload already carries. Survivors-only ordering pinned structurally in both definition suites; live-executed verbatim on SQL Server 2025. Closes #1959 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| @@ -140,14 +140,25 @@ public void BuildQuery_PlanCaptureOffByDefault_NoPlanClauses_LiteParity() | |||
| [Fact] | |||
| public void BuildQuery_PlanCaptureOn_Standard_SplicesWholeModuleTextPlanIntoEveryBranch() | |||
There was a problem hiding this comment.
Nit: this test name (...SplicesWholeModuleTextPlanIntoEveryBranch) is now stale. Per the updated body/comment, the whole point of #1959 is that the render happens once, outside the ranked derived table — no longer "into every branch". Worth renaming (e.g. ...SplicesWholeModuleTextPlanOnceAfterRank) so the name doesn't contradict the assertions right below it.
| Assert.True( | ||
| collapsed.IndexOf("NOTLIKE", StringComparison.Ordinal) > derivedClose, | ||
| "the self-filter moved back inside the ranked derived table - the inner TOP's headroom exists because it runs post-ranking"); | ||
| Assert.Contains("TOP(300)", collapsed, StringComparison.Ordinal); |
There was a problem hiding this comment.
Test-coverage gap: AssertAppliesRunAgainstSurvivorsOnly is only invoked from the capturePlanXml: true (Darling) tests (here and in the Azure plan-on test). But the property it pins — dm_exec_sql_text and the NOT LIKE self-filter running after the ranked derived table closes — applies unconditionally, including Lite's default plan-off path (BuildQuery_Standard_JoinsPlanAttributes_WithExclusions / BuildQuery_Azure_SkipsPlanAttributes_RunsPerDatabase above). Since StandardQueryText/AzureSqlDbQueryText are shared verbatim between both flag states this isn't a live bug today, but a regression that broke the ordering only under CapturePlanXml = false would slip through untested. Worth calling the helper from those tests too.
Review: query_stats / procedure_stats rank-before-render (#1959)OverviewThis applies the same structural fix to both Lite/Darling parityNo drift — Correctness
StyleMatches the T-SQL conventions in CONTRIBUTING.md (uppercase keywords, lowercase non-abbreviated types, Minor findings (posted inline)
No security, injection, or missing-index concerns. Solid, well-tested change — the field-measurement rigor described in the PR body is reflected in the structural test assertions added here. |
…-only ordering on the plan-off paths too Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| AND st.text NOT LIKE N'%PerformanceMonitorLite%' | ||
| AND qs.last_execution_time >= DATEADD(MINUTE, -10, GETDATE()) | ||
| /*EXCLUSION_FILTER*/ | ||
| SELECT TOP (300) |
There was a problem hiding this comment.
Minor/optional: the inner TOP (300) headroom over the outer TOP (200) is a fixed constant sized for the self-filter (st.text NOT LIKE '%PerformanceMonitorLite%') to remove enough rows post-rank without starving the final 200. Since total_elapsed_time is a cumulative counter, a monitoring query polling very frequently (e.g. sub-10s interval against a fleet with many databases) could rank unusually high purely from execution volume, and if more than ~100 of the top-300-by-elapsed rows turn out to be self-noise, the final result silently comes back short of 200 rather than backfilling from the next-ranked candidates — no error, just fewer/different rows than before this change.
The CHANGELOG documents this tradeoff and cites 99.2% row-identity parity from field validation, so this reads as an accepted, measured risk rather than an oversight — flagging only in case a wider headroom (or a TOP (200) outer count no longer aiming for exactness) is worth a comment noting the failure mode is "quietly fewer rows," not a crash.
Review summaryWhat this does: Rewrites Parity: Because the fix lives entirely in the shared Correctness — traced in detail, no bugs found:
Tests: The new structural pinning assertions ( One thing worth a second look (posted inline): Out of scope but related (not required for this PR): No security, injection, or secrets concerns — all changes are static SQL text templates; existing parameterization ( |
The field investigation's validated rewrite, applied to both collectors and both variants each. Full mechanism, measurements, and caveats in #1959 and the CHANGELOG entry. Shape summary: derived table ranks on cheap DMV columns (query_stats inner TOP 300 for post-ranking self-filter headroom; procedure_stats exact), applies run against survivors only, procedure_stats' single outer render round-trips the handle via CONVERT(varbinary(64), ..., 1) so no raw column threads through the three branches and the stored payload is untouched. Survivors-only ordering pinned structurally in both definition suites (watched-red names the violation); both plan-on variants executed VERBATIM on SQL Server 2025 via the reflection-extracted emitted text. The SQL matrix on this PR is the four-engine proof.
🤖 Generated with Claude Code