Repository navigation
feat(analytics-datafusion): cache and reuse the per-segment Lucene bitmap - #94
Closed
rishabhmaurya wants to merge 1 commit into
Closed
rishabhmaurya wants to merge 1 commit into
rishabhmaurya wants to merge 1 commit into
Conversation
…tmap
When a query delegates a predicate to Lucene, the filter bitmap was built
per row-group: each RG re-ran createCollector → weight.scorer(leaf), which
for an expensive query (e.g. a leading-wildcard WildcardQuery over a large
term dictionary) walks the whole FST. With N row-groups per segment spread
across N partitions, that is N redundant FST walks for one segment.
Build the bitmap ONCE per segment instead, cached in an Arc<OnceLock> keyed
by (annotation_id, writer_generation) and shared across every partition and
chunk of that segment; each RG slices its own window out of the shared
segment-relative bitmap. Covers both delegation roles:
- correctness leaves: per-segment cache + concurrent warm_cache() prewarm
on the blocking pool, so the bitmap is ready (or in flight) before the
first poll.
- performance peers: per-(annotation_id × segment) cache, built lazily on
the first RG that passes the selectivity gate.
The FST-walk cost is independent of match count, so this is the dominant
win for expensive delegated predicates (q23: ~21s → ~2.2s index_query_time
on a single-segment shard). The selectivity gate and page/bloom pruning are
unchanged: a segment whose RGs never pass the gate still builds nothing.
A per-chunk OnceLock variant and a K-way parallel drain were both tried and
reverted — concurrent createCollector calls for one Lucene segment serialize
on Java-side FST/term-enum state, so splitting the drain is slower than one
serial pass.
Failure semantics are explicit. The cached cell is a Result, not an Option,
so a drained-but-empty segment (genuinely zero matches) is told apart from a
failed drain. A correctness predicate has no DataFusion fallback, so a failed
drain propagates as an error and fails the query rather than silently
dropping the row group; a performance peer ignores the failure and lets
DataFusion's FilterExec evaluate the retained predicate. Both the peer and
correctness caches share one SegmentBitmapCache<K> helper.
Gated by a dynamic cluster setting, datafusion.indexed.bitmap_cache_enabled
(default true): set false to fall back to the legacy per-row-group path
(eager correctness collector; peers skip precompute) without a redeploy,
plumbed through WireConfigSnapshot → DatafusionQueryConfig.
Signed-off-by: rishabhmaurya <rishabhmaurya05@gmail.com>
Owner
Author
|
Retargeting against opensearch-project/OpenSearch:main as a draft. |
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.
Description
When a query delegates a filter predicate to Lucene, the per-segment match
bitmap was built once per row group: every RG re-ran
createCollector → weight.scorer(leaf). For an expensive query — e.g. a leading-wildcardWildcardQueryover a large term dictionary —scorer()walks the whole FST,and that cost is independent of how many docs match. With N row groups in a
segment spread across N partitions, one segment paid N redundant FST walks.
This change builds the bitmap once per segment and shares it across every
partition and chunk via an
Arc<OnceLock>; each RG slices its own window outof the shared segment-relative bitmap. Both delegation roles are covered:
warm_cache()prewarm on the blocking pool, so the bitmap is ready (or in flight) before
the first poll.
(annotation_id, writer_generation)cache, builtlazily on the first RG that passes the selectivity gate.
Measured on ClickBench q23 (single-segment shard):
index_query_timedropsfrom ~21s to ~2.2s. The selectivity gate and page/bloom pruning are unchanged —
a segment whose RGs never pass the gate still builds nothing.
Failure semantics. The cached cell holds a
Result, not anOption, so adrained-but-empty segment (genuinely zero matches) is distinct from a failed
drain. A correctness predicate has no DataFusion fallback, so a failed drain
propagates as an error rather than silently dropping the row group; a
performance peer ignores the failure and lets DataFusion's
FilterExecevaluate the retained predicate. Peer and correctness caches share one
SegmentBitmapCache<K>helper.Kill-switch. Gated by the dynamic cluster setting
datafusion.indexed.bitmap_cache_enabled(defaulttrue). Set itfalsetofall back to the legacy per-row-group path (eager correctness collector; peers
skip precompute) without a redeploy.
A per-chunk
OnceLockvariant and a K-way parallel drain were both tried andreverted: concurrent
createCollectorcalls for one Lucene segment serializeon Java-side FST/term-enum state, so splitting the drain is slower than a single
serial pass.
Related Issues
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.