Skip to main content

commonware_storage/metadata/
storage.rs

1use super::{Config, Error};
2use crate::Context;
3use commonware_codec::{Codec, FixedSize, ReadExt};
4use commonware_cryptography::{crc32, Crc32};
5use commonware_runtime::{
6    telemetry::metrics::{Counter, Gauge, GaugeExt, MetricsExt as _},
7    Blob, BufMut, Error as RError, IoBufMut,
8};
9use commonware_utils::Span;
10use futures::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}
70
71/// Implementation of [Metadata] storage.
72pub struct Metadata<E: Context, K: Span, V: Codec> {
73    context: E,
74
75    map: BTreeMap<K, V>,
76    partition: String,
77    state: State<E::Blob, K>,
78
79    sync_overwrites: Counter,
80    sync_rewrites: Counter,
81    keys: Gauge,
82}
83
84impl<E: Context, K: Span, V: Codec> Metadata<E, K, V> {
85    /// Initialize a new [Metadata] instance.
86    pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
87        // Open dedicated blobs
88        let (left_blob, left_len) = context.open(&cfg.partition, BLOB_NAMES[0]).await?;
89        let (right_blob, right_len) = context.open(&cfg.partition, BLOB_NAMES[1]).await?;
90
91        // Find latest blob (check which includes a hash of the other)
92        let (left_map, left_wrapper) =
93            Self::load(&context, &cfg.codec_config, 0, left_blob, left_len).await?;
94        let (right_map, right_wrapper) =
95            Self::load(&context, &cfg.codec_config, 1, right_blob, right_len).await?;
96
97        // Choose latest blob
98        let mut map = left_map;
99        let mut cursor = 0;
100        let mut version = left_wrapper.version;
101        if right_wrapper.version > left_wrapper.version {
102            cursor = 1;
103            map = right_map;
104            version = right_wrapper.version;
105        }
106        let next_version = version.checked_add(1).expect("version overflow");
107
108        // Create metrics
109        let sync_rewrites =
110            context.counter("sync_rewrites", "number of syncs that rewrote all data");
111        let sync_overwrites = context.counter(
112            "sync_overwrites",
113            "number of syncs that modified existing data",
114        );
115        let keys = context.gauge("keys", "number of tracked keys");
116
117        // Return metadata
118        let _ = keys.try_set(map.len());
119        Ok(Self {
120            context,
121
122            map,
123            partition: cfg.partition,
124            state: State {
125                cursor,
126                next_version,
127                key_order_changed: next_version, // rewrite on startup because we don't have a diff record
128                blobs: [left_wrapper, right_wrapper],
129            },
130
131            sync_rewrites,
132            sync_overwrites,
133            keys,
134        })
135    }
136
137    async fn load(
138        context: &E,
139        codec_config: &V::Cfg,
140        index: usize,
141        blob: E::Blob,
142        len: u64,
143    ) -> Result<(BTreeMap<K, V>, Wrapper<E::Blob, K>), Error> {
144        // Get blob length
145        if len == 0 {
146            // Empty blob
147            return Ok((BTreeMap::new(), Wrapper::empty(blob)));
148        }
149
150        // Read blob
151        let len: usize = len.try_into().expect("blob too large for platform");
152        let buf = blob
153            .read_at(0, len)
154            .await?
155            .coalesce_with_pool(context.storage_buffer_pool());
156
157        // Verify integrity.
158        //
159        // 8 bytes for version + 4 bytes for checksum.
160        if buf.len() < 8 + crc32::Digest::SIZE {
161            // Truncate and return none
162            warn!(
163                blob = index,
164                len = buf.len(),
165                "blob is too short: truncating"
166            );
167            blob.resize(0).await?;
168            blob.sync().await?;
169            return Ok((BTreeMap::new(), Wrapper::empty(blob)));
170        }
171
172        // Extract checksum
173        let checksum_index = buf.len() - crc32::Digest::SIZE;
174        let stored_checksum =
175            u32::from_be_bytes(buf.as_ref()[checksum_index..].try_into().unwrap());
176        let computed_checksum = Crc32::checksum(&buf.as_ref()[..checksum_index]);
177        if stored_checksum != computed_checksum {
178            // Truncate and return none
179            warn!(
180                blob = index,
181                stored = stored_checksum,
182                computed = computed_checksum,
183                "checksum mismatch: truncating"
184            );
185            blob.resize(0).await?;
186            blob.sync().await?;
187            return Ok((BTreeMap::new(), Wrapper::empty(blob)));
188        }
189
190        // Get parent
191        let version = u64::from_be_bytes(buf.as_ref()[..8].try_into().unwrap());
192
193        // Extract data
194        //
195        // If the checksum is correct, we assume data is correctly packed and we don't perform
196        // length checks on the cursor.
197        let mut data = BTreeMap::new();
198        let mut lengths = HashMap::new();
199        let mut cursor = u64::SIZE;
200        while cursor < checksum_index {
201            // Read key
202            let key = K::read(&mut buf.as_ref()[cursor..].as_ref())
203                .expect("unable to read key from blob");
204            cursor += key.encode_size();
205
206            // Read value
207            let value = V::read_cfg(&mut buf.as_ref()[cursor..].as_ref(), codec_config)
208                .expect("unable to read value from blob");
209            lengths.insert(key.clone(), Info::new(cursor, value.encode_size()));
210            cursor += value.encode_size();
211            data.insert(key, value);
212        }
213
214        // Return info
215        Ok((data, Wrapper::new(blob, version, lengths, buf)))
216    }
217
218    /// Get a value from [Metadata] (if it exists).
219    pub fn get(&self, key: &K) -> Option<&V> {
220        self.map.get(key)
221    }
222
223    /// Get a mutable reference to a value from [Metadata] (if it exists).
224    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
225        // Get value
226        let value = self.map.get_mut(key)?;
227
228        // Mark key as modified.
229        //
230        // We need to mark both blobs as modified because we may need to update both files.
231        let cursor = self.state.cursor;
232        self.state.blobs[cursor].modified.insert(key.clone());
233        self.state.blobs[1 - cursor].modified.insert(key.clone());
234
235        Some(value)
236    }
237
238    /// Clear all values from [Metadata]. The new state will not be persisted until [Self::sync] is
239    /// called.
240    pub fn clear(&mut self) {
241        // Clear map
242        self.map.clear();
243
244        // Mark key order as changed
245        self.state.key_order_changed = self.state.next_version;
246        self.keys.set(0);
247    }
248
249    /// Put a value into [Metadata].
250    ///
251    /// If the key already exists, the value will be overwritten and the previous
252    /// value is returned. The value stored will not be persisted until [Self::sync]
253    /// is called.
254    pub fn put(&mut self, key: K, value: V) -> Option<V> {
255        // Insert value, getting previous value if it existed
256        let previous = self.map.insert(key.clone(), value);
257
258        // Mark key as modified.
259        //
260        // We need to mark both blobs as modified because we may need to update both files.
261        if previous.is_some() {
262            let cursor = self.state.cursor;
263            self.state.blobs[cursor].modified.insert(key.clone());
264            self.state.blobs[1 - cursor].modified.insert(key);
265        } else {
266            self.state.key_order_changed = self.state.next_version;
267        }
268        let _ = self.keys.try_set(self.map.len());
269        previous
270    }
271
272    /// Perform a [Self::put] and [Self::sync] in a single operation.
273    ///
274    /// Like calling [Self::sync] directly, this commits all pending metadata
275    /// changes, not just the provided key.
276    pub async fn put_sync(&mut self, key: K, value: V) -> Result<(), Error> {
277        self.put(key, value);
278        self.sync().await
279    }
280
281    /// Update (or insert) a value in [Metadata] using a closure.
282    pub fn upsert(&mut self, key: K, f: impl FnOnce(&mut V))
283    where
284        V: Default,
285    {
286        if let Some(value) = self.get_mut(&key) {
287            // Update existing value
288            f(value);
289        } else {
290            // Insert new value
291            let mut value = V::default();
292            f(&mut value);
293            self.put(key, value);
294        }
295    }
296
297    /// Update (or insert) a value in [Metadata] using a closure and sync immediately.
298    pub async fn upsert_sync(&mut self, key: K, f: impl FnOnce(&mut V)) -> Result<(), Error>
299    where
300        V: Default,
301    {
302        self.upsert(key, f);
303        self.sync().await
304    }
305
306    /// Remove a value from [Metadata] (if it exists).
307    pub fn remove(&mut self, key: &K) -> Option<V> {
308        // Get value
309        let past = self.map.remove(key);
310
311        // Mark key as modified.
312        if past.is_some() {
313            self.state.key_order_changed = self.state.next_version;
314        }
315        let _ = self.keys.try_set(self.map.len());
316
317        past
318    }
319
320    /// Iterate over all keys in metadata.
321    pub fn keys(&self) -> impl Iterator<Item = &K> {
322        self.map.keys()
323    }
324
325    /// Retain only the keys that satisfy the predicate.
326    pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool) {
327        // Retain only keys that satisfy the predicate
328        let old_len = self.map.len();
329        self.map.retain(|k, v| f(k, v));
330        let new_len = self.map.len();
331
332        // If the number of keys has changed, mark the key order as changed
333        if new_len != old_len {
334            self.state.key_order_changed = self.state.next_version;
335            let _ = self.keys.try_set(self.map.len());
336        }
337    }
338
339    /// Atomically commit the current state of [Metadata].
340    pub async fn sync(&mut self) -> Result<(), Error> {
341        // Extract values we need
342        let cursor = self.state.cursor;
343        let next_version = self.state.next_version;
344        let key_order_changed = self.state.key_order_changed;
345
346        // Compute next version.
347        //
348        // While it is possible that extremely high-frequency updates to metadata could cause an
349        // eventual overflow of version, syncing once per millisecond would overflow in 584,942,417
350        // years.
351        let past_version = self.state.blobs[cursor].version;
352        let next_next_version = next_version.checked_add(1).expect("version overflow");
353
354        // Get target blob (the one we will modify)
355        let target_cursor = 1 - cursor;
356
357        // When key order is stable, each blob's modified set tracks the value
358        // deltas it has not yet received. If the target has none, the current
359        // cursor already points at a durable copy of the latest state and
360        // writing another version would only rotate blobs.
361        if key_order_changed < past_version && self.state.blobs[target_cursor].modified.is_empty() {
362            return Ok(());
363        }
364
365        // Update the state.
366        self.state.cursor = target_cursor;
367        self.state.next_version = next_next_version;
368
369        // Get a mutable reference to the target blob.
370        let target = &mut self.state.blobs[target_cursor];
371
372        // Determine if we can overwrite existing data in place, updating the
373        // in-memory mirror for equal-size values as we go. If any value changes
374        // encoded length, subsequent offsets shift and the blob must be rebuilt.
375        let mut overwrite = true;
376        if key_order_changed < past_version {
377            for key in target.modified.iter() {
378                let info = target.lengths.get(key).expect("key must exist");
379                let new_value = self.map.get(key).expect("key must exist");
380                if info.length == new_value.encode_size() {
381                    // Overwrite existing value
382                    let start = info.start;
383                    let end = start + info.length;
384                    let mut buf = &mut target.data.as_mut()[start..end];
385                    new_value.write(&mut buf);
386                } else {
387                    // Rewrite all
388                    overwrite = false;
389                    break;
390                }
391            }
392        } else {
393            // If the key order has changed, we need to rewrite all data
394            overwrite = false;
395        }
396
397        // Overwrite existing data
398        if overwrite {
399            // Update version
400            (&mut target.data.as_mut()[0..u64::SIZE]).put_u64(next_version);
401
402            // Update checksum
403            let checksum_index = target.data.len() - crc32::Digest::SIZE;
404            let checksum = Crc32::checksum(&target.data.as_ref()[..checksum_index]);
405            (&mut target.data.as_mut()[checksum_index..]).put_u32(checksum);
406
407            // Freeze the mirror so async writes can hold zero-copy slices, then recover the
408            // mutable mirror after all writes complete.
409            let data = std::mem::take(&mut target.data).freeze();
410
411            // Write each modified value from the frozen mirror, followed by the
412            // version and checksum.
413            let writes = target
414                .modified
415                .iter()
416                .map(|key| {
417                    let info = target.lengths.get(key).expect("key must exist");
418                    let start = info.start;
419                    let end = start + info.length;
420                    target.blob.write_at(start as u64, data.slice(start..end))
421                })
422                .chain([
423                    target.blob.write_at(0, data.slice(0..u64::SIZE)),
424                    target.blob.write_at(
425                        checksum_index as u64,
426                        data.slice(checksum_index..checksum_index + crc32::Digest::SIZE),
427                    ),
428                ]);
429            try_join_all(writes).await?;
430            target.blob.sync().await?;
431
432            // Clear modified keys to avoid writing the same data
433            target.modified.clear();
434
435            // Update state
436            target.version = next_version;
437            target.data = data.into_mut_with_pool(self.context.storage_buffer_pool());
438            self.sync_overwrites.inc();
439            return Ok(());
440        }
441
442        // Clear modified keys to avoid writing the same data
443        target.modified.clear();
444
445        // Since we can't overwrite in place, we rewrite the entire blob.
446        // Pooled buffers do not grow, so compute the final encoded length before
447        // selecting a destination buffer.
448        let mut lengths = HashMap::with_capacity(self.map.len());
449        let mut next_data_len = u64::SIZE + crc32::Digest::SIZE;
450        for (key, value) in &self.map {
451            let value_len = value.encode_size();
452            lengths.insert(key.clone(), Info::new(0, value_len));
453            next_data_len += key.encode_size() + value_len;
454        }
455
456        // Capture the old length before reusing this buffer so shrinking
457        // rewrites still resize the persisted blob.
458        let target_data_len = target.data.len();
459
460        // Reuse the existing blob mirror when its allocation is already large enough.
461        let mut next_data = if target.data.capacity() >= next_data_len {
462            let mut data = std::mem::take(&mut target.data);
463            data.clear();
464            data
465        } else {
466            self.context.storage_buffer_pool().alloc(next_data_len)
467        };
468        next_data.put_u64(next_version);
469
470        // Build new data
471        for (key, value) in &self.map {
472            key.write(&mut next_data);
473            let info = lengths.get_mut(key).expect("key must exist");
474            info.start = next_data.len();
475            value.write(&mut next_data);
476        }
477        next_data.put_u32(Crc32::checksum(next_data.as_ref()));
478
479        // Shrinking rewrites must also persist the resize, so they need a full sync.
480        let next_data = next_data.freeze();
481        if next_data.len() < target_data_len {
482            target.blob.write_at(0, next_data.clone()).await?;
483            target.blob.resize(next_data.len() as u64).await?;
484            target.blob.sync().await?;
485        } else {
486            // Non-shrinking rewrites are a single write and can use range-scoped
487            // durability.
488            target.blob.write_at_sync(0, next_data.clone()).await?;
489        }
490
491        // Update blob state
492        target.version = next_version;
493        target.lengths = lengths;
494        target.data = next_data.into_mut_with_pool(self.context.storage_buffer_pool());
495
496        self.sync_rewrites.inc();
497        Ok(())
498    }
499
500    /// Remove the underlying blobs for this [Metadata].
501    pub async fn destroy(self) -> Result<(), Error> {
502        let state = self.state;
503        for (i, wrapper) in state.blobs.into_iter().enumerate() {
504            drop(wrapper.blob);
505            self.context
506                .remove(&self.partition, Some(BLOB_NAMES[i]))
507                .await?;
508            debug!(blob = i, "destroyed blob");
509        }
510        match self.context.remove(&self.partition, None).await {
511            Ok(()) => {}
512            Err(RError::PartitionMissing(_)) => {
513                // Partition already removed or never existed.
514            }
515            Err(err) => return Err(Error::Runtime(err)),
516        }
517        Ok(())
518    }
519}