perf(engine): full-corpus agg path deep-cloned the memtable under lock - #42
Merged
Merged
Conversation
`search_inner`'s `need_full_corpus` block (used whenever a request has
`aggs` and the columnar fast-agg path doesn't apply) built its corpus
by calling `memtable.all_docs_with_sources()` -- which deep-clones
every buffered document's JSON tree WHILE holding the per-shard
`parking_lot::RwLock` read guard for the entire clone.
Under concurrent dashboard load (several Kibana panel queries firing
within milliseconds of each other, all touching the same
memtable-resident index), this serialised the shard lock across every
concurrent request each doing a full O(doc) clone under it -- measured
1.2-2.7s just for this phase on a ~1000-doc memtable, scaling up with
the size of the concurrent burst.
The exact same class of bug was already fixed for the fast-agg path
via `all_docs_with_sources_arc()` (Arc-sharing: an O(1) refcount bump
under the lock instead of an O(doc) deep clone), per that function's
own doc comment ("was deep-cloning the entire memtable per agg
request... ~100-300ms/query at 1e5 buffered docs"). This `need_full_corpus`
path just never got the same treatment.
Applied the identical fix: Arc-share out of the memtable under the
lock, then deep-clone into the owned `Value` `run_aggs_with_all` needs
AFTER the lock is released -- the expensive clone no longer holds up
concurrent readers/writers on the same shard.
Verified: 15 concurrent identical date_histogram aggregation queries
against a real ~14k-doc memtable-resident index (kibana_sample_data_logs)
went from 1.2-2.7s (with `slow_query` warnings) to a consistent
~150-170ms with zero warnings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XJaZygeuRfZfUg2B8tPKU
4 tasks
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.
Summary
search_inner'sneed_full_corpusblock (used whenever a request hasaggsand the columnar fast-agg path doesn't apply) built its corpus by deep-cloning the entire memtable while holding the per-shard RwLock read guard, causing severe latency under concurrent dashboard load even on tiny datasets.Root cause
memtable.all_docs_with_sources()clones every buffered document's JSON tree, and the clone happens inside thes.read()critical section (per-shardparking_lot::RwLock). Under concurrent load — several Kibana dashboard panel queries firing within milliseconds of each other, all touching the same memtable-resident index — this serialises the shard lock across every concurrent request, each paying a full O(doc) clone under it. Measured 1.2–2.7s just for this phase on a ~1000-doc memtable, scaling with the size of the concurrent burst.The exact same class of bug was already fixed for the fast-agg path via
all_docs_with_sources_arc()(Arc-sharing: an O(1) refcount bump under the lock instead of an O(doc) deep clone) — see that function's own doc comment ("was deep-cloning the entire memtable per agg request... ~100-300ms/query at 1e5 buffered docs"). Thisneed_full_corpuspath just never got the same treatment.Fix
Arc-share out of the memtable under the lock, then deep-clone into the owned
Valuerun_aggs_with_allneeds after the lock is released. The expensive clone no longer holds up concurrent readers/writers on the same shard.Test plan
cargo build --release -p xerj-engine -p xerj-api -p xerj-servercargo fmt --check/cargo clippy --no-deps— cleancargo test --release -p xerj-engine --lib— 156/156 passeddate_histogramaggregation queries against a real ~14k-doc memtable-resident index (kibana_sample_data_logs): went from 1.2–2.7s (withslow_querywarnings logged) to a consistent ~150–170ms with zero warnings🤖 Generated with Claude Code