reifydb_cdc/compact/
cache.rs1use std::sync::Arc;
5
6use reifydb_core::{common::CommitVersion, interface::cdc::Cdc, util::lru::LruCache};
7
8#[derive(Clone)]
9pub struct BlockCache {
10 inner: Arc<LruCache<CommitVersion, Arc<Vec<Cdc>>>>,
11}
12
13impl BlockCache {
14 pub const DEFAULT_CAPACITY: usize = 8;
15
16 pub fn new(capacity: usize) -> Self {
17 Self {
18 inner: Arc::new(LruCache::new(capacity.max(1))),
19 }
20 }
21
22 pub fn get(&self, key: CommitVersion) -> Option<Arc<Vec<Cdc>>> {
23 self.inner.get(&key)
24 }
25
26 pub fn put(&self, key: CommitVersion, value: Arc<Vec<Cdc>>) {
27 let _ = self.inner.put(key, value);
28 }
29
30 pub fn remove(&self, key: CommitVersion) {
31 let _ = self.inner.remove(&key);
32 }
33
34 pub fn clear(&self) {
35 self.inner.clear();
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 fn cv(n: u64) -> CommitVersion {
44 CommitVersion(n)
45 }
46
47 fn empty_block() -> Arc<Vec<Cdc>> {
48 Arc::new(Vec::new())
49 }
50
51 #[test]
52 fn put_then_get_returns_inserted_arc() {
53 let cache = BlockCache::new(4);
54 let block = empty_block();
55 cache.put(cv(1), block.clone());
56
57 let got = cache.get(cv(1)).expect("entry should be present");
58 assert!(Arc::ptr_eq(&got, &block));
59 }
60
61 #[test]
62 fn get_returns_none_for_missing_key() {
63 let cache = BlockCache::new(4);
64 assert!(cache.get(cv(1)).is_none());
65
66 cache.put(cv(1), empty_block());
67 assert!(cache.get(cv(2)).is_none());
68 }
69
70 #[test]
71 fn put_overwrites_existing_value() {
72 let cache = BlockCache::new(4);
73 let first = empty_block();
74 let second = empty_block();
75 assert!(!Arc::ptr_eq(&first, &second));
76
77 cache.put(cv(1), first);
78 cache.put(cv(1), second.clone());
79
80 let got = cache.get(cv(1)).expect("entry should be present");
81 assert!(Arc::ptr_eq(&got, &second));
82 }
83
84 #[test]
85 fn remove_drops_value() {
86 let cache = BlockCache::new(4);
87 cache.put(cv(1), empty_block());
88
89 cache.remove(cv(1));
90 assert!(cache.get(cv(1)).is_none());
91 }
92
93 #[test]
94 fn remove_missing_key_is_noop() {
95 let cache = BlockCache::new(4);
96 cache.remove(cv(99));
97
98 cache.put(cv(1), empty_block());
99 cache.remove(cv(99));
100 assert!(cache.get(cv(1)).is_some());
101 }
102
103 #[test]
104 fn clear_empties_cache() {
105 let cache = BlockCache::new(4);
106 cache.put(cv(1), empty_block());
107 cache.put(cv(2), empty_block());
108 cache.put(cv(3), empty_block());
109
110 cache.clear();
111
112 assert!(cache.get(cv(1)).is_none());
113 assert!(cache.get(cv(2)).is_none());
114 assert!(cache.get(cv(3)).is_none());
115 }
116
117 #[test]
118 fn eviction_drops_least_recently_used() {
119 let cache = BlockCache::new(2);
120 cache.put(cv(1), empty_block());
121 cache.put(cv(2), empty_block());
122 cache.put(cv(3), empty_block());
123
124 assert!(cache.get(cv(1)).is_none(), "oldest entry should be evicted");
125 assert!(cache.get(cv(2)).is_some());
126 assert!(cache.get(cv(3)).is_some());
127 }
128
129 #[test]
130 fn get_promotes_recency_so_old_key_survives() {
131 let cache = BlockCache::new(2);
132 cache.put(cv(1), empty_block());
133 cache.put(cv(2), empty_block());
134
135 let _ = cache.get(cv(1));
136
137 cache.put(cv(3), empty_block());
138
139 assert!(cache.get(cv(1)).is_some(), "recently-touched key should survive");
140 assert!(cache.get(cv(2)).is_none(), "untouched older key should be evicted");
141 assert!(cache.get(cv(3)).is_some());
142 }
143
144 #[test]
145 fn new_with_zero_capacity_is_clamped_and_usable() {
146 let cache = BlockCache::new(0);
147 cache.put(cv(1), empty_block());
148 assert!(cache.get(cv(1)).is_some());
149 }
150
151 #[test]
152 fn clone_shares_backing_storage() {
153 let a = BlockCache::new(4);
154 let b = a.clone();
155
156 let block = empty_block();
157 a.put(cv(1), block.clone());
158
159 let got = b.get(cv(1)).expect("clone should see writes from original");
160 assert!(Arc::ptr_eq(&got, &block));
161 }
162}