commonware_storage/ordinal/
storage.rs1use super::{Config, Error};
2use crate::{rmap::RMap, Context};
3use commonware_codec::{CodecFixed, Encode, FixedSize, Read, ReadExt, Write as CodecWrite};
4use commonware_cryptography::{crc32, Crc32};
5use commonware_formatting::hex;
6use commonware_runtime::{
7 buffer::{Read as ReadBuffer, Write},
8 telemetry::metrics::{Counter, MetricsExt as _},
9 Blob, Buf, BufMut, BufferPooler, Error as RError,
10};
11use commonware_utils::bitmap::BitMap;
12use futures::future::try_join_all;
13use std::{
14 collections::{btree_map::Entry, BTreeMap, BTreeSet},
15 marker::PhantomData,
16};
17use tracing::{debug, warn};
18
19#[derive(Debug, Clone)]
21struct Record<V: CodecFixed<Cfg = ()>> {
22 value: V,
23 crc: u32,
24}
25
26impl<V: CodecFixed<Cfg = ()>> Record<V> {
27 fn new(value: V) -> Self {
28 let crc = Crc32::checksum(&value.encode());
29 Self { value, crc }
30 }
31
32 fn is_valid(&self) -> bool {
33 self.crc == Crc32::checksum(&self.value.encode())
34 }
35}
36
37impl<V: CodecFixed<Cfg = ()>> FixedSize for Record<V> {
38 const SIZE: usize = V::SIZE + crc32::Digest::SIZE;
39}
40
41impl<V: CodecFixed<Cfg = ()>> CodecWrite for Record<V> {
42 fn write(&self, buf: &mut impl BufMut) {
43 self.value.write(buf);
44 self.crc.write(buf);
45 }
46}
47
48impl<V: CodecFixed<Cfg = ()>> Read for Record<V> {
49 type Cfg = ();
50
51 fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
52 let value = V::read(buf)?;
53 let crc = u32::read(buf)?;
54
55 Ok(Self { value, crc })
56 }
57}
58
59#[cfg(feature = "arbitrary")]
60impl<V: CodecFixed<Cfg = ()>> arbitrary::Arbitrary<'_> for Record<V>
61where
62 V: for<'a> arbitrary::Arbitrary<'a>,
63{
64 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
65 let value = V::arbitrary(u)?;
66 Ok(Self::new(value))
67 }
68}
69
70pub struct Ordinal<E: BufferPooler + Context, V: CodecFixed<Cfg = ()>> {
72 context: E,
74 config: Config,
75
76 blobs: BTreeMap<u64, Write<E::Blob>>,
78
79 intervals: RMap,
81
82 pending: BTreeSet<u64>,
84
85 puts: Counter,
87 gets: Counter,
88 has: Counter,
89 syncs: Counter,
90 pruned: Counter,
91
92 _phantom: PhantomData<V>,
93}
94
95impl<E: BufferPooler + Context, V: CodecFixed<Cfg = ()>> Ordinal<E, V> {
96 pub async fn init(
106 context: E,
107 config: Config,
108 bits: Option<BTreeMap<u64, &Option<BitMap>>>,
109 ) -> Result<Self, Error> {
110 let record_size = Record::<V>::SIZE as u64;
112 let items_per_blob = config.items_per_blob.get();
113 let mut blobs = BTreeMap::new();
114 let stored_blobs = if bits.is_none() {
115 match context.remove(&config.partition, None).await {
116 Ok(()) | Err(RError::PartitionMissing(_)) => Vec::new(),
117 Err(err) => return Err(Error::Runtime(err)),
118 }
119 } else {
120 match context.scan(&config.partition).await {
121 Ok(blobs) => blobs,
122 Err(RError::PartitionMissing(_)) => Vec::new(),
123 Err(err) => return Err(Error::Runtime(err)),
124 }
125 };
126
127 for name in stored_blobs {
129 let (blob, mut len) = context.open(&config.partition, &name).await?;
130 let index = match name.try_into() {
131 Ok(index) => u64::from_be_bytes(index),
132 Err(nm) => Err(Error::InvalidBlobName(hex(&nm)))?,
133 };
134
135 if bits.is_some() && len % record_size != 0 {
137 warn!(
138 blob = index,
139 invalid_size = len,
140 record_size,
141 "blob size is not a multiple of record size, truncating"
142 );
143 len -= len % record_size;
144 blob.resize(len).await?;
145 blob.sync().await?;
146 }
147
148 debug!(blob = index, len, "found index blob");
149 blobs.insert(index, (blob, len));
150 }
151
152 debug!(
154 blobs = blobs.len(),
155 "rebuilding intervals from existing index"
156 );
157 let start = context.current();
158 let mut items = 0;
159 let mut intervals = RMap::new();
160 if let Some(bits) = &bits {
161 let sections = blobs.keys().copied().collect::<Vec<_>>();
163 for section in sections {
164 let keep = match bits.get(§ion) {
165 Some(Some(bits)) => bits.count_ones() != 0,
166 Some(None) => true,
167 None => false,
168 };
169 if !keep {
170 context
171 .remove(&config.partition, Some(§ion.to_be_bytes()))
172 .await?;
173 blobs.remove(§ion);
174 }
175 }
176
177 let empty = vec![0u8; Record::<V>::SIZE];
180 for (section, (blob, size)) in &blobs {
181 let Some(Some(bits)) = bits.get(section) else {
183 continue;
184 };
185 let mut modified = false;
186 for bit_index in 0..(*size / record_size) {
187 if bit_index >= bits.len() || !bits.get(bit_index) {
188 blob.write_at(bit_index * record_size, empty.clone())
189 .await?;
190 modified = true;
191 }
192 }
193 if modified {
194 blob.sync().await?;
195 }
196 }
197
198 for (section, bits) in bits {
200 if let Some(bits) = bits {
201 if bits.count_ones() == 0 {
202 continue;
203 }
204 }
205
206 let Some((blob, size)) = blobs.get(section) else {
207 return Err(Error::MissingRecord(section * items_per_blob));
208 };
209
210 let mut set_indices = bits.as_ref().map(|bits| bits.ones_iter());
213 let mut all_indices = 0..items_per_blob;
214 let mut replay_blob =
215 ReadBuffer::from_pooler(&context, blob.clone(), *size, config.replay_buffer);
216 while let Some(bit_index) = set_indices
217 .as_mut()
218 .map_or_else(|| all_indices.next(), |indices| indices.next())
219 {
220 let index = section * items_per_blob + bit_index;
221 if bit_index >= items_per_blob {
222 return Err(Error::MissingRecord(index));
223 }
224 let offset = bit_index * record_size;
225 if offset + record_size > *size {
226 return Err(Error::MissingRecord(index));
227 }
228
229 replay_blob.seek_to(offset)?;
231 let mut record_buf = replay_blob.read(Record::<V>::SIZE).await?;
232 if let Ok(record) = Record::<V>::read(&mut record_buf) {
233 if record.is_valid() {
234 items += 1;
235 intervals.insert(index);
236 continue;
237 }
238 }
239 return Err(Error::MissingRecord(index));
240 }
241 }
242 }
243 debug!(
244 items,
245 elapsed = ?context.current().duration_since(start).unwrap_or_default(),
246 "rebuilt intervals"
247 );
248
249 let blobs = blobs
251 .into_iter()
252 .map(|(index, (blob, len))| {
253 (
254 index,
255 Write::from_pooler(&context, blob, len, config.write_buffer),
256 )
257 })
258 .collect();
259
260 let puts = context.counter("puts", "Number of put calls");
262 let gets = context.counter("gets", "Number of get calls");
263 let has = context.counter("has", "Number of has calls");
264 let syncs = context.counter("syncs", "Number of sync calls");
265 let pruned = context.counter("pruned", "Number of pruned blobs");
266
267 Ok(Self {
268 context,
269 config,
270 blobs,
271 intervals,
272 pending: BTreeSet::new(),
273 puts,
274 gets,
275 has,
276 syncs,
277 pruned,
278 _phantom: PhantomData,
279 })
280 }
281
282 pub async fn put(&mut self, index: u64, value: V) -> Result<(), Error> {
284 self.puts.inc();
285
286 let items_per_blob = self.config.items_per_blob.get();
288 let section = index / items_per_blob;
289 if let Entry::Vacant(entry) = self.blobs.entry(section) {
290 let (blob, len) = self
291 .context
292 .open(&self.config.partition, §ion.to_be_bytes())
293 .await?;
294 entry.insert(Write::from_pooler(
295 &self.context,
296 blob,
297 len,
298 self.config.write_buffer,
299 ));
300 debug!(section, "created blob");
301 }
302
303 let blob = self.blobs.get_mut(§ion).unwrap();
305 let offset = (index % items_per_blob) * Record::<V>::SIZE as u64;
306 let record = Record::new(value);
307 blob.write_at(offset, record.encode_mut()).await?;
308 self.pending.insert(section);
309
310 self.intervals.insert(index);
312
313 Ok(())
314 }
315
316 pub async fn get(&self, index: u64) -> Result<Option<V>, Error> {
318 self.gets.inc();
319
320 if self.intervals.get(&index).is_none() {
322 return Ok(None);
323 }
324
325 let items_per_blob = self.config.items_per_blob.get();
327 let section = index / items_per_blob;
328 let blob = self.blobs.get(§ion).unwrap();
329 let offset = (index % items_per_blob) * Record::<V>::SIZE as u64;
330 let mut read_buf = blob.read_at(offset, Record::<V>::SIZE).await?;
331 let record = Record::<V>::read(&mut read_buf)?;
332
333 if record.is_valid() {
335 Ok(Some(record.value))
336 } else {
337 Err(Error::InvalidRecord(index))
338 }
339 }
340
341 pub fn has(&self, index: u64) -> bool {
343 self.has.inc();
344
345 self.intervals.get(&index).is_some()
346 }
347
348 pub fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>) {
350 self.intervals.next_gap(index)
351 }
352
353 pub fn ranges(&self) -> impl Iterator<Item = (u64, u64)> + '_ {
355 self.intervals.iter().map(|(&s, &e)| (s, e))
356 }
357
358 pub fn ranges_from(&self, from: u64) -> impl Iterator<Item = (u64, u64)> + '_ {
360 self.intervals.iter_from(from).map(|(&s, &e)| (s, e))
361 }
362
363 pub fn first_index(&self) -> Option<u64> {
365 self.intervals.first_index()
366 }
367
368 pub fn last_index(&self) -> Option<u64> {
370 self.intervals.last_index()
371 }
372
373 pub fn missing_items(&self, start: u64, max: usize) -> Vec<u64> {
378 self.intervals.missing_items(start, max)
379 }
380
381 pub async fn prune(&mut self, min: u64) -> Result<(), Error> {
386 let items_per_blob = self.config.items_per_blob.get();
388 let min_section = min / items_per_blob;
389 let sections_to_remove: Vec<u64> = self
390 .blobs
391 .keys()
392 .filter(|&§ion| section < min_section)
393 .copied()
394 .collect();
395
396 for section in sections_to_remove {
398 if let Some(blob) = self.blobs.remove(§ion) {
399 drop(blob);
400 self.context
401 .remove(&self.config.partition, Some(§ion.to_be_bytes()))
402 .await?;
403
404 let start_index = section * items_per_blob;
406 let end_index = (section + 1) * items_per_blob - 1;
407 self.intervals.remove(start_index, end_index);
408 debug!(section, start_index, end_index, "pruned blob");
409 }
410
411 self.pruned.inc();
413 }
414
415 self.pending.retain(|§ion| section >= min_section);
417
418 Ok(())
419 }
420
421 pub async fn sync(&mut self) -> Result<(), Error> {
423 self.syncs.inc();
424
425 if self.pending.is_empty() {
426 return Ok(());
427 }
428
429 let futures: Vec<_> = self
430 .blobs
431 .iter_mut()
432 .filter(|(section, _)| self.pending.contains(section))
433 .map(|(_, blob)| blob.sync())
434 .collect();
435 try_join_all(futures).await?;
436
437 self.pending.clear();
439
440 Ok(())
441 }
442
443 pub async fn destroy(self) -> Result<(), Error> {
445 for (i, blob) in self.blobs.into_iter() {
446 drop(blob);
447 self.context
448 .remove(&self.config.partition, Some(&i.to_be_bytes()))
449 .await?;
450 debug!(section = i, "destroyed blob");
451 }
452 match self.context.remove(&self.config.partition, None).await {
453 Ok(()) => {}
454 Err(RError::PartitionMissing(_)) => {
455 }
457 Err(err) => return Err(Error::Runtime(err)),
458 }
459 Ok(())
460 }
461}
462
463#[cfg(all(test, feature = "arbitrary"))]
464mod conformance {
465 use super::*;
466 use commonware_codec::conformance::CodecConformance;
467
468 commonware_conformance::conformance_tests! {
469 CodecConformance<Record<u32>>
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use commonware_runtime::deterministic::Context;
477
478 type TestOrdinal = Ordinal<Context, u64>;
479
480 fn is_send<T: Send>(_: T) {}
481
482 #[allow(dead_code)]
483 fn assert_ordinal_futures_are_send(ordinal: &mut TestOrdinal, key: u64) {
484 is_send(ordinal.get(key));
485 is_send(ordinal.put(key, 0u64));
486 }
487
488 #[allow(dead_code)]
489 fn assert_ordinal_destroy_is_send(ordinal: TestOrdinal) {
490 is_send(ordinal.destroy());
491 }
492}