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	/// Default: 8 blocks * 1024 entries ~= 8K cached entries.
15	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}