Repository navigation
Route the Query Store trend from cached rollup coverage instead of a fresh min/max(bucket) read - #4618
Merged
Conversation
…fresh min/max(bucket) read The trend router (get_query_store_duration_trend and the desktop Query Store chart) used to run min(bucket)/max(bucket) over the corrected hourly rollup on every call to decide the raw/rollup boundary. Both callers already hold a RollupCoverage from the coverage probe they share with every other reader of the store, so the resolver now reads its floor and measured ceiling from that cache first, and falls back to the live probe-and-read only when the coverage carries no ceiling for the view (cold or unknown coverage). Routing decisions and the raw-only fallback are unchanged; the ceiling IS the same exclusive boundary the pure decision computes from max(bucket) + bucket width, so no adjustment is needed when reading it from the cache. Refs #4611. Builds on the rollup-ceiling change from #4605.
…roof Pure pins for RollupCoverage.CeilingOf: an unknown view answers null, the six-argument MergeRollupFloors overload replaces a re-measured view's cached ceiling while a view reused from cache keeps its own, and the five-argument overload keeps leaving every new entry's ceiling unset. A live pin against a real TimescaleDB seeds six hours of raw query_store_stats, refreshes the hourly rollup to now minus two hours, and checks CeilingOf against the engine's own materialization watermark. A further refresh and a forced re-measure show the ceiling advancing; an immediate follow-up call inside the cache's reuse window shows it holding steady. Reworded a comment that used 'ruling' to 'decision' for plain wording.
…trend-bounds-cached
QueryStoreTrendRoutingCachedLiveTests proves the coverage-taking overload of QueryStoreTrendRouting.ResolveAsync issues no fresh min(bucket)/max(bucket) probe against query_store_stats_corrected_hourly once a caller already holds a RollupCoverage with a measured ceiling for that view, and that it reaches the same routing decision the uncached probe-and-read overload does for the same store. Counted through pg_stat_statements against the actual call, the same discipline StoreSizeCacheLiveTests uses.
erikdarlingdata
marked this pull request as ready for review
September 28, 2026 17:50
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4611. Also carries #4605's rollup materialization ceiling (
RollupCoverage.CeilingOf), which this uses.Summary
get_query_store_duration_trendand the desktop Query Store trend chart used to runmin(bucket)/max(bucket)overquery_store_stats_corrected_hourlyon every call, to decide where the rollup-served region ends and the raw tail begins. Both callers already probe and cache aRollupCoveragefor the whole store on a timer, and that coverage now carries each rollup's measured materialization ceiling. This change reads the floor and ceiling for the corrected hourly rollup out of that cache instead of issuing a fresh bounds query on every trend load.The ceiling (#4605)
RollupCoveragegains a per-view materialization CEILING: the instant at or above which a rollup holds nothing yet, read from the engine's own watermark (or amax(bucket) + bucket widthfallback) in the same probe cycle that measures the floor, and cached the same way.CeilingOfanswers null for a view that was never probed, is absent, or has materialized nothing — the safe "unknown" reading that keeps a caller on the raw path. Its own pins: an unknown-view pin, a cache-merge pin, and a live test against a real TimescaleDB (RollupCoverageCeilingLiveTests) proving the ceiling matches the engine's watermark, advances on a further refresh, and holds while the cache's reuse window is still open.What changed
QueryStoreTrendRoutinggets aResolveAsync(RollupCoverage, NpgsqlDataSource, CancellationToken)overload. When the coverage carries a ceiling forquery_store_stats_corrected_hourly, it builds the route directly fromCeilingOf/FloorOf— no store round trip.CeilingOfis already the exact exclusive boundary the old pure decision computed asnewest bucket + bucket width, so it becomesRawStartUtcwith no adjustment.ResolveAsync(NpgsqlDataSource, ...)probe-and-read path unchanged, so a cold or unknown coverage is never less correct than before.DarlingMcpTrendTools) and the desktop viewer (ViewerDataService.QueryTrends) both now fetch their already-cached coverage first and pass it in, instead of letting the resolver probe on its own.Test plan
Run on a local TimescaleDB rig (
timescale/timescaledb:2.30.1-pg18,shared_preload_libraries=timescaledb,pg_stat_statements):QueryStoreTrendRoutingCachedLiveTests.ResolveAsync_WithACachedCeiling_IssuesNoBoundsProbe_AndMatchesTheFallbackRoute: seeds two hours of Query Store snapshots, materializes the rollup, resolves the route once through the uncached probe-and-read overload as the reference answer, then resolves it again through the coverage-taking overload and asserts the two routes agree (UseRollup,RawStartUtc,RollupFloorUtc) and thatpg_stat_statementsshows ZERO calls matching themin(bucket)/max(bucket)bounds statement after the cached call — the samepg_stat_statementscounting disciplineStoreSizeCacheLiveTestsuses against the product's own call path, not a mock.Total: 1, Failed: 0.Total: 1, Failed: 1. Reverted after confirming; the branch is clean.QueryStoreTrendRoutingTests,QueryStoreTrendRoutingLiveTests,QueryStoreTrendRoutingCachedLiveTests,RollupCoverageCeilingLiveTests,RollupCoverageRoutingTests,StorageCommandTimeoutTests,StartupCommandTimeoutTests:Total: 98, Failed: 0.CommentFilterAdoptionTests,DocCommentHygieneTests,McpPayloadContractCensusTests, every*Census*class):Total: 355, Failed: 0.McpToolsListBudgetTests:Total: 5, Failed: 0.Darling.Tests, the Darling Viewer, and the Darling Service each build with-p:EnableWindowsTargeting=trueat 0 warnings, 0 errors.Lite.Tests: no Lite/DuckDB code changes here, so no Lite class is affected.CHANGELOG
SECTION: Changed
ENTRY: - The Darling Viewer's Query Store trend chart and
get_query_store_duration_trendno longer re-scan the hourly rollup's oldest and newest hour on every load ([#4618]) - They take the rollup's range from the coverage the service already caches, instead of a freshmin/maxread over the rollup on every call.REF: [#4618]: #4618