1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Copyright (c) 2024-present, fjall-rs
// This source code is licensed under both the Apache 2.0 and MIT License
// (found in the LICENSE-* files in the repository)

use crate::{value::UserValue, value_log::ValueLogId, ValueHandle};
use quick_cache::{sync::Cache, Equivalent, Weighter};

type Item = UserValue;

#[derive(Eq, std::hash::Hash, PartialEq)]
pub struct CacheKey(ValueLogId, ValueHandle);

impl Equivalent<CacheKey> for (ValueLogId, &ValueHandle) {
    fn equivalent(&self, key: &CacheKey) -> bool {
        self.0 == key.0 && self.1 == &key.1
    }
}

impl From<(ValueLogId, ValueHandle)> for CacheKey {
    fn from((vid, vhandle): (ValueLogId, ValueHandle)) -> Self {
        Self(vid, vhandle)
    }
}

#[derive(Clone)]
struct BlobWeighter;

impl Weighter<CacheKey, Item> for BlobWeighter {
    #[allow(clippy::cast_possible_truncation)]
    fn weight(&self, _: &CacheKey, blob: &Item) -> u64 {
        blob.len() as u64
    }
}

/// Blob cache, in which blobs are cached in-memory
/// after being retrieved from disk
///
/// This speeds up consecutive accesses to the same blobs, improving
/// read performance for hot data.
pub struct BlobCache {
    data: Cache<CacheKey, Item, BlobWeighter, xxhash_rust::xxh3::Xxh3Builder>,
    capacity: u64,
}

impl std::fmt::Debug for BlobCache {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "BlobCache<cap: {} bytes>", self.capacity)
    }
}

impl BlobCache {
    /// Creates a new block cache with roughly `n` bytes of capacity
    #[must_use]
    pub fn with_capacity_bytes(bytes: u64) -> Self {
        use quick_cache::sync::DefaultLifecycle;

        Self {
            data: Cache::with(
                10_000,
                bytes,
                BlobWeighter,
                xxhash_rust::xxh3::Xxh3Builder::new(),
                DefaultLifecycle::default(),
            ),
            capacity: bytes,
        }
    }

    pub(crate) fn insert(&self, key: CacheKey, value: UserValue) {
        self.data.insert(key, value);
    }

    pub(crate) fn get(&self, vlog_id: ValueLogId, vhandle: &ValueHandle) -> Option<Item> {
        let key = (vlog_id, vhandle);
        self.data.get(&key)
    }

    /// Returns the cache capacity in bytes
    #[must_use]
    pub fn capacity(&self) -> u64 {
        self.capacity
    }

    /// Returns the size in bytes
    #[must_use]
    pub fn size(&self) -> u64 {
        self.data.weight()
    }

    /// Returns the number of cached blocks
    #[must_use]
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Returns `true` if there are no cached blocks
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}