Repository navigation
Place the Query Store slicer overlay where the work ran (#1921, option 1) - #1938
Conversation
…n 1) #1841 moved the slicer BARS to the hour an interval ran and left the OVERLAY on the hour it was collected, so a point sat up to one Query Store interval -- 60 minutes by default, 1440 at most -- to the RIGHT of the bar describing the very same work. The file's own comment stated the invariant this broke: its dedup half kept holding, its placement half silently stopped. Both apps now place the overlay on COALESCE(interval_start_time_utc, collection_time), and the WINDOW moves with the x-value -- filtering on collection time while plotting on the interval start disagrees at both edges AND disagrees with the bars about whether an interval is in the window at all. Same collection_time pruning bounds the slicer already ships (#1892/#1923); the issue's own "no ceiling" scope note predates that round. ORDER BY moves too, or the chart gets a non-monotonic series and draws a zig-zag rather than a curve. TWO THINGS THE INSTRUCTION DID NOT ANTICIPATE, both verified before acting: - The nominated watched-red pin does not move. Its rows are seeded WITHOUT an interval identity, so COALESCE correctly falls back to collection_time and the point stays at bucketStart+15m. Rather than force it, its assertion is unchanged and now pins the legacy FALLBACK, and the behavioral proof went into the tier-2 test whose rows can actually show the move -- where the interval starts in h0 and every collection lands in h1. Watched red there: reverting the x-value fails it with "Expected 06:00, Actual 07:10", the 70-minute offset, and the legacy pin keeps passing. Both generations are now covered by the test whose data can demonstrate each. - Lite has no dedicated overlay read. It fed the overlay from the history GRID's read, which is a deliberately raw per-collection projection (#1845) windowed on collection time -- right for a list of collection events, wrong for a series drawn over the bars. Moving that shared read would have silently changed the history grid too, so Lite gained a dedicated read instead, matching the structure Darling has had since #683. It picks up the DEDUP the overlay never had: an interval collected repeatedly used to draw as a rising staircase of restatements and now draws as one point at its final values, which is what the bar beside it always showed. That is the same invariant, not extra scope. Both source guards caught the new read and were updated deliberately (5 -> 6 dedup sites, 7 -> 8 rank filters, tie-break total 7 -> 8) -- the #1912 guard fired from the direction of a read being ADDED rather than edited, which is what it was written for. Darling 4033 passed / 0 failed against live PG 18.4 + TSDB 2.28.1, Lite 1937 passed / 0 failed, full -t:Rebuild 0 warnings / 0 errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review: Place the Query Store slicer overlay where the work ran (#1921)OverviewFixes the overlay/bar disagreement introduced by #1841: the slicer bars moved to CorrectnessTraced the math through both the DuckDB-backed live test (
ParityVerified both apps received matching treatment:
Minor findings (posted inline)
Overall this is a very thorough, well-tested fix with good live-DB coverage on both apps; no correctness or parity issues found beyond the nits above. |
| @@ -1,4 +1,4 @@ | |||
| /* | |||
| /* | |||
There was a problem hiding this comment.
This file (and Lite.Tests/QueryStoreDedupReadTests.cs, Lite.Tests/QueryStoreSliceTieBreakSourceTests.cs) picked up a leading UTF-8 BOM in this PR — confirmed absent on the merge-base revision (git show 4405108:<path> | head -c 3 is empty; this PR's head has EF BB BF). It's harmless for the C# compiler, but it's inconsistent with the rest of the codebase and is likely an accidental editor/tool artifact worth stripping before merge.
| COALESCE, not a bare column: rows collected before #1841 tier 2 have no interval start, | ||
| and they keep collection_time placement, which is the same two-generation handling every | ||
| other post-#1841 read uses. */ | ||
| COALESCE(interval_start_time_utc, collection_time) AS point_time, |
There was a problem hiding this comment.
Minor naming nit: the query now aliases this as point_time, but it's read back into the shared ItemTimelinePoint.CollectionTime field (declared above at line 34-35). For Query Store rows that field can now hold the interval start rather than the actual collection time, while Lite's brand-new QueryStoreItemTimelinePoint names the equivalent concept PointTime. Understandable given ItemTimelinePoint is shared with the still-collection-time-based query_stats/procedure_stats overlays, but worth a look for clarity (e.g. a distinct record, or renaming the field) since "the point is placed where the work ran, not observed" is the whole thesis of this PR.
| @@ -1,4 +1,4 @@ | |||
| /* | |||
| /* | |||
There was a problem hiding this comment.
Same accidental leading UTF-8 BOM as Lite/Services/LocalDataService.QueryStore.cs and Lite.Tests/QueryStoreSliceTieBreakSourceTests.cs — absent on the merge-base revision, worth stripping.
| @@ -1,4 +1,4 @@ | |||
| /* | |||
| /* | |||
There was a problem hiding this comment.
Same accidental leading UTF-8 BOM as Lite/Services/LocalDataService.QueryStore.cs and Lite.Tests/QueryStoreDedupReadTests.cs — absent on the merge-base revision, worth stripping.
|
PR UP — #1938, all checks green, Two things the brief didn't anticipate, both verified before acting rather than worked around: 1. The nominated watched-red pin does not move — and shouldn't. 2. Lite has no dedicated overlay read. It fed the overlay from the history grid's read — a deliberately raw per-collection projection (#1845) windowed on collection time, right for a list of collection events and wrong for a series drawn over the bars. Moving that shared read would have silently changed the history grid too, a surface outside this issue. Lite gained a dedicated read instead, matching Darling's structure since #683 — and it picks up the dedup the overlay never had, so a repeatedly-collected interval now draws as one point at its final values instead of a rising staircase. Same invariant, not extra scope. Also followed your correction over the issue text: the Both source guards caught the new read and were updated deliberately (5→6 dedup sites, 7→8 rank filters, tie-break total 7→8) — the #1912 guard fired from the direction of a read being added rather than edited, which is what it was written for. Darling 4033/0 against live PG 18.4 + TSDB 2.28.1, Lite 1937/0, |
…to PointTime The field holds the interval start for Query Store rows since #1921, so the old name lied for exactly the series this PR changed. PointTime is truthful for every series: the per-collection siblings' point time IS their collection time. Lite's new record already used the honest name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ReviewOverview: Fixes #1921 by placing the Query Store slicer overlay on Correctness
Lite/Darling parity
Style/observations (non-blocking)
No correctness, parity, or security issues found. The test coverage (behavioral pin using data that can actually distinguish the two placements, plus updated source-shape guards) is a good match for a bug that's easy to silently reintroduce. |
Fixes #1921. Erik's call: option 1 — the overlay becomes work-placed-when-it-ran.
The disagreement
Selecting a Query Store row overlays that item's curve on the slicer. #1841 moved the bars to the hour an
interval ran and left the overlay on the hour it was collected, so a point sat up to one Query Store
interval — 60 minutes by default, 1440 at the maximum — to the RIGHT of the bar describing the very same work.
The file's own comment stated the invariant that broke:
Its dedup half kept holding. Its placement half silently stopped.
The fix
Both apps place the overlay on
COALESCE(interval_start_time_utc, collection_time)— the same expression thebars use — and pre-tier-2 rows keep collection-time placement, the same two-generation handling every other
post-#1841 read uses.
The window moved with the x-value, which is the part that would otherwise still disagree. Filtering on
collection time while plotting on the interval start disagrees at both edges — an interval that started
before the window but closed inside it draws a point to the left of the range, and the window's own last
interval, whose closing fetch lands after the range ends, vanishes — and worse, it disagrees with the bars
about whether an interval is in the window at all. Same
collection_timepruning bounds the slicer alreadyships (#1892/#1923); the issue's own "no ceiling" scope note is stale, written before that round added one.
ORDER BYmoved too — ordering on collection time while plotting on the interval start hands the chart anon-monotonic series, which a line control draws as a zig-zag rather than a curve.
Two things the brief didn't anticipate
1. The nominated watched-red pin does not move — and shouldn't.
QueryStoreAggregates_CountARecollectedIntervalOnce_...seeds its rows without an interval identity(
intervalId/intervalStartUtcdefault to NULL), soCOALESCEcorrectly falls back and the point stays atbucketStart+15m. It passed before and after the change.Rather than force it, its assertion is unchanged and now pins the legacy fallback, and the behavioral
proof went into
QueryStoreIntervalIdentity_PlacesWorkWhenItRan_..., whose tier-2 rows genuinely disagree —the interval starts in
h0and every collection lands inh1. Watched red there: reverting the x-value failsit with
Expected 06:00, Actual 07:10, the 70-minute offset, while the legacy pin keeps passing. Bothgenerations are now covered by the test whose data can actually demonstrate each.
2. Lite has no dedicated overlay read.
It fed the overlay from the history grid's read — a deliberately raw per-collection projection (#1845)
windowed on collection time. That is right for a list of collection events and wrong for a series drawn over
the bars, and moving the shared read would have silently changed the history grid's window too, a surface
outside this issue.
So Lite gained a dedicated overlay read, matching the structure Darling has had since #683. It picks up the
dedup the overlay never had: an interval collected repeatedly used to draw as a rising staircase of
restatements and now draws as one point at its final values — which is what the bar beside it always showed.
That is the same invariant ("the overlay agrees with the bars"), not extra scope.
Testing
Watched red on the behavioral pin, as above. Both source guards caught the new read and were updated
deliberately — 5 → 6 dedup sites, 7 → 8 rank filters, tie-break total 7 → 8. The #1912 tie-break guard fired
from the direction of a read being added rather than edited, which is exactly what it was written for.
New live-DuckDB pin
Overlay_PlacesPointsWhereTheWorkRan_AgreeingWithTheBarsItIsDrawnOverasserts theinvariant as an assertion rather than a comment: same x and same value as the bar computed from the same rows.
Sibling timelines are untouched and correctly so —
query_stats/procedure_statsare per-collection deltaswith no interval concept, so they stay on
collection_time.Suites: Darling 4033 passed / 0 failed against live PostgreSQL 18.4 + TimescaleDB 2.28.1 (10 skipped =
gated); Lite 1937 passed / 0 failed. Full-solution
-t:Rebuild: 0 warnings, 0 errors.Installer.Tests deliberately not run.
Accepted costs, recorded so they aren't re-reported as bugs
The overlay can no longer show WHEN a value was observed — a real thing to want, and what option 2 would have
kept — and several intervals from one collection cycle now spread across the x-axis instead of stacking.
Spreading is the truthful picture: those intervals really did run at different times.
Deferred
None.
🤖 Generated with Claude Code