Seek log/metric-bucket queries by key range instead of scanning

Log reads (logs_since, the unsorted Logs query path) now seek the
IngestedLog range directly from since_sequence/before_sequence instead
of scanning from IndexKey::Start and filtering every prior entry out
in a closure. Metric-comparison filters and bucket_values now seek to
a single metric's Bucket key range instead of scanning every declared
metric's buckets. evaluate_promql sits on top of bucket_values, so its
per-poll dashboard evaluation is narrowed by the same fix without
further changes; caching prior evaluations across polls is left for a
follow-up if profiling ever shows it's needed (per ticket 081's own
scope, not built speculatively).

Resolves tickets 077, 078, 081.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
Hugh Kaznowski
2026-09-06 20:51:24 +01:00
co-authored by Claude Sonnet 5
parent 65330d37e0
commit 946fd50d53
8 changed files with 128 additions and 195 deletions
+11 -4
View File
@@ -56,11 +56,14 @@ pub(super) fn handle_query_inner(
}
let limit = params.limit.unwrap_or(500).min(10_000);
let compiled_params = snapshot.compiled_param_declarations();
let since_bound = match params.since_sequence {
Some(since) => std::ops::Bound::Excluded(since),
None => std::ops::Bound::Unbounded,
};
let filtered = snapshot
.logs_range()
.filter(|(sequence, timestamp, _)| {
params.since_sequence.is_none_or(|since| *sequence > since)
&& params.start_ms.is_none_or(|start| timestamp.as_millis() >= start)
.logs_in_sequence_range((since_bound, std::ops::Bound::Unbounded))
.filter(|(_, timestamp, _)| {
params.start_ms.is_none_or(|start| timestamp.as_millis() >= start)
&& params.end_ms.is_none_or(|end| timestamp.as_millis() <= end)
})
.filter(|(sequence, timestamp, text)| {
@@ -80,6 +83,10 @@ pub(super) fn handle_query_inner(
})
.collect(),
Some(sort_by) => {
// Not range-bounded by since_sequence/start_ms: SortHelperKey::WithParam
// sorts by the param's value first (that's the point — it's what gives
// sort-by-param order), so sequence/time bounds can't narrow a BTreeMap
// range seek here the way they do for the unsorted path above.
static EMPTY_HELPER: std::sync::LazyLock<BTreeMap<SortHelperKey, Timestamp>> =
std::sync::LazyLock::new(BTreeMap::new);
let helper_index =