commonware_storage/cache/
storage.rs1use super::Config;
2use crate::{
3 journal::{
4 Error,
5 segmented::variable::{Config as JConfig, Journal},
6 },
7 rmap::RMap,
8};
9use commonware_codec::{CodecShared, EncodeSize, Read, ReadExt, Write, varint::UInt};
10use commonware_runtime::{
11 Buf, BufMut, Metrics, ReadOptions, Storage,
12 telemetry::metrics::{Counter, Gauge, GaugeExt, MetricsExt as _},
13};
14use std::collections::{BTreeMap, BTreeSet};
15use tracing::debug;
16
17struct Record<V: CodecShared> {
19 index: u64,
20 value: V,
21}
22
23impl<V: CodecShared> Record<V> {
24 const fn new(index: u64, value: V) -> Self {
26 Self { index, value }
27 }
28}
29
30impl<V: CodecShared> Write for Record<V> {
31 fn write(&self, buf: &mut impl BufMut) {
32 UInt(self.index).write(buf);
33 self.value.write(buf);
34 }
35}
36
37impl<V: CodecShared> Read for Record<V> {
38 type Cfg = V::Cfg;
39
40 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
41 let index = UInt::read(buf)?.into();
42 let value = V::read_cfg(buf, cfg)?;
43 Ok(Self { index, value })
44 }
45}
46
47impl<V: CodecShared> EncodeSize for Record<V> {
48 fn encode_size(&self) -> usize {
49 UInt(self.index).encode_size() + self.value.encode_size()
50 }
51}
52
53#[cfg(feature = "arbitrary")]
54impl<V: CodecShared> arbitrary::Arbitrary<'_> for Record<V>
55where
56 V: for<'a> arbitrary::Arbitrary<'a>,
57{
58 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
59 Ok(Self::new(u.arbitrary()?, u.arbitrary()?))
60 }
61}
62
63struct Inner<E: Storage + Metrics, V: CodecShared> {
65 items_per_blob: u64,
66 journal: Journal<E, Record<V>>,
67 pending: BTreeSet<u64>,
68
69 oldest_allowed: Option<u64>,
71 indices: BTreeMap<u64, u64>,
72 intervals: RMap,
73
74 items_tracked: Gauge,
75 gets: Counter,
76 has: Counter,
77 syncs: Counter,
78}
79
80impl<E: Storage + Metrics, V: CodecShared> Inner<E, V> {
81 const fn section(&self, index: u64) -> u64 {
83 (index / self.items_per_blob) * self.items_per_blob
84 }
85
86 async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
88 let journal = Journal::<E, Record<V>>::init(
90 context.child("journal"),
91 JConfig {
92 partition: cfg.partition,
93 compression: cfg.compression,
94 codec_config: cfg.codec_config,
95 page_cache: cfg.page_cache,
96 write_buffer: cfg.write_buffer,
97 },
98 )
99 .await?;
100
101 let mut indices = BTreeMap::new();
103 let mut intervals = RMap::new();
104 let journal = {
105 debug!("initializing cache");
106 let mut replay = journal
107 .replay(0, 0, cfg.replay_buffer, ReadOptions::default())
108 .await?;
109 while let Some(result) = replay.next().await {
110 let (_, offset, _, data) = result?;
112
113 indices.insert(data.index, offset);
115
116 intervals.insert(data.index);
118 }
119 debug!(items = indices.len(), "cache initialized");
120 replay.finish()?
121 };
122
123 let items_tracked = context.gauge("items_tracked", "Number of items tracked");
125 let gets = context.counter("gets", "Number of gets performed");
126 let has = context.counter("has", "Number of has performed");
127 let syncs = context.counter("syncs", "Number of syncs called");
128 let _ = items_tracked.try_set(indices.len());
129
130 Ok(Self {
132 items_per_blob: cfg.items_per_blob.get(),
133 journal,
134 pending: BTreeSet::new(),
135 oldest_allowed: None,
136 indices,
137 intervals,
138 items_tracked,
139 gets,
140 has,
141 syncs,
142 })
143 }
144
145 async fn get(&self, index: u64) -> Result<Option<V>, Error> {
147 self.gets.inc();
149
150 let offset = match self.indices.get(&index) {
152 Some(offset) => *offset,
153 None => return Ok(None),
154 };
155
156 let section = self.section(index);
158 let record = self.journal.get(section, offset).await?;
159 Ok(Some(record.value))
160 }
161
162 fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>) {
164 self.intervals.next_gap(index)
165 }
166
167 fn first(&self) -> Option<u64> {
169 self.intervals.iter().next().map(|(&start, _)| start)
170 }
171
172 fn missing_items(&self, start: u64, max: usize) -> Vec<u64> {
174 self.intervals.missing_items(start, max)
175 }
176
177 fn has(&self, index: u64) -> bool {
179 self.has.inc();
181
182 self.indices.contains_key(&index)
184 }
185
186 async fn prune(mut self: Box<Self>, min: u64) -> Result<Box<Self>, Error> {
188 let min = self.section(min);
190
191 if let Some(oldest_allowed) = self.oldest_allowed
193 && min <= oldest_allowed
194 {
195 return Ok(self);
198 }
199 debug!(min, "pruning cache");
200
201 (self.journal, _) = self.journal.prune(min).await?;
203
204 loop {
206 let next = match self.pending.iter().next() {
207 Some(section) if *section < min => *section,
208 _ => break,
209 };
210 self.pending.remove(&next);
211 }
212
213 loop {
215 let next = match self.indices.first_key_value() {
216 Some((index, _)) if *index < min => *index,
217 _ => break,
218 };
219 self.indices.remove(&next).unwrap();
220 }
221
222 if min > 0 {
224 self.intervals.remove(0, min - 1);
225 }
226
227 self.oldest_allowed = Some(min);
230 let _ = self.items_tracked.try_set(self.indices.len());
231 Ok(self)
232 }
233
234 async fn put(mut self: Box<Self>, index: u64, value: V) -> Result<(Box<Self>, bool), Error> {
236 let oldest_allowed = self.oldest_allowed.unwrap_or(0);
238 if index < oldest_allowed {
239 debug!(index, oldest_allowed, "ignoring put below prune floor");
240 return Ok((self, false));
241 }
242
243 if self.indices.contains_key(&index) {
245 return Ok((self, true));
246 }
247
248 let record = Record::new(index, value);
250 let section = self.section(index);
251 let offset;
252 (self.journal, offset, _) = self.journal.append(section, &record).await?;
253
254 self.indices.insert(index, offset);
256
257 self.intervals.insert(index);
259
260 self.pending.insert(section);
262
263 let _ = self.items_tracked.try_set(self.indices.len());
265 Ok((self, true))
266 }
267
268 async fn sync(mut self: Box<Self>) -> Result<Box<Self>, Error> {
270 self.syncs.inc_by(self.pending.len() as u64);
271 self.journal = self.journal.sync(&self.pending).await?;
272 self.pending.clear();
273 Ok(self)
274 }
275
276 async fn destroy(self) -> Result<(), Error> {
278 self.journal.destroy().await
279 }
280}
281
282pub struct Cache<E: Storage + Metrics, V: CodecShared>(Box<Inner<E, V>>);
287
288impl<E: Storage + Metrics, V: CodecShared> std::fmt::Debug for Cache<E, V> {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 f.debug_struct("Cache")
291 .field("first_index", &self.0.intervals.first_index())
292 .field("last_index", &self.0.intervals.last_index())
293 .finish_non_exhaustive()
294 }
295}
296
297impl<E: Storage + Metrics, V: CodecShared> Cache<E, V> {
298 pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
303 Ok(Self(Box::new(Inner::init(context, cfg).await?)))
304 }
305
306 pub async fn get(&self, index: u64) -> Result<Option<V>, Error> {
308 self.0.get(index).await
309 }
310
311 pub fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>) {
313 self.0.next_gap(index)
314 }
315
316 pub fn first(&self) -> Option<u64> {
318 self.0.first()
319 }
320
321 pub fn missing_items(&self, start: u64, max: usize) -> Vec<u64> {
326 self.0.missing_items(start, max)
327 }
328
329 pub fn has(&self, index: u64) -> bool {
331 self.0.has(index)
332 }
333
334 pub async fn prune(mut self, min: u64) -> Result<Self, Error> {
339 self.0 = self.0.prune(min).await?;
340 Ok(self)
341 }
342
343 pub async fn put(mut self, index: u64, value: V) -> Result<Self, Error> {
349 (self.0, _) = self.0.put(index, value).await?;
350 Ok(self)
351 }
352
353 pub async fn sync(mut self) -> Result<Self, Error> {
355 self.0 = self.0.sync().await?;
356 Ok(self)
357 }
358
359 pub async fn put_sync(mut self, index: u64, value: V) -> Result<Self, Error> {
364 let stored;
365 (self.0, stored) = self.0.put(index, value).await?;
366 if !stored {
367 return Ok(self);
368 }
369 self.sync().await
370 }
371
372 pub async fn destroy(self) -> Result<(), Error> {
374 self.0.destroy().await
375 }
376}
377
378#[cfg(all(test, feature = "arbitrary"))]
379mod conformance {
380 use super::*;
381 use commonware_codec::conformance::CodecConformance;
382
383 commonware_conformance::conformance_tests! {
384 CodecConformance<Record<u64>>,
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use commonware_runtime::deterministic::Context;
392
393 type TestCache = Cache<Context, u64>;
394
395 fn is_send<T: Send>(_: T) {}
396
397 #[allow(dead_code)]
398 fn assert_cache_futures_are_send(cache: &TestCache, key: &u64) {
399 is_send(cache.get(*key));
400 }
401
402 #[allow(dead_code)]
403 fn assert_cache_destroy_is_send(cache: TestCache) {
404 is_send(cache.destroy());
405 }
406}