Skip to content

Query Store item-timeline overlay plots at collection_time while the bars it annotates are keyed on the interval start #1921

Description

@erikdarlingdata

Found while fixing #1892 and verified against the code; filed rather than folded in because the fix is a product decision about what the overlay is FOR, not a mechanical edit.

The disagreement

QueryStoreItemTimelineSql draws its points at collection_time:

  • Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.ItemTimeline.cs — projects collection_time as the x-value, windows on collection_time >= $5 / <= $6

That series is drawn over the Query Store slicer bars, which since #1841 are keyed on COALESCE(interval_start_time_utc, collection_time) — the hour the work RAN. The two axes are no longer the same instant, so a point sits up to one Query Store interval (default 60 minutes, max 1440) to the RIGHT of the bar describing the very same work.

The file's own comment states the invariant this breaks:

This series is drawn OVER the Query Store slicer bars, which ARE deduped (QueryStoreSlicerSql) — leaving the overlay raw would make the overlay disagree with the bars it annotates.

The dedup half of that invariant holds. The PLACEMENT half silently stopped holding when #1841 moved the bars and left the overlay where it was.

#1892 does not create this and does not worsen the x-axis, but it does add a second axis of disagreement: after #1892 the bars are windowed on the effective timestamp while the overlay is still windowed on collection_time, so at a range edge the two surfaces can disagree about whether a given interval is in the window at all.

Why this is a decision, not a mechanical edit

The overlay answers a genuinely different question from the bars. A bar says "this much work ran in this hour". An overlay point arguably says "at this moment, the collector observed these cumulative totals" — which is a real thing to want, and collection_time is the honest x-value for it.

So there are two defensible fixes and they are not equivalent:

  1. Move the overlay to the interval start, matching the bars. The overlay becomes "work, placed when it ran", consistent with everything else post-Query Store aggregates double-count re-collected cumulative intervals - tier 2: interval identity + CAGG rebuild (tier 1 read-side dedup shipped in #1845) #1841, and the file's stated invariant holds again. Cost: the overlay stops being able to show WHEN something was observed, and several points from one collection cycle for different intervals now spread across the x-axis instead of stacking.
  2. Keep collection_time and document the overlay as a collection-event series, updating the comment that currently claims agreement. Cost: the two series on one chart keep using different clocks, which is exactly the kind of thing that produces a support question.

Option 1 is probably right — it is the direction #1841 took everything else — but it changes what a shipped chart means, so it wants a deliberate call rather than being folded into an edge-case fix.

Scope when it is done

Both apps, and the window filter moves with the x-value (the #1892 shape: filter on the same expression you place on, keep a bare collection_time floor for chunk exclusion and no ceiling).

  • Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.ItemTimeline.cs
  • Lite's equivalent item-timeline read
  • The live pin ViewerQueriesTests.QueryStoreAggregates_CountARecollectedIntervalOnce_AtItsLatestValues_AgainstDevPostgres asserts the timeline point lands at bucketStart+15m (a collection time), so it moves with the decision.

Activity

  1. added 2 commits that reference this issue on Jul 31, 2026
  2. erikdarlingdata commented on Jul 31, 2026

    @erikdarlingdata
    OwnerAuthor

    Shipped in #1938, per Erik's option-1 ruling: the Query Store item-timeline overlay now plots at the interval start - work placed when it ran - matching the bars it annotates, with the window filter and ORDER BY moving with the x-value (the #1892 shape plus #1923's 30-day pruning ceiling). Both apps. Lite gained a DEDICATED overlay read rather than inheriting the history grid's raw per-collection projection, which converges the two apps' structure and fixed a second disagreement in the same feature: Lite's overlay never deduped, so a repeatedly-collected interval drew as a rising staircase of restatements - it now draws as one point at final values, which is what the bar beside it always showed. The legacy fallback (rows with no interval identity plot at collection_time) is pinned deliberately, and the behavioral proof lives in a test whose seeds genuinely discriminate (70-minute offset goes red on revert). Review round added: three accidental BOMs stripped and ItemTimelinePoint.CollectionTime renamed to PointTime, since the old name lied for exactly the series this change moved.

  3. added a commit that references this issue on Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions