Repository navigation
Compose: keep the NEWEST buckets when the row cap truncates, and say so (#1687) - #1724
Merged
Merged
Conversation
…so (#1687) A grouped time-series panel whose buckets x groups product exceeds the 10,000-row hard cap got "ORDER BY bucket LIMIT 10000", which keeps the EARLIEST rows. A 24-hour wait_stats panel grouped by wait_type at minute grain therefore rendered its first ~87 minutes as though that were the whole window - and because the truncated domain never crossed midnight, the x-axis showed no dates, so nothing on screen looked wrong. Same silent-truncation class as the retention-routing gaps (#1661/#1664/#1665): the numbers are right, the WINDOW is wrong. Two halves, both needed: 1. The cap now applies ORDER BY bucket DESC inside a subquery, with the survivors re-sorted ascending for the renderer. A truncated chart shows the RECENT end, which is what a monitoring chart is for. The wrapper opens AFTER any CTE so the WITH stays top-level, and no parameter positions move - the time-series LIMIT is a literal (only the Ranked arm binds a LIMIT parameter) and the wrapper only brackets text whose parameters were already appended in order. 2. When a time-series panel comes back sitting exactly on the cap, it attaches a notice through the existing #1665 notice plumbing, rendered by the noticeStrip already present on every web surface and carried verbatim through MCP. Exactly-at-cap is the only available signal: the compiler cannot know the product ahead of time because group cardinality is a property of the data, not the spec. A panel that legitimately produces exactly 10,000 rows is therefore reported as capped - a false positive accepted deliberately, because the reverse mistake is the bug this fixes. Retention and row-cap notices are joined rather than one winning: both truncate a panel from the same end, and a long grouped panel on a retention-active store hits both. Showing one and dropping the other would under-report the panel in the most trouble. Ranked and Scalar are untouched - a Ranked LIMIT is the caller's own topN, so reaching it is the request being honored, not truncation. The issue's optional third item (a write-time hint) is deliberately not implemented: MaxBuckets (5,000) is already below HardRowCap (10,000), so buckets alone can never trip the cap, and any pre-flight warning would have to guess at group cardinality it cannot know. The runtime notice covers the real case with real data instead. Validated against a throwaway PostgreSQL 17.10 cluster, not just string matching: the live compose-run test executes the wrapped SQL and the full compose suite ran with zero skips (192 tests, including the live-gated ones). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Closes #1687. Both halves the issue called the honest minimum: keep the newest slice, and say when the slice happened.
The bug
A grouped time-series panel whose (buckets × groups) product exceeds the 10,000-row hard cap got
ORDER BY bucket+LIMIT 10000— which keeps the earliest rows. A 24-hourwait_statspanel grouped bywait_typeat minute grain rendered its first ~87 minutes as though that were the whole window, and because the truncated domain never crossed midnight the x-axis showed no dates, so nothing on screen looked wrong.Same silent-truncation class as the retention gaps (#1661/#1664/#1665): the numbers are right and the window is wrong.
1. Keep the newest buckets
The cap now applies
ORDER BY bucket DESCinside a subquery, with the survivors re-sorted ascending for the renderer. Actual emitted SQL:Two things I was careful about, both pinned:
WITH m AS (...)at the top level rather than swallowing it into the subquery. Verified on the realquery_stats/object_nameoutput.ORDER BY value DESC / LIMIT $3.2. Say so
When a time-series panel comes back sitting exactly on the cap, a notice rides the existing #1665 plumbing (
payload["notice"]), rendered by thenoticeStripalready on every web surface and carried verbatim through MCP:It names both escape hatches, because "this was truncated" without "here is what to do about it" is only half a fix.
Exactly-at-cap is the only signal available. The compiler cannot know the product ahead of time — group cardinality is a property of the data, not the spec. So a panel that legitimately produces exactly 10,000 rows is reported as capped. That false positive is accepted deliberately: an unnecessary notice is cheap, and the reverse mistake is the bug this exists to fix.
Notices are joined, not prioritized. Retention and the row cap now truncate a panel from the same end, and a long grouped panel on a retention-active store hits both. Showing one and swallowing the other would under-report precisely the panel in the most trouble, so
CombineNoticesemits all that apply — extracted as its own function specifically so that behaviour is testable rather than inline.Ranked and Scalar are untouched. A Ranked
LIMITis the caller's owntopN, so reaching it is the request being honored, not truncation; warning there would be a false alarm on the most ordinary result there is.The issue's optional third item — deliberately not implemented
A write-time hint would have to guess.
MaxBucketsis 5,000 andHardRowCapis 10,000, so buckets alone can never trip the cap — capping always requires multiple groups, and group cardinality is exactly what a pre-flight check cannot know. Any such warning would be a heuristic firing on every grouped minute-grain panel, most of which are fine. The runtime notice covers the real case with real data instead. Flagging the reasoning rather than silently skipping it.Validation — run against a real Postgres, not just string-matched
The existing pins use
Contains, which is why they never caught this, so I did not rely on them. I stood up a throwaway PostgreSQL 17.10 cluster and ran the project's own live compose test, which executes a time-series panel end-to-end throughRunComposedPanelAsync:Zero skips is the part that matters: the wrapped SQL was parsed and executed by a real Postgres, not just matched as a string.
Six new tests: the DESC-limited subquery shape (including a
DoesNotContainon the ascending-inner form, which would re-introduce the bug while still looking wrapped), the CTE-ordering pin, Ranked/Scalar staying unwrapped, notice-at-exactly-cap, no-notice-below-cap, time-series-only, and the combine-all-notices behaviour.I did not touch
charts.js— the notice mechanism is already rendered there.I also borrowed the shared
DarlingDevPgrig briefly before switching to a throwaway; it was not running when I found it and it is not running now.🤖 Generated with Claude Code