Skip to main content

reifydb_cdc/compact/
cache.rs

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