feat: On disk cache for split footers#78
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces an optional persistent (on-disk) cache layer for split footers, backing the existing in-memory “hotcache” footer cache so footers can survive searcher restarts and avoid refetching from object storage.
Changes:
- Add a disk-backed size-bounded cache (
DiskSizedCache) and a two-tier cache wrapper (TieredSizedCache) to combine memory + optional disk tiers. - Wire the new disk footer cache into
SearcherContextconstruction and searcher startup via a newsplit_footer_disk_cache_capacityconfig option. - Extend metrics and documentation/config examples to reflect the new persistent cache.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| quickwit/quickwit-storage/src/metrics.rs | Adds metrics bucket for the new disk footer cache. |
| quickwit/quickwit-storage/src/lib.rs | Re-exports new cache types for downstream crates. |
| quickwit/quickwit-storage/src/cache/mod.rs | Registers and re-exports the new cache modules. |
| quickwit/quickwit-storage/src/cache/disk_sized_cache.rs | Implements the on-disk, LRU, size-bounded cache with tests. |
| quickwit/quickwit-storage/src/cache/tiered_sized_cache.rs | Implements the memory+disk tiered cache wrapper with tests. |
| quickwit/quickwit-config/src/node_config/mod.rs | Adds split_footer_disk_cache_capacity to SearcherConfig. |
| quickwit/quickwit-config/src/node_config/serialize.rs | Updates config serialization tests/defaults for new field. |
| quickwit/quickwit-serve/src/lib.rs | Opens disk cache under data dir and passes it into SearcherContext. |
| quickwit/quickwit-search/src/service.rs | Updates SearcherContext to use TieredSizedCache and new constructor arg. |
| quickwit/quickwit-search/src/leaf.rs | Switches footer cache parameter type to the tiered cache. |
| quickwit/quickwit-search/src/lib.rs | Updates context construction to new SearcherContext::new signature. |
| quickwit/quickwit-search/src/tests.rs | Updates tests to new SearcherContext::new signature. |
| docs/overview/concepts/querying.md | Documents the new split footer disk cache conceptually. |
| docs/configuration/node-config.md | Documents the new searcher configuration key. |
| config/quickwit.yaml | Adds commented example for enabling the new setting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| /// Returns the cached payload for the given key, if present on disk. | ||
| pub fn get(&self, key: &K) -> Option<OwnedBytes> { | ||
| let file_name = key.to_string(); |
There was a problem hiding this comment.
We are not concerned by this issue in the current PR. Should I still add this defensive check in case the cache is re-used later ?
| /// recency is refreshed. | ||
| pub fn put(&self, key: K, bytes: OwnedBytes) { | ||
| let num_bytes = bytes.len() as u64; | ||
| let file_name = key.to_string(); |
There was a problem hiding this comment.
We are not concerned by this issue in the current PR. Should I still add this defensive check in case the cache is re-used later ?
c6e7050 to
7440c8f
Compare
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
7440c8f to
ecd01e9
Compare
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
| for dir_entry_res in std::fs::read_dir(&root_path)? { | ||
| let dir_entry = dir_entry_res?; | ||
| let metadata = match dir_entry.metadata() { |
There was a problem hiding this comment.
This is calling stat on all files. It should be fast on an ssd (tried it locally, 150ms). I do have some concerns on EBS. Also this will also hold up the actor loop (never yelds). I assume it's okay on a fast disk as it's called only once. I think it can have bad side effects if the disk is low.
There was a problem hiding this comment.
We have the same logic for the splits cache: https://github.com/SekoiaLab/quickwit/blob/sekoia/quickwit/quickwit-storage/src/split_cache/mod.rs#L63-L66
There was a problem hiding this comment.
The test I performed was on 100k files. Actually looking a little bit further back in time, the number of entries in the current cache goes up to 220k per searcher. Given that this cache could be configured bigger and that it will never get wiped until the limit is reached, I think we can expect it to go up to millions.
There was a problem hiding this comment.
To avoid blocking the loop we could make open async and iterate inside spawn_blocking.
Regarding all the metadata calls I'm not sure we should remove them. The alternative of having a single manifest file feels a bit tricky because it adds write overhead and is problematic for crash consistency while the metadata calls are made only on startup
There was a problem hiding this comment.
If we want to avoid making all the stat calls we could store the size of the files in their name so we would have all the required info.
We would also lose the seed order for the LRUCache that is using mtime, but this is flawed anyway because it doesn't track the last time the footer was accessed, only the time it was created.
6e10f50 to
bf2d309
Compare
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
bf2d309 to
aa723c2
Compare
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
rdettai-sk
left a comment
There was a problem hiding this comment.
Looks good! The tiered approach is just a bit unfortunate in that it uses 2 locks / 2 states when theoretically only 1 should be enough. But I think contention should remain small as the runtime doesn't have many threads. Also eviction on a two tier state would not be easy.
| }; | ||
| if !victims.is_empty() { | ||
| let root_path = self.root_path.clone(); | ||
| let _ = tokio::task::spawn_blocking(move || remove_files(&root_path, &victims)).await; |
There was a problem hiding this comment.
I think we don't need to await this
There was a problem hiding this comment.
I think we could end in a strange state if the key is added again before being removed
Signed-off-by: Darkheir <raphael.cohen@sekoia.io>
Description
Describe the proposed changes made in this PR.
How was this PR tested?
Describe how you tested this PR.