Skip to main content

commonware_storage/archive/immutable/
storage.rs

1use crate::{
2    Context,
3    archive::{Error, Identifier, immutable::Config},
4    freezer::{self, Checkpoint, Cursor, Freezer},
5    metadata::{self, Metadata},
6    ordinal::{self, Ordinal},
7};
8use commonware_codec::{CodecShared, EncodeSize, FixedSize, Read, ReadExt, Write};
9use commonware_runtime::{
10    Buf, BufMut,
11    telemetry::metrics::{Counter, MetricsExt as _},
12};
13use commonware_utils::{Array, bitmap::BitMap, sequence::prefixed_u64::U64};
14use futures::{TryFutureExt as _, try_join};
15use std::collections::BTreeMap;
16use tracing::debug;
17
18/// Prefix for [Freezer] records.
19const FREEZER_PREFIX: u8 = 0;
20
21/// Prefix for [Ordinal] records.
22const ORDINAL_PREFIX: u8 = 1;
23
24/// Item stored in [Metadata] to ensure [Freezer] and [Ordinal] remain consistent.
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26enum Record {
27    Freezer(Checkpoint),
28    Ordinal(Option<BitMap>),
29}
30
31impl Record {
32    /// Get the [Freezer] [Checkpoint] from the [Record].
33    fn freezer(&self) -> &Checkpoint {
34        match self {
35            Self::Freezer(checkpoint) => checkpoint,
36            _ => panic!("incorrect record"),
37        }
38    }
39
40    /// Get the [Ordinal] [BitMap] from the [Record].
41    fn ordinal(&self) -> &Option<BitMap> {
42        match self {
43            Self::Ordinal(indices) => indices,
44            _ => panic!("incorrect record"),
45        }
46    }
47}
48
49impl Write for Record {
50    fn write(&self, buf: &mut impl BufMut) {
51        match self {
52            Self::Freezer(checkpoint) => {
53                buf.put_u8(0);
54                checkpoint.write(buf);
55            }
56            Self::Ordinal(indices) => {
57                buf.put_u8(1);
58                indices.write(buf);
59            }
60        }
61    }
62}
63
64impl Read for Record {
65    type Cfg = ();
66    fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
67        let tag = u8::read(buf)?;
68        match tag {
69            0 => Ok(Self::Freezer(Checkpoint::read(buf)?)),
70            1 => Ok(Self::Ordinal(Option::<BitMap>::read_cfg(
71                buf,
72                &(usize::MAX as u64),
73            )?)),
74            _ => Err(commonware_codec::Error::InvalidEnum(tag)),
75        }
76    }
77}
78
79impl EncodeSize for Record {
80    fn encode_size(&self) -> usize {
81        1 + match self {
82            Self::Freezer(_) => Checkpoint::SIZE,
83            Self::Ordinal(indices) => indices.encode_size(),
84        }
85    }
86}
87
88/// The archive's state, boxed so the public [Archive] handle stays pointer-sized.
89struct Inner<E: Context, K: Array, V: CodecShared> {
90    /// Number of items per section.
91    items_per_section: u64,
92
93    /// Metadata for the archive.
94    metadata: Metadata<E, U64, Record>,
95
96    /// Freezer for the archive.
97    freezer: Freezer<E, K, V>,
98
99    /// Ordinal for the archive.
100    ordinal: Ordinal<E, Cursor>,
101
102    // Metrics
103    gets: Counter,
104    has: Counter,
105    syncs: Counter,
106}
107
108impl<E: Context, K: Array, V: CodecShared> Inner<E, K, V> {
109    /// See [Archive::init].
110    async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
111        // Initialize metadata
112        let metadata = Metadata::<E, U64, Record>::init(
113            context.child("metadata"),
114            metadata::Config {
115                partition: cfg.metadata_partition,
116                codec_config: (),
117            },
118        )
119        .await?;
120
121        // Metadata is the commit record for lower-layer storage. If no checkpoint was committed,
122        // Freezer::init treats existing freezer blobs as uncommitted and starts empty.
123        let freezer_key = U64::new(FREEZER_PREFIX, 0);
124        let checkpoint = metadata.get(&freezer_key).map(|freezer| *freezer.freezer());
125
126        // Initialize table
127        //
128        // TODO (#1227): Use sharded metadata to provide consistency
129        let freezer = Freezer::init(
130            context.child("freezer"),
131            freezer::Config {
132                key_partition: cfg.freezer_key_partition,
133                key_write_buffer: cfg.freezer_key_write_buffer,
134                key_page_cache: cfg.freezer_key_page_cache,
135                value_partition: cfg.freezer_value_partition,
136                value_compression: cfg.freezer_value_compression,
137                value_write_buffer: cfg.freezer_value_write_buffer,
138                value_target_size: cfg.freezer_value_target_size,
139                table_partition: cfg.freezer_table_partition,
140                table_initial_size: cfg.freezer_table_initial_size,
141                table_resize_frequency: cfg.freezer_table_resize_frequency,
142                table_resize_chunk_size: cfg.freezer_table_resize_chunk_size,
143                table_replay_buffer: cfg.replay_buffer,
144                codec_config: cfg.codec_config,
145            },
146            checkpoint,
147        )
148        .await?;
149
150        // Collect committed ordinal sections. Ordinal::init removes stored sections that are not
151        // present in this map, so an empty map represents a committed empty ordinal.
152        let sections = metadata
153            .keys()
154            .filter(|k| k.prefix() == ORDINAL_PREFIX)
155            .collect::<Vec<_>>();
156        let mut section_bits = BTreeMap::new();
157        for section in sections {
158            // Get record
159            let bits = metadata.get(section).unwrap().ordinal();
160
161            // Get section
162            let section = section.value();
163            section_bits.insert(section, bits);
164        }
165
166        // Initialize ordinal
167        //
168        // TODO (#1227): Use sharded metadata to provide consistency
169        let ordinal = Ordinal::init(
170            context.child("ordinal"),
171            ordinal::Config {
172                partition: cfg.ordinal_partition,
173                items_per_blob: cfg.items_per_section,
174                write_buffer: cfg.ordinal_write_buffer,
175                replay_buffer: cfg.replay_buffer,
176            },
177            Some(section_bits),
178        )
179        .await?;
180
181        // Initialize metrics
182        let gets = context.counter("gets", "Number of gets performed");
183        let has = context.counter("has", "Number of has performed");
184        let syncs = context.counter("syncs", "Number of syncs called");
185
186        Ok(Self {
187            items_per_section: cfg.items_per_section.get(),
188            metadata,
189            freezer,
190            ordinal,
191            gets,
192            has,
193            syncs,
194        })
195    }
196
197    /// Get the value for the given index.
198    async fn get_index(&self, index: u64) -> Result<Option<V>, Error> {
199        // Get ordinal
200        let Some(cursor) = self.ordinal.get(index).await? else {
201            return Ok(None);
202        };
203
204        // Get journal entry
205        let result = self
206            .freezer
207            .get(freezer::Identifier::Cursor(cursor))
208            .await?;
209
210        // Get value
211        Ok(result)
212    }
213
214    /// Get the value for the given key.
215    async fn get_key(&self, key: &K) -> Result<Option<V>, Error> {
216        // Get table entry
217        let result = self.freezer.get(freezer::Identifier::Key(key)).await?;
218
219        // Get value
220        Ok(result)
221    }
222
223    /// Initialize the section.
224    fn initialize_section(&mut self, section: u64) {
225        // Create active bit vector
226        let bits = BitMap::zeroes(self.items_per_section);
227
228        // Store record
229        let key = U64::new(ORDINAL_PREFIX, section);
230        self.metadata.put(key, Record::Ordinal(Some(bits)));
231        debug!(section, "initialized section");
232    }
233}
234
235impl<E: Context, K: Array, V: CodecShared> Inner<E, K, V> {
236    /// See [crate::archive::Archive::put].
237    async fn put(mut self: Box<Self>, index: u64, key: K, data: V) -> Result<Box<Self>, Error> {
238        // Ignore duplicates
239        if self.ordinal.has(index) {
240            return Ok(self);
241        }
242
243        // Initialize section if it doesn't exist
244        let section = index / self.items_per_section;
245        let ordinal_key = U64::new(ORDINAL_PREFIX, section);
246        if self.metadata.get(&ordinal_key).is_none() {
247            self.initialize_section(section);
248        }
249        let record = self.metadata.get_mut(&ordinal_key).unwrap();
250
251        // Update active bits
252        let done = if let Record::Ordinal(Some(bits)) = record {
253            bits.set(index % self.items_per_section, true);
254            bits.count_ones() == self.items_per_section
255        } else {
256            false
257        };
258        if done {
259            *record = Record::Ordinal(None);
260        }
261
262        // Put in table
263        let cursor;
264        (self.freezer, cursor) = self.freezer.put(key, data).await?;
265
266        // Put section and offset in ordinal
267        self.ordinal = self.ordinal.put(index, cursor).await?;
268
269        Ok(self)
270    }
271
272    /// See [crate::archive::Archive::get].
273    async fn get(&self, identifier: Identifier<'_, K>) -> Result<Option<V>, Error> {
274        self.gets.inc();
275
276        match identifier {
277            Identifier::Index(index) => self.get_index(index).await,
278            Identifier::Key(key) => self.get_key(key).await,
279        }
280    }
281
282    /// See [crate::archive::Archive::has].
283    async fn has(&self, identifier: Identifier<'_, K>) -> Result<bool, Error> {
284        self.has.inc();
285
286        match identifier {
287            Identifier::Index(index) => Ok(self.ordinal.has(index)),
288            Identifier::Key(key) => Ok(self.freezer.has(key).await?),
289        }
290    }
291
292    /// See [crate::archive::Archive::sync].
293    async fn sync(mut self: Box<Self>) -> Result<Box<Self>, Error> {
294        self.syncs.inc();
295
296        // Sync journal and ordinal
297        let ((freezer, checkpoint), ordinal) = try_join!(
298            self.freezer.sync().map_err(Error::from),
299            self.ordinal.sync().map_err(Error::from)
300        )?;
301        self.freezer = freezer;
302        self.ordinal = ordinal;
303
304        // Publish the freezer checkpoint with a single metadata sync after the
305        // freezer and ordinal state are durable.
306        let freezer_key = U64::new(FREEZER_PREFIX, 0);
307        self.metadata = self
308            .metadata
309            .put_sync(freezer_key, Record::Freezer(checkpoint))
310            .await?;
311
312        Ok(self)
313    }
314
315    /// See [crate::archive::Archive::next_gap].
316    fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>) {
317        self.ordinal.next_gap(index)
318    }
319
320    /// See [crate::archive::Archive::missing_items].
321    fn missing_items(&self, index: u64, max: usize) -> Vec<u64> {
322        self.ordinal.missing_items(index, max)
323    }
324
325    /// See [crate::archive::Archive::ranges].
326    fn ranges(&self) -> impl Iterator<Item = (u64, u64)> {
327        self.ordinal.ranges()
328    }
329
330    /// See [crate::archive::Archive::ranges_from].
331    fn ranges_from(&self, from: u64) -> impl Iterator<Item = (u64, u64)> {
332        self.ordinal.ranges_from(from)
333    }
334
335    /// See [crate::archive::Archive::first_index].
336    fn first_index(&self) -> Option<u64> {
337        self.ordinal.first_index()
338    }
339
340    /// See [crate::archive::Archive::last_index].
341    fn last_index(&self) -> Option<u64> {
342        self.ordinal.last_index()
343    }
344
345    /// See [crate::archive::Archive::destroy].
346    async fn destroy(self) -> Result<(), Error> {
347        // Destroy ordinal
348        self.ordinal.destroy().await?;
349
350        // Destroy freezer
351        self.freezer.destroy().await?;
352
353        // Destroy metadata
354        self.metadata.destroy().await?;
355
356        Ok(())
357    }
358}
359
360/// An immutable key-value store for ordered data with a minimal memory footprint.
361///
362/// Mutating functions consume the archive and return it only on success: an error (or a
363/// dropped future) destroys the handle.
364pub struct Archive<E: Context, K: Array, V: CodecShared>(Box<Inner<E, K, V>>);
365
366impl<E: Context, K: Array, V: CodecShared> std::fmt::Debug for Archive<E, K, V> {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        f.debug_struct("Archive")
369            .field("first_index", &self.0.first_index())
370            .field("last_index", &self.0.last_index())
371            .finish_non_exhaustive()
372    }
373}
374
375impl<E: Context, K: Array, V: CodecShared> Archive<E, K, V> {
376    /// Initialize a new [Archive] with the given [Config].
377    pub async fn init(context: E, cfg: Config<V::Cfg>) -> Result<Self, Error> {
378        Ok(Self(Box::new(Inner::init(context, cfg).await?)))
379    }
380}
381
382impl<E: Context, K: Array, V: CodecShared> crate::archive::Archive for Archive<E, K, V> {
383    type Key = K;
384    type Value = V;
385
386    async fn put(mut self, index: u64, key: K, data: V) -> Result<Self, Error> {
387        self.0 = self.0.put(index, key, data).await?;
388        Ok(self)
389    }
390
391    async fn get(&self, identifier: Identifier<'_, K>) -> Result<Option<V>, Error> {
392        self.0.get(identifier).await
393    }
394
395    async fn has(&self, identifier: Identifier<'_, K>) -> Result<bool, Error> {
396        self.0.has(identifier).await
397    }
398
399    async fn sync(mut self) -> Result<Self, Error> {
400        self.0 = self.0.sync().await?;
401        Ok(self)
402    }
403
404    fn next_gap(&self, index: u64) -> (Option<u64>, Option<u64>) {
405        self.0.next_gap(index)
406    }
407
408    fn missing_items(&self, index: u64, max: usize) -> Vec<u64> {
409        self.0.missing_items(index, max)
410    }
411
412    fn ranges(&self) -> impl Iterator<Item = (u64, u64)> {
413        self.0.ranges()
414    }
415
416    fn ranges_from(&self, from: u64) -> impl Iterator<Item = (u64, u64)> {
417        self.0.ranges_from(from)
418    }
419
420    fn first_index(&self) -> Option<u64> {
421        self.0.first_index()
422    }
423
424    fn last_index(&self) -> Option<u64> {
425        self.0.last_index()
426    }
427
428    async fn destroy(self) -> Result<(), Error> {
429        self.0.destroy().await
430    }
431}
432
433#[cfg(all(test, feature = "arbitrary"))]
434mod conformance {
435    use super::*;
436    use commonware_codec::conformance::CodecConformance;
437
438    commonware_conformance::conformance_tests! {
439        CodecConformance<Record>
440    }
441}