value_log/
blob_cache.rs

1// Copyright (c) 2024-present, fjall-rs
2// This source code is licensed under both the Apache 2.0 and MIT License
3// (found in the LICENSE-* files in the repository)
4
5use crate::{value::UserValue, value_log::ValueLogId, ValueHandle};
6use quick_cache::{sync::Cache, Equivalent, Weighter};
7
8type Item = UserValue;
9
10#[derive(Eq, std::hash::Hash, PartialEq)]
11pub struct CacheKey(ValueLogId, ValueHandle);
12
13impl Equivalent<CacheKey> for (ValueLogId, &ValueHandle) {
14    fn equivalent(&self, key: &CacheKey) -> bool {
15        self.0 == key.0 && self.1 == &key.1
16    }
17}
18
19impl From<(ValueLogId, ValueHandle)> for CacheKey {
20    fn from((vid, vhandle): (ValueLogId, ValueHandle)) -> Self {
21        Self(vid, vhandle)
22    }
23}
24
25#[derive(Clone)]
26struct BlobWeighter;
27
28impl Weighter<CacheKey, Item> for BlobWeighter {
29    #[allow(clippy::cast_possible_truncation)]
30    fn weight(&self, _: &CacheKey, blob: &Item) -> u64 {
31        blob.len() as u64
32    }
33}
34
35/// Blob cache, in which blobs are cached in-memory
36/// after being retrieved from disk
37///
38/// This speeds up consecutive accesses to the same blobs, improving
39/// read performance for hot data.
40pub struct BlobCache {
41    // NOTE: rustc_hash performed best: https://fjall-rs.github.io/post/fjall-2-1
42    /// Concurrent cache implementation
43    data: Cache<CacheKey, Item, BlobWeighter, rustc_hash::FxBuildHasher>,
44
45    /// Capacity in bytes
46    capacity: u64,
47}
48
49impl std::fmt::Debug for BlobCache {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "BlobCache<cap: {} bytes>", self.capacity)
52    }
53}
54
55impl BlobCache {
56    /// Creates a new block cache with roughly `n` bytes of capacity.
57    #[must_use]
58    pub fn with_capacity_bytes(bytes: u64) -> Self {
59        use quick_cache::sync::DefaultLifecycle;
60
61        #[allow(clippy::default_trait_access)]
62        let quick_cache = Cache::with(
63            10_000,
64            bytes,
65            BlobWeighter,
66            Default::default(),
67            DefaultLifecycle::default(),
68        );
69
70        Self {
71            data: quick_cache,
72            capacity: bytes,
73        }
74    }
75
76    pub(crate) fn insert(&self, key: CacheKey, value: UserValue) {
77        self.data.insert(key, value);
78    }
79
80    pub(crate) fn get(&self, vlog_id: ValueLogId, vhandle: &ValueHandle) -> Option<Item> {
81        self.data.get(&(vlog_id, vhandle))
82    }
83
84    /// Returns the cache capacity in bytes.
85    #[must_use]
86    pub fn capacity(&self) -> u64 {
87        self.capacity
88    }
89
90    /// Returns the size in bytes.
91    #[must_use]
92    pub fn size(&self) -> u64 {
93        self.data.weight()
94    }
95
96    /// Returns the number of cached blocks.
97    #[must_use]
98    pub fn len(&self) -> usize {
99        self.data.len()
100    }
101
102    /// Returns `true` if there are no cached blocks.
103    #[must_use]
104    pub fn is_empty(&self) -> bool {
105        self.len() == 0
106    }
107}