Skip to main content

reifydb_cdc/compact/
cache.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use 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}