Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/configuration/node-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ This section contains the configuration options for a Searcher.
| `request_timeout_secs` | The time before a search request is cancelled. This should match the timeout of the stack calling into quickwit if there is one set. | `30` |
| `use_metastore_read_replica` | If true, routes read-only metastore requests from searchers, including DataFusion when enabled, to nodes running the `metastore_read_replica` service. Searchers require at least one `metastore_read_replica` node at startup and do not fall back to the primary metastore. | `false` |

These in-memory cache capacities bound the memory held by cached entries, keys included, not just
the size of the cached values. `partial_request_cache_capacity` is the most affected by this, since
its keys embed the search request itself: at an unchanged setting it holds somewhat fewer entries
than a value-only budget would suggest.
Comment on lines +316 to +319

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clarify the duplicated partial-cache budget

This does not bound total memory for partial_request_cache_capacity: SearcherContext::new passes the full configuration independently to both LeafSearchCache and ListFieldsCache (quickwit-search/src/service.rs lines 473–475), and each constructs its own MemorySizedCache. On a node receiving both search and list-fields traffic, cached entries can therefore consume roughly twice the configured capacity even before cache metadata overhead, so this new guarantee is misleading unless the caches share a budget or the documentation explicitly describes the per-cache limit.

Useful? React with 👍 / 👎.


### Searcher split cache configuration

This section contains the configuration options for the on-disk searcher split cache. Files are stored in the data directory under `searcher-split-cache/`.
Expand Down
8 changes: 8 additions & 0 deletions quickwit/quickwit-config/src/node_config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,14 @@ impl CacheConfig {
}
}

pub fn with_capacity_and_policy(capacity: ByteSize, policy: CachePolicy) -> Self {
CacheConfig {
capacity: Some(capacity),
policy: Some(policy),
virtual_caches: Vec::new(),
}
}

pub fn capacity(&self) -> ByteSize {
// this should always be there
self.capacity.unwrap_or_default()
Expand Down
50 changes: 48 additions & 2 deletions quickwit/quickwit-search/src/leaf_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use quickwit_config::CacheConfig;
use quickwit_proto::search::{
CountHits, LeafResourceStats, LeafSearchResponse, SearchRequest, SplitIdAndFooterOffsets,
};
use quickwit_storage::{MemorySizedCache, OwnedBytes};
use quickwit_storage::{MemUsage, MemorySizedCache, OwnedBytes};
use tantivy::index::SegmentId;

/// A cache to memoize `leaf_search_single_split` results.
Expand Down Expand Up @@ -118,6 +118,14 @@ impl CacheKey {
}
}

impl MemUsage for CacheKey {
fn heap_mem_usage(&self) -> usize {
// `SearchRequest` cannot easily implement `MemUsage`, but `encoded_len()` is a good proxy
// for its memory footprint.
self.split_id.heap_mem_usage() + self.request.encoded_len()
Comment on lines +123 to +125

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Account for SearchRequest's heap allocations

For searches spanning many indexes or containing many snippet fields, encoded_len() omits the heap allocation for each Vec element and nested structure—for example, every String in index_id_patterns occupies an inline header in the vector in addition to its encoded bytes. Because the request is cloned into a key for every cached split, these omitted allocations accumulate across entries, allowing the partial-request cache to retain substantially more memory than its configured capacity. Compute the request's transitive allocation sizes, including vector capacities and nested fields, rather than using its protobuf wire length.

Useful? React with 👍 / 👎.

}
}

/// A (half-open) range bounded inclusively below and exclusively above [start..end).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
struct HalfOpenRange {
Expand Down Expand Up @@ -249,7 +257,45 @@ mod tests {
SplitIdAndFooterOffsets,
};

use super::LeafSearchCache;
use super::{CacheKey, LeafSearchCache, MemUsage};

#[test]
fn test_cache_key_mem_usage_accounts_the_request() {
let split_info = SplitIdAndFooterOffsets {
split_id: "01H4V2E9K7XQZ8N3M5P6R7S8T9".to_string(),
split_footer_start: 0,
split_footer_end: 100,
timestamp_start: None,
timestamp_end: None,
num_docs: 0,
};
let short_request = SearchRequest {
index_id_patterns: vec!["test-idx".to_string()],
query_ast: "short".to_string(),
max_hits: 10,
..Default::default()
};
let padding = "x".repeat(10_000);
let long_request = SearchRequest {
query_ast: format!("short{padding}"),
..short_request.clone()
};

let short_key = CacheKey::from_split_meta_and_request(split_info.clone(), short_request);
let long_key = CacheKey::from_split_meta_and_request(split_info, long_request);

// The split id and the request are both owned outside the struct itself.
assert!(short_key.mem_usage() > size_of::<CacheKey>());
// A longer query AST is charged for, roughly byte for byte. `encoded_len` is a proxy, so
// only the order of magnitude is pinned here: the extra length must show up, and must not
// be wildly over-counted.
let delta = long_key.mem_usage() - short_key.mem_usage();
assert!(
delta >= padding.len() && delta <= padding.len() + 16,
"a {} byte longer query AST grew the key by {delta} bytes",
padding.len()
);
}

#[test]
fn test_leaf_search_cache_no_timestamp() {
Expand Down
Loading
Loading