3.7 KiB
3.7 KiB
077 — Log queries scan the full index instead of seeking by key range
Summary
Every log read — including polling reads that only want logs newer
than since_sequence — walks the entire retained log range and
filters with a closure, rather than seeking the BTreeMap directly to
the relevant key range. This is the read-path optimization the
left-right/sort-helper-index work never got around to: iterators
should narrow the underlying key range as far as the caller's bounds
allow, and accept range bounds as a parameter instead of scanning from
Start/MetricBucketSplit and filtering after the fact.
Where
EngineSnapshot::logs_range(src/engine/snapshot/mod.rs:159-168) always rangesIndexKey::Start..IndexKey::LogParamSplit— i.e. every ingested log ever retained — with no way for a caller to pass a narrower bound.EngineSnapshot::logs_since(src/engine/snapshot/mod.rs:170-181) callslogs_range()and then filters bysequence < before_sequenceand a timestamp window in a closure — it has all the information needed to seek instead (before_sequenceboundssequencedirectly), but throws that away and scans linearly regardless of how small the actual window is.- The unsorted log-query path in
handle_query_inner(src/engine/snapshot/query.rs:59-65) does the same thing:snapshot.logs_range().filter(|(sequence, timestamp, _)| { since ... start_ms ... end_ms ... }). On every poll (frontend passessince_sequence— seewebapp/src/lib/api.ts:263,343), this walks every previously-seen log line again just to throw it away in the filter, before even getting to the new ones. IndexKey::IngestedLog { sequence, timestamp }(src/engine/index_key.rs:10-13) derivesOrdfield-order-first onsequence, so a lower bound ofIndexKey::IngestedLog { sequence: since + 1, timestamp: Timestamp::MIN }(or equivalent) is a valid, correct seek — sequence is assigned monotonically at ingest (src/engine/handle.rs:118-119), so this isn't a heuristic, it's exact.
Scope
- Change
logs_rangeto accept a key range (e.g.RangeBounds<IndexKey>or a(Bound<u64>, Bound<u64>)over sequence) instead of hardcodingStart..LogParamSplit, so callers can seek. - Rework
logs_sinceand theEngineQuery::Logshandler inquery.rs:59-65to build a lower bound fromsince_sequence(and, where possible, fromstart_ms/note below) and pass it through, instead of filtering post-hoc from the very start of the index. - Note: because
IngestedLogkeys sort by(sequence, timestamp)and not(timestamp, sequence), astart_ms/end_ms-only query (nosince_sequence) can't be turned into an exact key-range seek from this index alone even though ingestion is roughly time-ordered by sequence — call this out explicitly rather than seeking incorrectly; it may be acceptable to leave that case as a scan, or to binary-search for the first sequence at/afterstart_mssince timestamps are non-decreasing with sequence in practice. - The sorted branch (
query.rs:82-128) iteratessort_helper_indexdirectly rather thanlogs_range, so it's less affected, but still does a per-candidateindex.get()(query.rs:106-107) with no early bound — check whether the helper index itself can be range-bounded oncesince_sequence/start_msare known. - Add/extend a test asserting that a
since_sequencepoll on a large log store does not touch (or does not compare against) log entries at or beforesince_sequence— a range-based seek should make this observably true, not just faster.
Depends on
None. Related to ticket 060/061 (sort helper indexes) — same underlying principle (avoid full scans by indexing/seeking).