reifydb_store_multi/tier/read/
buffer.rs1use std::{collections::HashMap, sync::Arc};
5
6use reifydb_core::{common::CommitVersion, encoded::key::EncodedKey};
7use reifydb_runtime::sync::mutex::Mutex;
8use reifydb_value::util::cowvec::CowVec;
9
10use crate::tier::VersionedGetResult;
11
12#[derive(Clone)]
13struct CacheEntry {
14 version: CommitVersion,
15 value: Option<CowVec<u8>>,
16 seq: u64,
17}
18
19struct Inner {
20 entries: HashMap<EncodedKey, CacheEntry>,
21 next_seq: u64,
22 capacity: usize,
23}
24
25#[derive(Clone)]
26pub struct MultiReadBufferTier {
27 inner: Arc<Mutex<Inner>>,
28}
29
30impl MultiReadBufferTier {
31 pub fn new(capacity: usize) -> Self {
32 Self {
33 inner: Arc::new(Mutex::new(Inner {
34 entries: HashMap::new(),
35 next_seq: 0,
36 capacity: capacity.max(1),
37 })),
38 }
39 }
40
41 pub fn set_capacity(&self, capacity: usize) {
42 let mut inner = self.inner.lock();
43 inner.capacity = capacity.max(1);
44 inner.entries.clear();
45 }
46
47 pub fn get(&self, key: &EncodedKey, version: CommitVersion) -> VersionedGetResult {
48 let mut inner = self.inner.lock();
49 let Some(entry) = inner.entries.get(key) else {
50 return VersionedGetResult::NotFound;
51 };
52 if entry.version > version {
53 return VersionedGetResult::NotFound;
54 }
55 let stored_version = entry.version;
56 let value = entry.value.clone();
57 let seq = inner.next_seq;
58 inner.next_seq += 1;
59 if let Some(e) = inner.entries.get_mut(key) {
60 e.seq = seq;
61 }
62 match value {
63 Some(v) => VersionedGetResult::Value {
64 value: v,
65 version: stored_version,
66 },
67 None => VersionedGetResult::Tombstone,
68 }
69 }
70
71 pub fn insert(&self, key: EncodedKey, version: CommitVersion, value: Option<CowVec<u8>>) {
72 let mut inner = self.inner.lock();
73 match inner.entries.get(&key) {
74 Some(existing) if existing.version > version => return,
75 _ => {}
76 }
77 let seq = inner.next_seq;
78 inner.next_seq += 1;
79 inner.entries.insert(
80 key,
81 CacheEntry {
82 version,
83 value,
84 seq,
85 },
86 );
87 while inner.entries.len() > inner.capacity {
88 let Some(oldest) = inner.entries.iter().min_by_key(|(_, e)| e.seq).map(|(k, _)| k.clone())
89 else {
90 break;
91 };
92 inner.entries.remove(&oldest);
93 }
94 }
95
96 pub fn invalidate(&self, key: &EncodedKey) {
97 self.inner.lock().entries.remove(key);
98 }
99
100 pub fn clear(&self) {
101 self.inner.lock().entries.clear();
102 }
103
104 #[cfg(test)]
105 pub fn len(&self) -> usize {
106 self.inner.lock().entries.len()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn key(s: &str) -> EncodedKey {
115 EncodedKey::new(s.as_bytes().to_vec())
116 }
117
118 fn val(s: &str) -> CowVec<u8> {
119 CowVec::new(s.as_bytes().to_vec())
120 }
121
122 #[test]
123 fn insert_then_get_returns_value_when_version_high_enough() {
124 let read = MultiReadBufferTier::new(8);
125 read.insert(key("k"), CommitVersion(5), Some(val("v5")));
126 match read.get(&key("k"), CommitVersion(5)) {
127 VersionedGetResult::Value {
128 value: v,
129 version: ver,
130 } => {
131 assert_eq!(v.as_ref(), b"v5");
132 assert_eq!(ver, CommitVersion(5));
133 }
134 VersionedGetResult::Tombstone => panic!("expected value, got tombstone"),
135 VersionedGetResult::NotFound => panic!("expected hit at exactly stored version"),
136 }
137 assert!(matches!(read.get(&key("k"), CommitVersion(9)), VersionedGetResult::Value { .. }));
140 }
141
142 #[test]
143 fn get_below_stored_version_misses_so_caller_reads_through() {
144 let read = MultiReadBufferTier::new(8);
149 read.insert(key("k"), CommitVersion(5), Some(val("v5")));
150 assert!(
151 matches!(read.get(&key("k"), CommitVersion(4)), VersionedGetResult::NotFound),
152 "must miss below the stored version"
153 );
154 }
155
156 #[test]
157 fn tombstone_is_cached_and_served() {
158 let read = MultiReadBufferTier::new(8);
159 read.insert(key("k"), CommitVersion(3), None);
160 assert!(matches!(read.get(&key("k"), CommitVersion(3)), VersionedGetResult::Tombstone));
161 }
162
163 #[test]
164 fn invalidate_removes_the_key() {
165 let read = MultiReadBufferTier::new(8);
166 read.insert(key("k"), CommitVersion(1), Some(val("v1")));
167 read.invalidate(&key("k"));
168 assert!(
169 matches!(read.get(&key("k"), CommitVersion(1)), VersionedGetResult::NotFound),
170 "invalidated key must miss"
171 );
172 }
173
174 #[test]
175 fn newer_insert_overwrites_but_older_insert_is_ignored() {
176 let read = MultiReadBufferTier::new(8);
177 read.insert(key("k"), CommitVersion(5), Some(val("v5")));
178 read.insert(key("k"), CommitVersion(2), Some(val("v2")));
181 match read.get(&key("k"), CommitVersion(5)) {
182 VersionedGetResult::Value {
183 value: v,
184 ..
185 } => assert_eq!(v.as_ref(), b"v5", "older insert must not overwrite"),
186 VersionedGetResult::Tombstone => panic!("unexpected tombstone"),
187 VersionedGetResult::NotFound => panic!("unexpected miss"),
188 }
189 read.insert(key("k"), CommitVersion(7), Some(val("v7")));
191 match read.get(&key("k"), CommitVersion(7)) {
192 VersionedGetResult::Value {
193 value: v,
194 version: ver,
195 } => {
196 assert_eq!(v.as_ref(), b"v7");
197 assert_eq!(ver, CommitVersion(7));
198 }
199 VersionedGetResult::Tombstone => panic!("unexpected tombstone"),
200 VersionedGetResult::NotFound => panic!("unexpected miss"),
201 }
202 }
203
204 #[test]
205 fn eviction_bounds_size_and_never_changes_correctness() {
206 let read = MultiReadBufferTier::new(2);
209 read.insert(key("a"), CommitVersion(1), Some(val("a")));
210 read.insert(key("b"), CommitVersion(1), Some(val("b")));
211 read.insert(key("c"), CommitVersion(1), Some(val("c")));
212 assert!(read.len() <= 2, "read buffer must stay within capacity");
213 for k in ["a", "b", "c"] {
215 if let VersionedGetResult::Value {
216 value: v,
217 ..
218 } = read.get(&key(k), CommitVersion(1))
219 {
220 assert_eq!(v.as_ref(), k.as_bytes());
221 }
222 }
223 }
224
225 #[test]
226 fn clone_shares_backing_storage() {
227 let a = MultiReadBufferTier::new(4);
228 let b = a.clone();
229 a.insert(key("k"), CommitVersion(1), Some(val("v")));
230 assert!(
231 matches!(b.get(&key("k"), CommitVersion(1)), VersionedGetResult::Value { .. }),
232 "clone observes writes from the original"
233 );
234 }
235}