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