You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Database query details p95 query scans the full trace window and times out #3034
Opening a database statement in Services > Database loads the Slowest 5% of Queries tile by calculating p95 across the selected trace window. On a high-volume trace source with a 24-hour window, the generated query times out after 60 seconds.
This is reproducible with the default OpenTelemetry-style trace schema, where Duration is numeric and database statements are read from SpanAttributes.
Environment
HyperDX 2.35.0
Self-hosted ClickStack
Trace source using the standard Duration UInt64 and SpanAttributes Map(...) fields
Steps to reproduce
Open Services > Database.
Select a 24-hour time range on a high-volume trace source.
Open one of the database statement rows.
Observe the Slowest 5% of Queries tile in the detail drawer.
Generated query
Sanitized version of the query shown by HyperDX:
SELECT
quantile(0.95)(
toFloat64OrDefault(toString(Duration))
) AS p95_duration_ns,
p95_duration_ns / 1e6 AS p95
FROM traces
WHERETimestamp>= fromUnixTimestamp64Milli(...)
ANDTimestamp<= fromUnixTimestamp64Milli(...)
AND coalesce(
nullIf(SpanAttributes['db.query.text'], ''),
nullIf(SpanAttributes['db.statement'], '')
) IN ('GET /resource/:id')
EXPLAIN indexes = 1 shows timestamp and primary-key pruning, but the generated database-statement predicate does not select the available map-value skipping index. The query therefore evaluates the coalesced map lookup across the selected trace slice.
Duration is already UInt64, so converting it through String and then Float64 also appears unnecessary.
The database query detail drawer should remain usable for common 24-hour ranges without requiring a higher timeout or a full scan of the selected trace slice.
Possible directions include preserving the native numeric type for Duration, allowing the database statement expression to point to an indexed or materialized column, or using a projection/aggregate path for the repeated percentile calculation.
I'd like to claim this one, if it's open for a PR.
I reproduced it locally and traced the cause: EXPLAIN indexes = 1 selects no skip index for the generated statement predicate, so every tile in the drawer reads all granules in the window, not just the p95 one. The coalesced map lookup is what hides the index — a predicate an index does cover (has(AttributeItems, 'db.query.text=…') on 26.2+, has(mapValues(Attributes), …) on the older schema) prunes it as a superset while the coalesce still decides the match. On 4M spans that is 499 → 26 granules and 2945 → 256 ms CPU, with identical p95 and row counts; same ratios at 20M. Verified on ClickHouse 26.8 and 26.1.
I have that change ready with tests. Before I open it: is that the direction you'd want, or would you rather solve it with a materialized column or projection for the statement, as the issue suggests? Happy to go either way.
Note that this only fixes the drawer. The tab's own top-N and per-query charts still scan the window by design, since they rank across all statements.
@teeohhem since you vouched me — is this something you'd want a PR for, and
should the issue get the Accepting PR label first?
Activity
archat-hash commentedon Oct 7, 2026
I'd like to claim this one, if it's open for a PR.
I reproduced it locally and traced the cause: EXPLAIN indexes = 1 selects no skip index for the generated statement predicate, so every tile in the drawer reads all granules in the window, not just the p95 one. The coalesced map lookup is what hides the index — a predicate an index does cover (has(AttributeItems, 'db.query.text=…') on 26.2+, has(mapValues(Attributes), …) on the older schema) prunes it as a superset while the coalesce still decides the match. On 4M spans that is 499 → 26 granules and 2945 → 256 ms CPU, with identical p95 and row counts; same ratios at 20M. Verified on ClickHouse 26.8 and 26.1.
I have that change ready with tests. Before I open it: is that the direction you'd want, or would you rather solve it with a materialized column or projection for the statement, as the issue suggests? Happy to go either way.
Note that this only fixes the drawer. The tab's own top-N and per-query charts still scan the window by design, since they rank across all statements.
@teeohhem since you vouched me — is this something you'd want a PR for, and
should the issue get the
Accepting PRlabel first?fix(app): let the Database query drawer use the span attribute skip i…