Skip to content

Plan analysis: Bare Scan rule and Serial Plan benefit formula (#4526, #4528) - #4545

Merged
erikdarlingdata merged 1 commit into
devfrom
plan-sync/4526-4528
Sep 28, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
plan-sync/4526-4528

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4526
Fixes #4528
Part of #4511

Why

PerformanceStudio has two small plan-analysis improvements that PerformanceMonitor's PerformanceMonitor.PlanAnalysis project hadn't picked up yet: a new rule for a full-table scan with no predicate, and a corrected formula for the Serial Plan benefit estimate.

What changes

Rule 34, "Bare Scan" (PlanAnalyzer.cs): flags a Clustered Index Scan or heap Table Scan that has no predicate and is not a lookup.

  • An actual plan fires when the scan's own elapsed time is above 0ms; an estimated plan fires when the scan costs at least 20% of the plan.
  • Three or fewer output columns suggests a covering nonclustered index. More than three columns suggests a columnstore index instead, because column count doesn't make a columnstore index more expensive the way it does a rowstore index.
  • Added to BenefitScorer.OperatorTimeRules so it gets a benefit score, the same as the other scan rules.

Serial Plan benefit formula (BenefitScorer.cs): replaces the two-branch estimate (a flat 75% when CPU-bound, otherwise the CPU/elapsed ratio times 75% capped at 50%) with a single formula: (cpu * (DOP - 1) / DOP) / elapsed * 100, assuming a DOP of 4, capped at 100%. A statement whose subtree cost is under 1 gets no benefit at all, since a trivial plan doesn't gain from parallelism.

Both changes mirror PerformanceStudio's own commits behavior-for-behavior, adapted only for the difference that this project has no AnalyzerConfig, so there is no rule-disable guard.

Test plan

New class: Darling/Darling.Tests/PlanSync4526And4528Tests.cs, two test classes:

  • PlanSync4526Tests: a bare heap scan with an actual own time and 2 output columns gets the covering-index advice; a bare clustered-index scan with 5 output columns gets the columnstore advice; a scan with a predicate gets no warning; an estimated plan at 25% of cost warns, one at 10% does not.

  • PlanSync4528Tests: cpu 900 / elapsed 1000 / cost 5 scores 67.5% (the old formula capped this case at 50%); cpu 2000 / elapsed 1000 / cost 5 scores 100% (the old formula gave a flat 75%); a statement with subtree cost 0.5 gets no benefit at all (the old formula had no such gate).

  • Runtime RED on dev: 6 of the 8 new facts fail there (the two "no warning" facts pass on dev, as they should): both Bare Scan warn facts and the estimated-plan fact, and all three Serial Plan benefit facts.

  • Mutations, each reverted: the narrow-output threshold colCount <= 3 → <= 0 fails the covering-index fact; removing the StatementSubTreeCost >= 1.0 gate fails the trivial-cost fact.

  • Run: the new classes plus the plan-analysis classes: Total: 239, Failed: 0, Skipped: 2 (the live-Postgres plan-tool class, which runs in CI). Lite.Tests builds. No test enumerates warning types, so no census pin changed.

Note: this PR also ports PerformanceStudio's single Serial Plan benefit formula into BenefitScorer. No entry point calls the scorer yet (#4546), so that part has no visible effect until it does. The user-facing entry for benefit values belongs with that change.

CHANGELOG

SECTION: Added
ENTRY: - Plan analysis: added the "Bare Scan" finding for a full-table or full-clustered-index scan with no predicate ([#4545]) - A Clustered Index Scan or heap Table Scan with no predicate now gets a covering-index or columnstore suggestion depending on its output width.
REF: [#4545]: #4545

…#4528)

Rule 34 "Bare Scan" flags a Clustered Index Scan or heap Table Scan with no predicate that isn't a lookup. An actual plan fires when the scan's own time is above 0ms; an estimated plan fires at 20% or more of the plan's cost. Three or fewer output columns suggests a covering nonclustered index; more columns suggests columnstore, since column count doesn't penalize a columnstore index the way it does a rowstore one.

The Serial Plan benefit estimate is replaced with a single formula: (cpu * (DOP - 1) / DOP) / elapsed * 100, assuming a DOP of 4, capped at 100%. There is no benefit when the statement's subtree cost is under 1, since a trivial plan doesn't gain from parallelism.

Fixes #4526
Fixes #4528
Part of #4511
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