Skip to main content

commonware_storage/metadata/
storage.rs

1use super::{Config, Error};
2use crate::{Context, SyncCompletion};
3use commonware_codec::{Codec, FixedSize, ReadExt};
4use commonware_cryptography::{Crc32, crc32};
5use commonware_runtime::{
6    Blob, BufMut, Error as RError, Handle, IoBufMut, ReadOptions, WriteOptions,
7    telemetry::metrics::{Counter, Gauge, GaugeExt, MetricsExt as _},
8};
9use commonware_utils::Span;
10use futures::{FutureExt as _, future::try_join_all};
11use std::collections::{BTreeMap, BTreeSet, HashMap};
12use tracing::{debug, warn};
13
14/// The names of the two blobs that store metadata.
15const BLOB_NAMES: [&[u8]; 2] = [b"left", b"right"];
16
17/// Information about a value in a [Wrapper].
18struct Info {
19    start: usize,
20    length: usize,
21}
22
23impl Info {
24    /// Create a new [Info].
25    const fn new(start: usize, length: usize) -> Self {
26        Self { start, length }
27    }
28}
29
30/// One of the two wrappers that store metadata.
31struct Wrapper<B: Blob, K: Span> {
32    blob: B,
33    version: u64,
34    lengths: HashMap<K, Info>,
35    modified: BTreeSet<K>,
36    data: IoBufMut,
37}
38
39impl<B: Blob, K: Span> Wrapper<B, K> {
40    /// Create a new [Wrapper].
41    const fn new(blob: B, version: u64, lengths: HashMap<K, Info>, data: IoBufMut) -> Self {
42        Self {
43            blob,
44            version,
45            lengths,
46            modified: BTreeSet::new(),
47            data,
48        }
49    }
50
51    /// Create a new empty [Wrapper].
52    fn empty(blob: B) -> Self {
53        Self {
54            blob,
55            version: 0,
56            lengths: HashMap::new(),
57            modified: BTreeSet::new(),
58            data: IoBufMut::default(),
59        }
60    }
61}
62
63/// State used during [Metadata::sync] operations.
64struct State<B: Blob, K: Span> {
65    cursor: usize,
66    next_version: u64,
67    key_order_changed: u64,
68    blobs: [Wrapper<B, K>; 2],
69    /// The completion of the last started sync, until observed.
70    ///
71    /// At most one sync is ever in flight: a new sync always targets the copy the pending sync
72    /// left as last-known-durable, so it must first prove the pending sync completed.
73    pending: Option<SyncCompletion>,
74}
75
76/// The store's state, boxed so the public [Metadata] handle stays pointer-sized.
77struct Inner<E: Context, K: Span, V: Codec> {
78    context: E,
79
80    map: BTreeMap<K, V>,
81    partition: String,
82    state: State<E::Blob, K>,
83
84    sync_overwrites: Counter,
85    sync_rewrites: Counter,
86    keys: Gauge,
87}
88
89/// One copy of the store, as loaded at startup.
90enum Loaded<B: Blob, K: Span, V> {
91    /// The copy decoded cleanly (an empty blob decodes to an empty map).
92    Valid(BTreeMap<K, V>, Wrapper<B, K>),
93    /// The copy holds bytes that fail validation.
94    Invalid(B),
95}
96
97impl<E: Context, K: Span, V: Codec> Inner<E, K, V> {
98    /// See [Metadata::init].
99    async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
100        // Open dedicated blobs
101        let (left_blob, left_len) = context.open(&cfg.partition, BLOB_NAMES[0]).await?;
102        let (right_blob, right_len) = context.open(&cfg.partition, BLOB_NAMES[1]).await?;
103
104        // Find latest blob (check which includes a hash of the other). Syncs alternate copies
105        // and drain the previous sync first, so at most one copy is ever mid-write: both copies
106        // failing validation is corruption, and adopting a fresh store would mask it.
107        let left = Self::load(&context, &cfg.codec_config, 0, left_blob, left_len).await?;
108        let right = Self::load(&context, &cfg.codec_config, 1, right_blob, right_len).await?;
109        if matches!((&left, &right), (Loaded::Invalid(_), Loaded::Invalid(_))) {
110            return Err(Error::Corruption(
111                "both metadata copies failed validation".into(),
112            ));
113        }
114        let (left_map, left_wrapper) = Self::normalize(left).await?;
115        let (right_map, right_wrapper) = Self::normalize(right).await?;
116
117        // Choose latest blob
118        let mut map = left_map;
119        let mut cursor = 0;
120        let mut version = left_wrapper.version;
121        if right_wrapper.version > left_wrapper.version {
122            cursor = 1;
123            map = right_map;
124            version = right_wrapper.version;
125        }
126        let next_version = version.checked_add(1).expect("version overflow");
127
128        // Create metrics
129        let sync_rewrites =
130            context.counter("sync_rewrites", "number of syncs that rewrote all data");
131        let sync_overwrites = context.counter(
132            "sync_overwrites",
133            "number of syncs that modified existing data",
134        );
135        let keys = context.gauge("keys", "number of tracked keys");
136
137        // Return metadata
138        let _ = keys.try_set(map.len());
139        Ok(Self {
140            context,
141
142            map,
143            partition: cfg.partition,
144            state: State {
145                cursor,
146                next_version,
147                key_order_changed: next_version, // rewrite on startup because we don't have a diff record
148                blobs: [left_wrapper, right_wrapper],
149                pending: None,
150            },
151
152            sync_rewrites,
153            sync_overwrites,
154            keys,
155        })
156    }
157
158    async fn load(
159        context: &E,
160        codec_config: &V::Cfg,
161        index: usize,
162        blob: E::Blob,
163        len: u64,
164    ) -> Result<Loaded<E::Blob, K, V>, Error> {
165        // Get blob length
166        if len == 0 {
167            // Empty blob
168            return Ok(Loaded::Valid(BTreeMap::new(), Wrapper::empty(blob)));
169        }
170
171        // The full encoded blob remains in the in-memory mirror after decoding, so request that
172        // pages brought in by this read need not remain in the OS page cache.
173        let len: usize = len.try_into().expect("blob too large for platform");
174        let buf = blob
175            .read_at(0, len, ReadOptions::DONT_CACHE)
176            .await?
177            .coalesce_with_pool(context.storage_buffer_pool());
178
179        // Verify integrity.
180        //
181        // 8 bytes for version + 4 bytes for checksum.
182        if buf.len() < 8 + crc32::Digest::SIZE {
183            warn!(blob = index, len = buf.len(), "blob is too short");
184            return Ok(Loaded::Invalid(blob));
185        }
186
187        // Extract checksum
188        let checksum_index = buf.len() - crc32::Digest::SIZE;
189        let stored_checksum =
190            u32::from_be_bytes(buf.as_ref()[checksum_index..].try_into().unwrap());
191        let computed_checksum = Crc32::checksum(&buf.as_ref()[..checksum_index]);
192        if stored_checksum != computed_checksum {
193            warn!(
194                blob = index,
195                stored = stored_checksum,
196                computed = computed_checksum,
197                "checksum mismatch"
198            );
199            return Ok(Loaded::Invalid(blob));
200        }
201
202        // Get parent
203        let version = u64::from_be_bytes(buf.as_ref()[..8].try_into().unwrap());
204
205        // Extract data
206        //
207        // If the checksum is correct, we assume data is correctly packed and we don't perform
208        // length checks on the cursor.
209        let mut data = BTreeMap::new();
210        let mut lengths = HashMap::new();
211        let mut cursor = u64::SIZE;
212        while cursor < checksum_index {
213            // Read key
214            let key = K::read(&mut buf.as_ref()[cursor..].as_ref())
215                .expect("unable to read key from blob");
216            cursor += key.encode_size();
217
218            // Read value
219            let value = V::read_cfg(&mut buf.as_ref()[cursor..].as_ref(), codec_config)
220                .expect("unable to read value from blob");
221            lengths.insert(key.clone(), Info::new(cursor, value.encode_size()));
222            cursor += value.encode_size();
223            data.insert(key, value);
224        }
225
226        // Return info
227        Ok(Loaded::Valid(
228            data,
229            Wrapper::new(blob, version, lengths, buf),
230        ))
231    }
232
233    /// Adopt a valid copy, or durably reset the one copy a crash left mid-write.
234    async fn normalize(
235        copy: Loaded<E::Blob, K, V>,
236    ) -> Result<(BTreeMap<K, V>, Wrapper<E::Blob, K>), Error> {
237        match copy {
238            Loaded::Valid(map, wrapper) => Ok((map, wrapper)),
239            Loaded::Invalid(blob) => {
240                blob.resize(0).await?;
241                blob.sync().await?;
242                Ok((BTreeMap::new(), Wrapper::empty(blob)))
243            }
244        }
245    }
246
247    /// See [Metadata::get].
248    fn get(&self, key: &K) -> Option<&V> {
249        self.map.get(key)
250    }
251
252    /// See [Metadata::get_mut].
253    fn get_mut(&mut self, key: &K) -> Option<&mut V> {
254        // Get value
255        let value = self.map.get_mut(key)?;
256
257        // Mark key as modified.
258        //
259        // We need to mark both blobs as modified because we may need to update both files.
260        let cursor = self.state.cursor;
261        self.state.blobs[cursor].modified.insert(key.clone());
262        self.state.blobs[1 - cursor].modified.insert(key.clone());
263
264        Some(value)
265    }
266
267    /// See [Metadata::clear].
268    fn clear(&mut self) {
269        // Clear map
270        self.map.clear();
271
272        // Mark key order as changed
273        self.state.key_order_changed = self.state.next_version;
274        self.keys.set(0);
275    }
276
277    /// See [Metadata::put].
278    fn put(&mut self, key: K, value: V) -> Option<V> {
279        // Insert value, getting previous value if it existed
280        let previous = self.map.insert(key.clone(), value);
281
282        // Mark key as modified.
283        //
284        // We need to mark both blobs as modified because we may need to update both files.
285        if previous.is_some() {
286            let cursor = self.state.cursor;
287            self.state.blobs[cursor].modified.insert(key.clone());
288            self.state.blobs[1 - cursor].modified.insert(key);
289        } else {
290            self.state.key_order_changed = self.state.next_version;
291        }
292        let _ = self.keys.try_set(self.map.len());
293        previous
294    }
295
296    /// See [Metadata::upsert].
297    fn upsert(&mut self, key: K, f: impl FnOnce(&mut V))
298    where
299        V: Default,
300    {
301        if let Some(value) = self.get_mut(&key) {
302            // Update existing value
303            f(value);
304        } else {
305            // Insert new value
306            let mut value = V::default();
307            f(&mut value);
308            self.put(key, value);
309        }
310    }
311
312    /// See [Metadata::remove].
313    fn remove(&mut self, key: &K) -> Option<V> {
314        // Get value
315        let past = self.map.remove(key);
316
317        // Mark key as modified.
318        if past.is_some() {
319            self.state.key_order_changed = self.state.next_version;
320        }
321        let _ = self.keys.try_set(self.map.len());
322
323        past
324    }
325
326    /// See [Metadata::keys].
327    fn keys(&self) -> impl Iterator<Item = &K> {
328        self.map.keys()
329    }
330
331    /// See [Metadata::retain].
332    fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool) {
333        // Retain only keys that satisfy the predicate
334        let old_len = self.map.len();
335        self.map.retain(|k, v| f(k, v));
336        let new_len = self.map.len();
337
338        // If the number of keys has changed, mark the key order as changed
339        if new_len != old_len {
340            self.state.key_order_changed = self.state.next_version;
341            let _ = self.keys.try_set(self.map.len());
342        }
343    }
344
345    /// Wait for an in-flight sync started by [Metadata::start_sync], surfacing its failure.
346    async fn wait_for_pending(&mut self) -> Result<(), RError> {
347        // A failure is surfaced without writing: the failed copy's on-disk state is unknown,
348        // and a write to the other (only durable) copy could destroy both. The consuming
349        // caller destroys the store on the error.
350        let Some(completion) = &self.state.pending else {
351            return Ok(());
352        };
353        completion.clone().await?;
354        self.state.pending = None;
355        Ok(())
356    }
357
358    /// See [Metadata::sync].
359    async fn sync(&mut self) -> Result<(), RError> {
360        self.wait_for_pending().await?;
361        self.write_next_version(false).await?;
362        Ok(())
363    }
364
365    /// See [Metadata::start_sync].
366    async fn start_sync(&mut self) -> Result<Handle<()>, RError> {
367        self.wait_for_pending().await?;
368        self.write_next_version(true).await
369    }
370
371    /// Write and persist the next version of the store to the target blob.
372    async fn write_next_version(&mut self, pipelined: bool) -> Result<Handle<()>, RError> {
373        // Extract values we need
374        let cursor = self.state.cursor;
375        let next_version = self.state.next_version;
376        let key_order_changed = self.state.key_order_changed;
377
378        // Compute next version.
379        //
380        // While it is possible that extremely high-frequency updates to metadata could cause an
381        // eventual overflow of version, syncing once per millisecond would overflow in 584,942,417
382        // years.
383        let past_version = self.state.blobs[cursor].version;
384        let next_next_version = next_version.checked_add(1).expect("version overflow");
385
386        // Get target blob (the one we will modify)
387        let target_cursor = 1 - cursor;
388
389        // When key order is stable, each blob's modified set tracks the value
390        // deltas it has not yet received. If the target has none, the current
391        // cursor already points at a durable copy of the latest state and
392        // writing another version would only rotate blobs.
393        if key_order_changed < past_version && self.state.blobs[target_cursor].modified.is_empty() {
394            return Ok(Handle::ready(Ok(())));
395        }
396
397        // Update the state.
398        self.state.cursor = target_cursor;
399        self.state.next_version = next_next_version;
400
401        // Get a mutable reference to the target blob.
402        let target = &mut self.state.blobs[target_cursor];
403
404        // Determine if we can overwrite existing data in place, updating the
405        // in-memory mirror for equal-size values as we go. If any value changes
406        // encoded length, subsequent offsets shift and the blob must be rebuilt.
407        let mut overwrite = true;
408        if key_order_changed < past_version {
409            for key in target.modified.iter() {
410                let info = target.lengths.get(key).expect("key must exist");
411                let new_value = self.map.get(key).expect("key must exist");
412                if info.length == new_value.encode_size() {
413                    // Overwrite existing value
414                    let start = info.start;
415                    let end = start + info.length;
416                    let mut buf = &mut target.data.as_mut()[start..end];
417                    new_value.write(&mut buf);
418                } else {
419                    // Rewrite all
420                    overwrite = false;
421                    break;
422                }
423            }
424        } else {
425            // If the key order has changed, we need to rewrite all data
426            overwrite = false;
427        }
428
429        // Overwrite existing data
430        if overwrite {
431            // Update version
432            (&mut target.data.as_mut()[0..u64::SIZE]).put_u64(next_version);
433
434            // Update checksum
435            let checksum_index = target.data.len() - crc32::Digest::SIZE;
436            let checksum = Crc32::checksum(&target.data.as_ref()[..checksum_index]);
437            (&mut target.data.as_mut()[checksum_index..]).put_u32(checksum);
438
439            // Freeze the mirror so async writes can hold zero-copy slices, then recover the
440            // mutable mirror after all writes complete. Since the mirror remains authoritative,
441            // every write requests cache bypass.
442            let data = std::mem::take(&mut target.data).freeze();
443
444            // Write each modified value from the frozen mirror, followed by the
445            // version and checksum.
446            let writes = target
447                .modified
448                .iter()
449                .map(|key| {
450                    let info = target.lengths.get(key).expect("key must exist");
451                    let start = info.start;
452                    let end = start + info.length;
453                    target.blob.write_at(
454                        start as u64,
455                        data.slice(start..end),
456                        WriteOptions::DONT_CACHE,
457                    )
458                })
459                .chain([
460                    target
461                        .blob
462                        .write_at(0, data.slice(0..u64::SIZE), WriteOptions::DONT_CACHE),
463                    target.blob.write_at(
464                        checksum_index as u64,
465                        data.slice(checksum_index..checksum_index + crc32::Digest::SIZE),
466                        WriteOptions::DONT_CACHE,
467                    ),
468                ]);
469            try_join_all(writes).await?;
470            let sync = if pipelined {
471                Some(target.blob.start_sync().await)
472            } else {
473                target.blob.sync().await?;
474                None
475            };
476
477            // Clear modified keys to avoid writing the same data
478            target.modified.clear();
479
480            // Update state
481            target.version = next_version;
482            target.data = data.into_mut_with_pool(self.context.storage_buffer_pool());
483            self.sync_overwrites.inc();
484            return Ok(self.record_pending(sync));
485        }
486
487        // Clear modified keys to avoid writing the same data
488        target.modified.clear();
489
490        // Since we can't overwrite in place, we rewrite the entire blob.
491        // Pooled buffers do not grow, so compute the final encoded length before
492        // selecting a destination buffer.
493        let mut lengths = HashMap::with_capacity(self.map.len());
494        let mut next_data_len = u64::SIZE + crc32::Digest::SIZE;
495        for (key, value) in &self.map {
496            let value_len = value.encode_size();
497            lengths.insert(key.clone(), Info::new(0, value_len));
498            next_data_len += key.encode_size() + value_len;
499        }
500
501        // Capture the old length before reusing this buffer so shrinking
502        // rewrites still resize the persisted blob.
503        let target_data_len = target.data.len();
504
505        // Reuse the existing blob mirror when its allocation is already large enough.
506        let mut next_data = if target.data.capacity() >= next_data_len {
507            let mut data = std::mem::take(&mut target.data);
508            data.clear();
509            data
510        } else {
511            self.context.storage_buffer_pool().alloc(next_data_len)
512        };
513        next_data.put_u64(next_version);
514
515        // Build new data
516        for (key, value) in &self.map {
517            key.write(&mut next_data);
518            let info = lengths.get_mut(key).expect("key must exist");
519            info.start = next_data.len();
520            value.write(&mut next_data);
521        }
522        next_data.put_u32(Crc32::checksum(next_data.as_ref()));
523
524        // Shrinking rewrites must also persist the resize, so they need a full sync.
525        let next_data = next_data.freeze();
526        let shrinking = next_data.len() < target_data_len;
527
528        // The encoded blob becomes the authoritative in-memory mirror below, so every write
529        // requests cache bypass.
530        let sync = if pipelined {
531            target
532                .blob
533                .write_at(0, next_data.clone(), WriteOptions::DONT_CACHE)
534                .await?;
535            if shrinking {
536                target.blob.resize(next_data.len() as u64).await?;
537            }
538            Some(target.blob.start_sync().await)
539        } else if shrinking {
540            target
541                .blob
542                .write_at(0, next_data.clone(), WriteOptions::DONT_CACHE)
543                .await?;
544            target.blob.resize(next_data.len() as u64).await?;
545            target.blob.sync().await?;
546            None
547        } else {
548            // Non-shrinking rewrites are a single write and can use range-scoped
549            // durability.
550            target
551                .blob
552                .write_at(
553                    0,
554                    next_data.clone(),
555                    WriteOptions::SYNC | WriteOptions::DONT_CACHE,
556                )
557                .await?;
558            None
559        };
560
561        // Update blob state
562        target.version = next_version;
563        target.lengths = lengths;
564        target.data = next_data.into_mut_with_pool(self.context.storage_buffer_pool());
565
566        self.sync_rewrites.inc();
567        Ok(self.record_pending(sync))
568    }
569
570    /// Record a started blob sync (if any) as the pending sync and return its observer handle.
571    fn record_pending(&mut self, sync: Option<Handle<()>>) -> Handle<()> {
572        let Some(sync) = sync else {
573            return Handle::ready(Ok(()));
574        };
575        let completion: SyncCompletion = sync.boxed().shared();
576        let handle = Handle::from_future(completion.clone());
577        self.state.pending = Some(completion);
578        handle
579    }
580
581    /// See [Metadata::destroy].
582    async fn destroy(mut self) -> Result<(), Error> {
583        if let Some(pending) = self.state.pending.take() {
584            let _ = pending.await;
585        }
586        let state = self.state;
587        for (i, wrapper) in state.blobs.into_iter().enumerate() {
588            drop(wrapper.blob);
589            self.context
590                .remove(&self.partition, Some(BLOB_NAMES[i]))
591                .await?;
592            debug!(blob = i, "destroyed blob");
593        }
594        match self.context.remove(&self.partition, None).await {
595            Ok(()) => {}
596            Err(RError::PartitionMissing(_)) => {
597                // Partition already removed or never existed.
598            }
599            Err(err) => return Err(Error::Runtime(err)),
600        }
601        Ok(())
602    }
603}
604
605/// Implementation of [Metadata] storage.
606///
607/// Storage-mutating functions consume the store and return it only on success: an error (or a
608/// dropped future) destroys the handle.
609pub struct Metadata<E: Context, K: Span, V: Codec>(Box<Inner<E, K, V>>);
610
611impl<E: Context, K: Span, V: Codec> std::fmt::Debug for Metadata<E, K, V> {
612    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
613        f.debug_struct("Metadata")
614            .field("keys", &self.0.map.len())
615            .finish_non_exhaustive()
616    }
617}
618
619impl<E: Context, K: Span, V: Codec> Metadata<E, K, V> {
620    /// Initialize a new [Metadata] instance.
621    pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
622        Ok(Self(Box::new(Inner::init(context, cfg).await?)))
623    }
624
625    /// Get a value from [Metadata] (if it exists).
626    pub fn get(&self, key: &K) -> Option<&V> {
627        self.0.get(key)
628    }
629
630    /// Get a mutable reference to a value from [Metadata] (if it exists).
631    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
632        self.0.get_mut(key)
633    }
634
635    /// Clear all values from [Metadata]. The new state will not be persisted until [Self::sync] is
636    /// called.
637    pub fn clear(&mut self) {
638        self.0.clear();
639    }
640
641    /// Put a value into [Metadata].
642    ///
643    /// If the key already exists, the value will be overwritten and the previous
644    /// value is returned. The value stored will not be persisted until [Self::sync]
645    /// is called.
646    pub fn put(&mut self, key: K, value: V) -> Option<V> {
647        self.0.put(key, value)
648    }
649
650    /// Perform a [Self::put] and [Self::sync] in a single operation.
651    ///
652    /// Like calling [Self::sync] directly, this commits all pending metadata
653    /// changes, not just the provided key.
654    pub async fn put_sync(mut self, key: K, value: V) -> Result<Self, Error> {
655        self.0.put(key, value);
656        self.0.sync().await?;
657        Ok(self)
658    }
659
660    /// Update (or insert) a value in [Metadata] using a closure.
661    pub fn upsert(&mut self, key: K, f: impl FnOnce(&mut V))
662    where
663        V: Default,
664    {
665        self.0.upsert(key, f);
666    }
667
668    /// Update (or insert) a value in [Metadata] using a closure and sync immediately.
669    pub async fn upsert_sync(mut self, key: K, f: impl FnOnce(&mut V)) -> Result<Self, Error>
670    where
671        V: Default,
672    {
673        self.0.upsert(key, f);
674        self.0.sync().await?;
675        Ok(self)
676    }
677
678    /// Remove a value from [Metadata] (if it exists).
679    pub fn remove(&mut self, key: &K) -> Option<V> {
680        self.0.remove(key)
681    }
682
683    /// Iterate over all keys in metadata.
684    pub fn keys(&self) -> impl Iterator<Item = &K> {
685        self.0.keys()
686    }
687
688    /// Retain only the keys that satisfy the predicate.
689    pub fn retain(&mut self, f: impl FnMut(&K, &V) -> bool) {
690        self.0.retain(f);
691    }
692
693    /// Atomically commit the current state of [Metadata].
694    pub async fn sync(mut self) -> Result<Self, Error> {
695        self.0.sync().await?;
696        Ok(self)
697    }
698
699    /// Atomically begin committing the current state of [Metadata], returning a completion handle.
700    ///
701    /// Awaiting the returned [Handle] provides the same guarantee as [Self::sync]. A started
702    /// sync's failure surfaces on the handle and again on the next sync, which fails (destroying
703    /// the store) without writing. At most one sync is in flight: a new call writes nothing
704    /// until the prior sync completes. Dropping the handle neither cancels the sync nor loses a
705    /// failure.
706    pub async fn start_sync(mut self) -> Result<(Self, Handle<()>), Error> {
707        let handle = self.0.start_sync().await?;
708        Ok((self, handle))
709    }
710
711    /// Remove the underlying blobs for this [Metadata].
712    pub async fn destroy(self) -> Result<(), Error> {
713        self.0.destroy().await
714    }
715}