Skip to main content

commonware_storage/metadata/
mod.rs

1//! A key-value store optimized for atomically committing a small collection of metadata.
2//!
3//! [Metadata] is a key-value store optimized for tracking a small collection of metadata
4//! that allows multiple updates to be committed in a single batch. It is commonly used with
5//! a variety of other underlying storage systems to persist application state across restarts.
6//!
7//! # Format
8//!
9//! Data stored in [Metadata] is serialized as a sequence of key-value pairs in either a
10//! "left" or "right" blob:
11//!
12//! ```text
13//! +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
14//! | 0 | 1 |    ...    | 8 | 9 |10 |11 |12 |13 |14 |15 |16 |  ...  |50 |...|90 |91 |92 |93 |
15//! +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
16//! |    Version (u64)  |      Key1     |              Value1           |...|  CRC32(u32)   |
17//! +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
18//! ```
19//!
20//! _To ensure the integrity of the data, a CRC32 checksum is appended to the end of the blob.
21//! This ensures that partial writes are detected before any data is relied on._
22//!
23//! # Atomic Updates
24//!
25//! To provide support for atomic updates, [Metadata] maintains two blobs: a "left" and a "right"
26//! blob. When a new update is committed, it is written to the "older" of the two blobs (indicated
27//! by the version persisted). Writes to [commonware_runtime::Blob] are not atomic and may only
28//! complete partially, so we only overwrite the "newer" blob once the "older" blob has been synced
29//! (otherwise, we would not be guaranteed to recover the latest complete state from disk on
30//! restart as half of a blob could be old data and half new data).
31//!
32//! # Delta Writes
33//!
34//! If the set of keys and the length of values are stable, [Metadata] will only write an update's
35//! delta to disk (rather than rewriting the entire metadata). This makes [Metadata] a great choice
36//! for maintaining even large collections of data (with the majority rarely modified).
37//!
38//! # Example
39//!
40//! ```rust
41//! use commonware_runtime::{Spawner, Runner, deterministic};
42//! use commonware_storage::metadata::{Metadata, Config};
43//! use commonware_utils::sequence::U64;
44//!
45//! let executor = deterministic::Runner::default();
46//! executor.start(|context| async move {
47//!     // Create a store
48//!     let mut metadata = Metadata::init(context, Config {
49//!         partition: "partition".into(),
50//!         codec_config: ((0..).into(), ()),
51//!     }).await.unwrap();
52//!
53//!     // Store metadata
54//!     metadata.put(U64::new(1), b"hello".to_vec());
55//!     metadata.put(U64::new(2), b"world".to_vec());
56//!
57//!     // Sync the metadata store (batch write changes)
58//!     metadata = metadata.sync().await.unwrap();
59//!
60//!     // Retrieve some metadata
61//!     let value = metadata.get(&U64::new(1)).unwrap();
62//!
63//! });
64//! ```
65
66#[cfg(all(test, feature = "arbitrary"))]
67mod conformance;
68mod storage;
69pub use storage::Metadata;
70use thiserror::Error;
71
72/// Errors that can occur when interacting with [Metadata].
73#[derive(Debug, Error)]
74pub enum Error {
75    #[error("runtime error: {0}")]
76    Runtime(#[from] commonware_runtime::Error),
77    #[error("corruption: {0}")]
78    Corruption(String),
79}
80
81/// Configuration for [Metadata] storage.
82#[derive(Clone)]
83pub struct Config<C> {
84    /// The [commonware_runtime::Storage] partition to use for storing metadata.
85    pub partition: String,
86
87    /// The codec configuration to use for the value stored in the metadata.
88    pub codec_config: C,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use commonware_formatting::hex;
95    use commonware_macros::{test_group, test_traced};
96    use commonware_runtime::{
97        Blob, Metrics as _, ReadOptions, Runner, Storage, Supervisor as _, WriteOptions,
98        deterministic,
99        mocks::{
100            DelayedSyncContext, PendingSyncs, RecordingContext, Recordings, WriteFaultContext,
101            WriteFaults, drive_pending_syncs, fail_pending_syncs, release_pending_syncs,
102        },
103    };
104    use commonware_utils::sequence::U64;
105    use futures::FutureExt as _;
106    use rand::{Rng, RngExt as _};
107
108    fn assert_options(recordings: &Recordings, reads: &[ReadOptions], writes: &[WriteOptions]) {
109        let snapshot = recordings.snapshot();
110        assert_eq!(snapshot.reads.as_slice(), reads);
111        assert_eq!(snapshot.writes.as_slice(), writes);
112        recordings.clear();
113    }
114
115    fn assert_durability(pending: &PendingSyncs, calls: usize, starts: usize, completions: usize) {
116        assert_eq!(pending.calls(), calls);
117        assert_eq!(pending.starts(), starts);
118        assert_eq!(pending.completions(), completions);
119    }
120
121    #[test_traced]
122    fn test_io_options_and_durability() {
123        let executor = deterministic::Runner::default();
124        executor.start(|context| async move {
125            let pending = PendingSyncs::default();
126            let (recording, recordings) = RecordingContext::new(DelayedSyncContext {
127                inner: context,
128                pending: pending.clone(),
129            });
130            let cfg = Config {
131                partition: "test".into(),
132                codec_config: ((0..).into(), ()),
133            };
134            let key = U64::new(1);
135            let extra_key = U64::new(2);
136            let mut metadata =
137                Metadata::<_, U64, Vec<u8>>::init(recording.child("first"), cfg.clone())
138                    .await
139                    .unwrap();
140
141            // Seed both mirrors so equal-size updates take the incremental branch.
142            metadata.put(key.clone(), vec![1; 8]);
143            metadata = metadata.sync().await.unwrap();
144            metadata = metadata.sync().await.unwrap();
145            recordings.clear();
146            pending.arm();
147
148            // Non-pipelined incremental writes request cache bypass and retain a trailing sync.
149            metadata.put(key.clone(), vec![2; 8]);
150            metadata = drive_pending_syncs(&pending, metadata.sync())
151                .await
152                .unwrap();
153            assert_options(
154                &recordings,
155                &[],
156                &[
157                    WriteOptions::DONT_CACHE,
158                    WriteOptions::DONT_CACHE,
159                    WriteOptions::DONT_CACHE,
160                ],
161            );
162            assert_durability(&pending, 1, 0, 0);
163
164            // Pipelined incremental writes request cache bypass and retain a started sync.
165            metadata.put(key.clone(), vec![3; 8]);
166            let (next, handle) = metadata.start_sync().await.unwrap();
167            metadata = next;
168            drive_pending_syncs(&pending, handle).await.unwrap();
169            assert_options(
170                &recordings,
171                &[],
172                &[
173                    WriteOptions::DONT_CACHE,
174                    WriteOptions::DONT_CACHE,
175                    WriteOptions::DONT_CACHE,
176                ],
177            );
178            assert_durability(&pending, 2, 1, 1);
179
180            // A growing pipelined rewrite requests cache bypass and retains a started sync.
181            metadata.put(extra_key.clone(), vec![4; 16]);
182            let (next, handle) = metadata.start_sync().await.unwrap();
183            metadata = next;
184            drive_pending_syncs(&pending, handle).await.unwrap();
185            assert_options(&recordings, &[], &[WriteOptions::DONT_CACHE]);
186            assert_durability(&pending, 3, 2, 2);
187
188            // A growing non-pipelined rewrite requests cache bypass and retains durability.
189            metadata = drive_pending_syncs(&pending, metadata.sync())
190                .await
191                .unwrap();
192            assert_options(
193                &recordings,
194                &[],
195                &[WriteOptions::SYNC | WriteOptions::DONT_CACHE],
196            );
197            assert_durability(&pending, 4, 2, 2);
198
199            // A shrinking pipelined rewrite requests cache bypass and retains a started sync.
200            metadata.remove(&extra_key);
201            let (next, handle) = metadata.start_sync().await.unwrap();
202            metadata = next;
203            drive_pending_syncs(&pending, handle).await.unwrap();
204            assert_options(&recordings, &[], &[WriteOptions::DONT_CACHE]);
205            assert_durability(&pending, 5, 3, 3);
206
207            // A shrinking non-pipelined rewrite requests cache bypass and retains a trailing sync.
208            metadata = drive_pending_syncs(&pending, metadata.sync())
209                .await
210                .unwrap();
211            assert_options(&recordings, &[], &[WriteOptions::DONT_CACHE]);
212            assert_durability(&pending, 6, 3, 3);
213
214            // Both populated mirrors request cache bypass when reloaded.
215            drop(metadata);
216            let metadata = Metadata::<_, U64, Vec<u8>>::init(recording.child("second"), cfg)
217                .await
218                .unwrap();
219            assert_options(
220                &recordings,
221                &[ReadOptions::DONT_CACHE, ReadOptions::DONT_CACHE],
222                &[],
223            );
224            metadata.destroy().await.unwrap();
225        });
226    }
227
228    #[test_traced]
229    fn test_start_sync_pipelined_destroy() {
230        let executor = deterministic::Runner::default();
231        executor.start(|context| async move {
232            let cfg = Config {
233                partition: "test".into(),
234                codec_config: ((0..).into(), ()),
235            };
236            let mut metadata =
237                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
238                    .await
239                    .unwrap();
240
241            // Two pipelined syncs back to back: the second drains the first before targeting
242            // the copy it left as last-known-durable.
243            let key = U64::new(1);
244            metadata.put(key.clone(), vec![3]);
245            let (mut metadata, h1) = metadata.start_sync().await.unwrap();
246            metadata.put(key.clone(), vec![4]);
247            let (metadata, h2) = metadata.start_sync().await.unwrap();
248            h1.await.unwrap();
249            h2.await.unwrap();
250            metadata.destroy().await.unwrap();
251
252            // Destroy drained the pending sync, so nothing survives the reopen.
253            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
254                .await
255                .unwrap();
256            assert_eq!(metadata.get(&key), None, "destroyed store must be empty");
257        });
258    }
259
260    #[test_traced]
261    fn test_start_sync_failure_fails_next_sync() {
262        let executor = deterministic::Runner::default();
263        executor.start(|context| async move {
264            let pending = PendingSyncs::default();
265            let cfg = Config {
266                partition: "test".into(),
267                codec_config: ((0..).into(), ()),
268            };
269            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
270                DelayedSyncContext {
271                    inner: context.child("first"),
272                    pending: pending.clone(),
273                },
274                cfg,
275            )
276            .await
277            .unwrap();
278
279            metadata.put(U64::new(1), vec![3]);
280            let (mut metadata, handle) = metadata.start_sync().await.unwrap();
281            fail_pending_syncs(&pending);
282            assert!(handle.await.is_err());
283
284            // The failed copy's on-disk state is unknown: the next sync observes the failure
285            // and fails, consuming the store, without writing the only durable copy.
286            metadata.put(U64::new(1), vec![4]);
287            assert!(metadata.start_sync().await.is_err());
288        });
289    }
290
291    #[test_traced]
292    fn test_start_sync_dropped_handle_does_not_cancel() {
293        let executor = deterministic::Runner::default();
294        executor.start(|context| async move {
295            let pending = PendingSyncs::default();
296            let cfg = Config {
297                partition: "test".into(),
298                codec_config: ((0..).into(), ()),
299            };
300            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
301                DelayedSyncContext {
302                    inner: context.child("first"),
303                    pending: pending.clone(),
304                },
305                cfg.clone(),
306            )
307            .await
308            .unwrap();
309
310            // Drop the handle while its sync is still parked: the sync must proceed anyway.
311            let key = U64::new(1);
312            metadata.put(key.clone(), vec![3]);
313            let (metadata, handle) = metadata.start_sync().await.unwrap();
314            drop(handle);
315            release_pending_syncs(&pending);
316            let metadata = drive_pending_syncs(&pending, metadata.sync())
317                .await
318                .unwrap();
319            drop(metadata);
320
321            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
322                .await
323                .unwrap();
324            assert_eq!(metadata.get(&key), Some(&vec![3]));
325        });
326    }
327
328    #[test_traced]
329    fn test_start_sync_dropped_handle_fails_next_sync() {
330        let executor = deterministic::Runner::default();
331        executor.start(|context| async move {
332            let pending = PendingSyncs::default();
333            let cfg = Config {
334                partition: "test".into(),
335                codec_config: ((0..).into(), ()),
336            };
337            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
338                DelayedSyncContext {
339                    inner: context.child("first"),
340                    pending: pending.clone(),
341                },
342                cfg,
343            )
344            .await
345            .unwrap();
346
347            // Drop the handle before its sync fails: nobody observes the failure directly,
348            // but the next sync does, failing without writing the only durable copy.
349            metadata.put(U64::new(1), vec![3]);
350            let (metadata, handle) = metadata.start_sync().await.unwrap();
351            drop(handle);
352            fail_pending_syncs(&pending);
353
354            assert!(metadata.sync().await.is_err());
355        });
356    }
357
358    #[test_traced]
359    fn test_start_sync_newest_copy_wins_on_reopen() {
360        let executor = deterministic::Runner::default();
361        executor.start(|context| async move {
362            let cfg = Config {
363                partition: "test".into(),
364                codec_config: ((0..).into(), ()),
365            };
366            let mut metadata =
367                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
368                    .await
369                    .unwrap();
370
371            let key = U64::new(1);
372            metadata.put(key.clone(), vec![3]);
373            let (mut metadata, h1) = metadata.start_sync().await.unwrap();
374            metadata.put(key.clone(), vec![4]);
375            let (metadata, h2) = metadata.start_sync().await.unwrap();
376            h1.await.unwrap();
377            h2.await.unwrap();
378            drop(metadata);
379
380            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
381                .await
382                .unwrap();
383            assert_eq!(metadata.get(&key), Some(&vec![4]));
384        });
385    }
386
387    #[test_traced]
388    fn test_start_sync_write_failure_consumes() {
389        let executor = deterministic::Runner::default();
390        executor.start(|context| async move {
391            let faults = WriteFaults::default();
392            let cfg = Config {
393                partition: "test".into(),
394                codec_config: ((0..).into(), ()),
395            };
396            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
397                WriteFaultContext {
398                    inner: context.child("first"),
399                    faults: faults.clone(),
400                },
401                cfg.clone(),
402            )
403            .await
404            .unwrap();
405
406            // Establish a stable key order with equal-size values so later syncs take the
407            // overwrite path, which mutates the target copy's state before writing.
408            let key = U64::new(1);
409            metadata.put(key.clone(), vec![1; 8]);
410            metadata = metadata.sync().await.unwrap();
411            metadata.put(key.clone(), vec![2; 8]);
412            metadata = metadata.sync().await.unwrap();
413
414            // The injected failure hits the inline writes, after the cursor has rotated onto
415            // the target copy: the call fails, consuming the store.
416            faults.arm();
417            metadata.put(key.clone(), vec![3; 8]);
418            assert!(metadata.start_sync().await.is_err());
419            faults.disarm();
420
421            // The store died with the target copy in an unknown state: a reopen recovers the
422            // last durable value.
423            let metadata = Metadata::<_, U64, Vec<u8>>::init(
424                WriteFaultContext {
425                    inner: context.child("second"),
426                    faults,
427                },
428                cfg,
429            )
430            .await
431            .unwrap();
432            assert_eq!(metadata.get(&key), Some(&vec![2; 8]));
433        });
434    }
435
436    #[test_traced]
437    fn test_start_sync_second_sync_waits_for_first() {
438        let executor = deterministic::Runner::default();
439        executor.start(|context| async move {
440            let pending = PendingSyncs::default();
441            let cfg = Config {
442                partition: "test".into(),
443                codec_config: ((0..).into(), ()),
444            };
445            let faults = WriteFaults::default();
446            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(
447                WriteFaultContext {
448                    inner: DelayedSyncContext {
449                        inner: context.child("first"),
450                        pending: pending.clone(),
451                    },
452                    faults: faults.clone(),
453                },
454                cfg,
455            )
456            .await
457            .unwrap();
458
459            // Park the first sync, then start a second: until the first sync's fsync completes,
460            // its target copy's on-disk state is unknown, so the second sync must not write a
461            // single byte to the other (only durable) copy.
462            let key = U64::new(1);
463            metadata.put(key.clone(), vec![3]);
464            let (mut metadata, handle) = metadata.start_sync().await.unwrap();
465            metadata.put(key.clone(), vec![4]);
466            let writes_before = faults.writes();
467            let mut second = Box::pin(metadata.sync());
468            for _ in 0..8 {
469                assert!((&mut second).now_or_never().is_none());
470            }
471            assert_eq!(faults.writes(), writes_before, "second sync must not write");
472            assert_eq!(
473                pending.starts(),
474                1,
475                "second sync must not start a blob sync"
476            );
477
478            release_pending_syncs(&pending);
479            handle.await.unwrap();
480            let metadata = drive_pending_syncs(&pending, second).await.unwrap();
481            metadata.destroy().await.unwrap();
482        });
483    }
484
485    #[test_traced]
486    fn test_put_get_clear() {
487        // Initialize the deterministic context
488        let executor = deterministic::Runner::default();
489        executor.start(|context| async move {
490            // Create a metadata store
491            let cfg = Config {
492                partition: "test".into(),
493                codec_config: ((0..).into(), ()),
494            };
495            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
496                .await
497                .unwrap();
498
499            // Get a key that doesn't exist
500            let key = U64::new(42);
501            let value = metadata.get(&key);
502            assert!(value.is_none());
503
504            // Check metrics
505            let buffer = context.encode();
506            assert!(buffer.contains("first_sync_rewrites_total 0"));
507            assert!(buffer.contains("first_sync_overwrites_total 0"));
508            assert!(buffer.contains("first_keys 0"));
509
510            // Put a key
511            let hello = b"hello".to_vec();
512            metadata.put(key.clone(), hello.clone());
513
514            // Get the key
515            let value = metadata.get(&key).unwrap();
516            assert_eq!(value, &hello);
517
518            // Check metrics
519            let buffer = context.encode();
520            assert!(buffer.contains("first_sync_rewrites_total 0"));
521            assert!(buffer.contains("first_sync_overwrites_total 0"));
522            assert!(buffer.contains("first_keys 1"));
523
524            // Sync the metadata store
525            metadata = metadata.sync().await.unwrap();
526
527            // Check metrics
528            let buffer = context.encode();
529            assert!(buffer.contains("first_sync_rewrites_total 1"));
530            assert!(buffer.contains("first_sync_overwrites_total 0"));
531            assert!(buffer.contains("first_keys 1"));
532
533            // Reopen the metadata store
534            drop(metadata);
535            let cfg = Config {
536                partition: "test".into(),
537                codec_config: ((0..).into(), ()),
538            };
539            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
540                .await
541                .unwrap();
542
543            // Check metrics
544            let buffer = context.encode();
545            assert!(buffer.contains("second_sync_rewrites_total 0"));
546            assert!(buffer.contains("second_sync_overwrites_total 0"));
547            assert!(buffer.contains("second_keys 1"));
548
549            // Get the key
550            let value = metadata.get(&key).unwrap();
551            assert_eq!(value, &hello);
552
553            // Test clearing the metadata store
554            metadata.clear();
555            let value = metadata.get(&key);
556            assert!(value.is_none());
557
558            // Check metrics
559            let buffer = context.encode();
560            assert!(buffer.contains("second_sync_rewrites_total 0"));
561            assert!(buffer.contains("second_sync_overwrites_total 0"));
562            assert!(buffer.contains("second_keys 0"));
563
564            metadata.destroy().await.unwrap();
565        });
566    }
567
568    #[test_traced]
569    fn test_put_returns_previous_value() {
570        let executor = deterministic::Runner::default();
571        executor.start(|context| async move {
572            let cfg = Config {
573                partition: "test".into(),
574                codec_config: ((0..).into(), ()),
575            };
576            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
577                .await
578                .unwrap();
579
580            let key = U64::new(42);
581
582            // First put returns None (no previous value)
583            let previous = metadata.put(key.clone(), b"first".to_vec());
584            assert!(previous.is_none());
585
586            // Second put returns the previous value
587            let previous = metadata.put(key.clone(), b"second".to_vec());
588            assert_eq!(previous, Some(b"first".to_vec()));
589
590            // Third put returns the previous value
591            let previous = metadata.put(key.clone(), b"third".to_vec());
592            assert_eq!(previous, Some(b"second".to_vec()));
593
594            // Current value is the latest
595            assert_eq!(metadata.get(&key), Some(&b"third".to_vec()));
596
597            // Different key returns None
598            let other_key = U64::new(99);
599            let previous = metadata.put(other_key.clone(), b"other".to_vec());
600            assert!(previous.is_none());
601
602            // Sync and verify persistence
603            metadata.sync().await.unwrap();
604
605            let cfg = Config {
606                partition: "test".into(),
607                codec_config: ((0..).into(), ()),
608            };
609            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
610                .await
611                .unwrap();
612
613            // After restart, put still returns previous value
614            let previous = metadata.put(key.clone(), b"fourth".to_vec());
615            assert_eq!(previous, Some(b"third".to_vec()));
616
617            metadata.destroy().await.unwrap();
618        });
619    }
620
621    #[test_traced]
622    fn test_multi_sync() {
623        // Initialize the deterministic context
624        let executor = deterministic::Runner::default();
625        executor.start(|context| async move {
626            // Create a metadata store
627            let cfg = Config {
628                partition: "test".into(),
629                codec_config: ((0..).into(), ()),
630            };
631            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
632                .await
633                .unwrap();
634
635            // Put a key
636            let key = U64::new(42);
637            let hello = b"hello".to_vec();
638            metadata.put(key.clone(), hello.clone());
639
640            // Sync the metadata store
641            metadata = metadata.sync().await.unwrap();
642
643            // Check metrics
644            let buffer = context.encode();
645            assert!(buffer.contains("first_sync_rewrites_total 1"));
646            assert!(buffer.contains("first_sync_overwrites_total 0"));
647            assert!(buffer.contains("first_keys 1"));
648
649            // Put an overlapping key and a new key
650            let world = b"world".to_vec();
651            metadata.put(key.clone(), world.clone());
652            let key2 = U64::new(43);
653            let foo = b"foo".to_vec();
654            metadata.put(key2.clone(), foo.clone());
655
656            // Sync the metadata store
657            metadata = metadata.sync().await.unwrap();
658
659            // Check metrics
660            let buffer = context.encode();
661            assert!(buffer.contains("first_sync_rewrites_total 2"));
662            assert!(buffer.contains("first_sync_overwrites_total 0"));
663            assert!(buffer.contains("first_keys 2"));
664
665            // Reopen the metadata store
666            drop(metadata);
667            let cfg = Config {
668                partition: "test".into(),
669                codec_config: ((0..).into(), ()),
670            };
671            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
672                .await
673                .unwrap();
674
675            // Check metrics
676            let buffer = context.encode();
677            assert!(buffer.contains("second_sync_rewrites_total 0"));
678            assert!(buffer.contains("second_sync_overwrites_total 0"));
679            assert!(buffer.contains("second_keys 2"));
680
681            // Get the key
682            let value = metadata.get(&key).unwrap();
683            assert_eq!(value, &world);
684            let value = metadata.get(&key2).unwrap();
685            assert_eq!(value, &foo);
686
687            // Remove the key
688            metadata.remove(&key);
689
690            // Sync the metadata store
691            metadata = metadata.sync().await.unwrap();
692
693            // Check metrics
694            let buffer = context.encode();
695            assert!(buffer.contains("second_sync_rewrites_total 1"));
696            assert!(buffer.contains("second_sync_overwrites_total 0"));
697            assert!(buffer.contains("second_keys 1"));
698
699            // Reopen the metadata store
700            drop(metadata);
701            let cfg = Config {
702                partition: "test".into(),
703                codec_config: ((0..).into(), ()),
704            };
705            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("third"), cfg)
706                .await
707                .unwrap();
708
709            // Check metrics
710            let buffer = context.encode();
711            assert!(buffer.contains("third_sync_rewrites_total 0"));
712            assert!(buffer.contains("third_sync_overwrites_total 0"));
713            assert!(buffer.contains("third_keys 1"));
714
715            // Get the key
716            let value = metadata.get(&key);
717            assert!(value.is_none());
718            let value = metadata.get(&key2).unwrap();
719            assert_eq!(value, &foo);
720
721            metadata.destroy().await.unwrap();
722        });
723    }
724
725    #[test_traced]
726    fn test_recover_corrupted_one() {
727        // Initialize the deterministic context
728        let executor = deterministic::Runner::default();
729        executor.start(|context| async move {
730            // Create a metadata store
731            let cfg = Config {
732                partition: "test".into(),
733                codec_config: ((0..).into(), ()),
734            };
735            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
736                .await
737                .unwrap();
738
739            // Put a key
740            let key = U64::new(42);
741            let hello = b"hello".to_vec();
742            metadata.put(key.clone(), hello.clone());
743
744            // Sync the metadata store
745            metadata = metadata.sync().await.unwrap();
746
747            // Put an overlapping key and a new key
748            let world = b"world".to_vec();
749            metadata.put(key.clone(), world.clone());
750            let key2 = U64::new(43);
751            let foo = b"foo".to_vec();
752            metadata.put(key2, foo.clone());
753
754            // Sync the metadata store
755            metadata.sync().await.unwrap();
756
757            // Corrupt the metadata store
758            let (blob, _) = context.open("test", b"left").await.unwrap();
759            blob.write_at(0, b"corrupted".to_vec(), WriteOptions::SYNC)
760                .await
761                .unwrap();
762
763            // Reopen the metadata store
764            let cfg = Config {
765                partition: "test".into(),
766                codec_config: ((0..).into(), ()),
767            };
768            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
769                .await
770                .unwrap();
771
772            // Get the key (falls back to non-corrupt)
773            let value = metadata.get(&key).unwrap();
774            assert_eq!(value, &hello);
775
776            metadata.destroy().await.unwrap();
777        });
778    }
779
780    #[test_traced]
781    fn test_recovered_mirror_supports_shrinking_rewrite() {
782        let executor = deterministic::Runner::default();
783        executor.start(|context| async move {
784            let cfg = Config {
785                partition: "test".into(),
786                codec_config: ((0..).into(), ()),
787            };
788            let mut metadata =
789                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
790                    .await
791                    .unwrap();
792            let key = U64::new(42);
793            let hello = b"hello".to_vec();
794            metadata.put(key.clone(), hello.clone());
795            metadata = metadata.sync().await.unwrap();
796            metadata.put(key.clone(), b"world".to_vec());
797            metadata.put(U64::new(43), b"foo".to_vec());
798            metadata.sync().await.unwrap();
799
800            // Corrupt the newer copy so the next initialization must repair it.
801            let (blob, _) = context.open("test", b"left").await.unwrap();
802            blob.write_at(0, b"corrupted".to_vec(), WriteOptions::SYNC)
803                .await
804                .unwrap();
805
806            // The repaired copy must support a shrinking rewrite: a stale tail left behind by
807            // recovery would survive the smaller write and poison the next reopen.
808            let mut metadata =
809                Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg.clone())
810                    .await
811                    .unwrap();
812            assert_eq!(metadata.get(&key).unwrap(), &hello);
813            metadata.clear();
814            metadata.sync().await.unwrap();
815
816            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("third"), cfg)
817                .await
818                .unwrap();
819            assert!(metadata.get(&key).is_none());
820            metadata.destroy().await.unwrap();
821        });
822    }
823
824    #[test_traced]
825    fn test_recover_corrupted_both() {
826        // Initialize the deterministic context
827        let executor = deterministic::Runner::default();
828        executor.start(|context| async move {
829            // Create a metadata store
830            let cfg = Config {
831                partition: "test".into(),
832                codec_config: ((0..).into(), ()),
833            };
834            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
835                .await
836                .unwrap();
837
838            // Put a key
839            let key = U64::new(42);
840            let hello = b"hello".to_vec();
841            metadata.put(key.clone(), hello.clone());
842
843            // Sync the metadata store
844            metadata = metadata.sync().await.unwrap();
845
846            // Put an overlapping key and a new key
847            let world = b"world".to_vec();
848            metadata.put(key.clone(), world.clone());
849            let key2 = U64::new(43);
850            let foo = b"foo".to_vec();
851            metadata.put(key2, foo.clone());
852
853            // Sync the metadata store
854            metadata.sync().await.unwrap();
855
856            // Corrupt the metadata store
857            let (blob, _) = context.open("test", b"left").await.unwrap();
858            blob.write_at(0, b"corrupted".to_vec(), WriteOptions::SYNC)
859                .await
860                .unwrap();
861            let (blob, _) = context.open("test", b"right").await.unwrap();
862            blob.write_at(0, b"corrupted".to_vec(), WriteOptions::SYNC)
863                .await
864                .unwrap();
865
866            // Both copies failing validation is impossible under a crash (syncs alternate and
867            // drain), so reopening must fail loudly rather than adopt a fresh store.
868            for child in ["second", "third"] {
869                let cfg = Config {
870                    partition: "test".into(),
871                    codec_config: ((0..).into(), ()),
872                };
873                let result = Metadata::<_, U64, Vec<u8>>::init(context.child(child), cfg).await;
874                assert!(matches!(result, Err(Error::Corruption(_))));
875            }
876        });
877    }
878
879    #[test_traced]
880    fn test_recover_corrupted_truncate() {
881        // Initialize the deterministic context
882        let executor = deterministic::Runner::default();
883        executor.start(|context| async move {
884            // Create a metadata store
885            let cfg = Config {
886                partition: "test".into(),
887                codec_config: ((0..).into(), ()),
888            };
889            let mut metadata = Metadata::init(context.child("first"), cfg).await.unwrap();
890
891            // Put a key
892            let key = U64::new(42);
893            let hello = b"hello".to_vec();
894            metadata.put(key.clone(), hello.clone());
895
896            // Sync the metadata store
897            metadata = metadata.sync().await.unwrap();
898
899            // Put an overlapping key and a new key
900            let world = b"world".to_vec();
901            metadata.put(key.clone(), world.clone());
902            let key2 = U64::new(43);
903            let foo = b"foo".to_vec();
904            metadata.put(key2, foo.clone());
905
906            // Sync the metadata store
907            metadata.sync().await.unwrap();
908
909            // Corrupt the metadata store
910            let (blob, len) = context.open("test", b"left").await.unwrap();
911            blob.resize(len - 8).await.unwrap();
912            blob.sync().await.unwrap();
913
914            // Reopen the metadata store
915            let cfg = Config {
916                partition: "test".into(),
917                codec_config: ((0..).into(), ()),
918            };
919            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
920                .await
921                .unwrap();
922
923            // Get the key (falls back to non-corrupt)
924            let value = metadata.get(&key).unwrap();
925            assert_eq!(value, &hello);
926
927            metadata.destroy().await.unwrap();
928        });
929    }
930
931    #[test_traced]
932    fn test_recover_corrupted_short() {
933        // Initialize the deterministic context
934        let executor = deterministic::Runner::default();
935        executor.start(|context| async move {
936            // Create a metadata store
937            let cfg = Config {
938                partition: "test".into(),
939                codec_config: ((0..).into(), ()),
940            };
941            let mut metadata = Metadata::init(context.child("first"), cfg).await.unwrap();
942
943            // Put a key
944            let key = U64::new(42);
945            let hello = b"hello".to_vec();
946            metadata.put(key.clone(), hello.clone());
947
948            // Sync the metadata store
949            metadata = metadata.sync().await.unwrap();
950
951            // Put an overlapping key and a new key
952            let world = b"world".to_vec();
953            metadata.put(key.clone(), world.clone());
954            let key2 = U64::new(43);
955            let foo = b"foo".to_vec();
956            metadata.put(key2, foo.clone());
957
958            // Sync the metadata store
959            metadata.sync().await.unwrap();
960
961            // Corrupt the metadata store
962            let (blob, _) = context.open("test", b"left").await.unwrap();
963            blob.resize(5).await.unwrap();
964            blob.sync().await.unwrap();
965
966            // Reopen the metadata store
967            let cfg = Config {
968                partition: "test".into(),
969                codec_config: ((0..).into(), ()),
970            };
971            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
972                .await
973                .unwrap();
974
975            // Get the key (falls back to non-corrupt)
976            let value = metadata.get(&key).unwrap();
977            assert_eq!(value, &hello);
978
979            metadata.destroy().await.unwrap();
980        });
981    }
982
983    #[test_traced]
984    fn test_unclean_shutdown() {
985        // Initialize the deterministic context
986        let executor = deterministic::Runner::default();
987        executor.start(|context| async move {
988            let key = U64::new(42);
989            let hello = b"hello".to_vec();
990            {
991                // Create a metadata store
992                let cfg = Config {
993                    partition: "test".into(),
994                    codec_config: ((0..).into(), ()),
995                };
996                let mut metadata = Metadata::init(context.child("first"), cfg).await.unwrap();
997
998                // Put a key
999                metadata.put(key.clone(), hello.clone());
1000
1001                // Drop metadata before sync
1002            }
1003
1004            // Reopen the metadata store
1005            let cfg = Config {
1006                partition: "test".into(),
1007                codec_config: ((0..).into(), ()),
1008            };
1009            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1010                .await
1011                .unwrap();
1012
1013            // Get the key
1014            let value = metadata.get(&key);
1015            assert!(value.is_none());
1016
1017            // Check metrics
1018            let buffer = context.encode();
1019            assert!(buffer.contains("second_sync_rewrites_total 0"));
1020            assert!(buffer.contains("second_sync_overwrites_total 0"));
1021            assert!(buffer.contains("second_keys 0"));
1022
1023            metadata.destroy().await.unwrap();
1024        });
1025    }
1026
1027    #[test_traced]
1028    #[should_panic(expected = "usize value is larger than u32")]
1029    fn test_value_too_big_error() {
1030        // Initialize the deterministic context
1031        let executor = deterministic::Runner::default();
1032        executor.start(|context| async move {
1033            // Create a metadata store
1034            let cfg = Config {
1035                partition: "test".into(),
1036                codec_config: ((0..).into(), ()),
1037            };
1038            let mut metadata = Metadata::init(context.child("storage"), cfg).await.unwrap();
1039
1040            // Create a value that exceeds u32::MAX bytes
1041            let value = vec![0u8; (u32::MAX as usize) + 1];
1042            metadata.put(U64::new(1), value);
1043
1044            // Assert
1045            metadata.sync().await.unwrap();
1046        });
1047    }
1048
1049    #[test_traced]
1050    fn test_delta_writes() {
1051        // Initialize the deterministic context
1052        let executor = deterministic::Runner::default();
1053        executor.start(|context| async move {
1054            // Create a metadata store
1055            let cfg = Config {
1056                partition: "test".into(),
1057                codec_config: ((0..).into(), ()),
1058            };
1059            let mut metadata = Metadata::init(context.child("storage"), cfg).await.unwrap();
1060
1061            // Put initial keys
1062            for i in 0..100 {
1063                metadata.put(U64::new(i), vec![i as u8; 100]);
1064            }
1065
1066            // First sync - should write everything to the first blob
1067            //
1068            // 100 keys * (8 bytes for key + 1 byte for len + 100 bytes for value) + 8 bytes for version + 4 bytes for checksum
1069            metadata = metadata.sync().await.unwrap();
1070            let buffer = context.encode();
1071            assert!(buffer.contains("sync_rewrites_total 1"), "{buffer}");
1072            assert!(buffer.contains("sync_overwrites_total 0"), "{buffer}");
1073            assert!(
1074                buffer.contains("runtime_storage_write_bytes_total 10912"),
1075                "{buffer}",
1076            );
1077
1078            // Modify just one key
1079            metadata.put(U64::new(51), vec![0xff; 100]);
1080
1081            // Sync again - should write everything to the second blob
1082            metadata = metadata.sync().await.unwrap();
1083            let buffer = context.encode();
1084            assert!(buffer.contains("sync_rewrites_total 2"), "{buffer}");
1085            assert!(buffer.contains("sync_overwrites_total 0"), "{buffer}");
1086            assert!(
1087                buffer.contains("runtime_storage_write_bytes_total 21824"),
1088                "{buffer}",
1089            );
1090
1091            // Sync again - should write only diff from the first blob
1092            //
1093            // 1 byte for len + 100 bytes for value + 8 byte for version + 4 bytes for checksum
1094            metadata = metadata.sync().await.unwrap();
1095            let buffer = context.encode();
1096            assert!(buffer.contains("sync_rewrites_total 2"), "{buffer}");
1097            assert!(buffer.contains("sync_overwrites_total 1"), "{buffer}");
1098            assert!(
1099                buffer.contains("runtime_storage_write_bytes_total 21937"),
1100                "{buffer}",
1101            );
1102
1103            // Sync again - both blobs already contain the latest state
1104            metadata = metadata.sync().await.unwrap();
1105            let buffer = context.encode();
1106            assert!(buffer.contains("sync_rewrites_total 2"), "{buffer}");
1107            assert!(buffer.contains("sync_overwrites_total 1"), "{buffer}");
1108            assert!(
1109                buffer.contains("runtime_storage_write_bytes_total 21937"),
1110                "{buffer}",
1111            );
1112
1113            // Remove a key - should rewrite everything
1114            //
1115            // 99 keys * (8 bytes for key + 1 bytes for len + 100 bytes for value) + 8 bytes for version + 4 bytes for checksum
1116            metadata.remove(&U64::new(51));
1117            metadata = metadata.sync().await.unwrap();
1118            let buffer = context.encode();
1119            assert!(buffer.contains("sync_rewrites_total 3"), "{buffer}");
1120            assert!(buffer.contains("sync_overwrites_total 1"), "{buffer}");
1121            assert!(
1122                buffer.contains("runtime_storage_write_bytes_total 32740"),
1123                "{buffer}"
1124            );
1125
1126            // Sync again - should also rewrite
1127            metadata = metadata.sync().await.unwrap();
1128            let buffer = context.encode();
1129            assert!(buffer.contains("sync_rewrites_total 4"), "{buffer}");
1130            assert!(buffer.contains("sync_overwrites_total 1"), "{buffer}");
1131            assert!(
1132                buffer.contains("runtime_storage_write_bytes_total 43543"),
1133                "{buffer}"
1134            );
1135
1136            // Modify in-place - should overwrite
1137            //
1138            // 1 byte for len + 100 bytes for value + 8 byte for version + 4 bytes for checksum
1139            metadata.put(U64::new(50), vec![0xff; 100]);
1140            metadata = metadata.sync().await.unwrap();
1141            let buffer = context.encode();
1142            assert!(buffer.contains("sync_rewrites_total 4"), "{buffer}");
1143            assert!(buffer.contains("sync_overwrites_total 2"), "{buffer}");
1144            assert!(
1145                buffer.contains("runtime_storage_write_bytes_total 43656"),
1146                "{buffer}"
1147            );
1148
1149            // Clean up
1150            metadata.destroy().await.unwrap();
1151        });
1152    }
1153
1154    #[test_traced]
1155    fn test_multi_key_overwrites() {
1156        let executor = deterministic::Runner::default();
1157        executor.start(|context| async move {
1158            let cfg = Config {
1159                partition: "test".into(),
1160                codec_config: ((0..).into(), ()),
1161            };
1162            let mut metadata =
1163                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1164                    .await
1165                    .unwrap();
1166
1167            // Put initial keys and populate both blobs
1168            for i in 0..100 {
1169                metadata.put(U64::new(i), vec![i as u8; 100]);
1170            }
1171            metadata = metadata.sync().await.unwrap();
1172            metadata = metadata.sync().await.unwrap();
1173            let buffer = context.encode();
1174            assert!(buffer.contains("first_sync_rewrites_total 2"), "{buffer}");
1175            assert!(
1176                buffer.contains("runtime_storage_write_bytes_total 21824"),
1177                "{buffer}",
1178            );
1179
1180            // Modify several keys with same-size values
1181            for i in [10u64, 11, 12, 50, 98, 99] {
1182                metadata.put(U64::new(i), vec![0xAA; 100]);
1183            }
1184
1185            // Sync writes one delta per modified value.
1186            //
1187            // 6 * (1 byte for len + 100 bytes for value) + 8 bytes for version
1188            // + 4 bytes for checksum.
1189            metadata = metadata.sync().await.unwrap();
1190            let buffer = context.encode();
1191            assert!(buffer.contains("first_sync_rewrites_total 2"), "{buffer}");
1192            assert!(buffer.contains("first_sync_overwrites_total 1"), "{buffer}");
1193            assert!(
1194                buffer.contains("runtime_storage_write_bytes_total 22442"),
1195                "{buffer}",
1196            );
1197
1198            // Sync again - the same deltas propagate to the other blob
1199            metadata = metadata.sync().await.unwrap();
1200            let buffer = context.encode();
1201            assert!(buffer.contains("first_sync_overwrites_total 2"), "{buffer}");
1202            assert!(
1203                buffer.contains("runtime_storage_write_bytes_total 23060"),
1204                "{buffer}",
1205            );
1206
1207            // Sync again - both blobs already contain the latest state
1208            metadata = metadata.sync().await.unwrap();
1209            let buffer = context.encode();
1210            assert!(buffer.contains("first_sync_rewrites_total 2"), "{buffer}");
1211            assert!(buffer.contains("first_sync_overwrites_total 2"), "{buffer}");
1212            assert!(
1213                buffer.contains("runtime_storage_write_bytes_total 23060"),
1214                "{buffer}",
1215            );
1216
1217            // Mix a same-size update with a size-changing update. The overwrite
1218            // scan updates the mirror for the smaller key before the size change
1219            // forces a rewrite, which must discard that partial mutation.
1220            metadata.put(U64::new(20), vec![0xBB; 100]);
1221            metadata.put(U64::new(30), vec![0xCC; 150]);
1222            metadata = metadata.sync().await.unwrap();
1223            metadata = metadata.sync().await.unwrap();
1224            let buffer = context.encode();
1225            assert!(buffer.contains("first_sync_rewrites_total 4"), "{buffer}");
1226            assert!(buffer.contains("first_sync_overwrites_total 2"), "{buffer}");
1227
1228            // Restart the metadata store
1229            drop(metadata);
1230            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1231                .await
1232                .unwrap();
1233
1234            // Verify every value survived exactly
1235            for i in 0..100u64 {
1236                let expected = match i {
1237                    10 | 11 | 12 | 50 | 98 | 99 => vec![0xAA; 100],
1238                    20 => vec![0xBB; 100],
1239                    30 => vec![0xCC; 150],
1240                    _ => vec![i as u8; 100],
1241                };
1242                assert_eq!(metadata.get(&U64::new(i)).unwrap(), &expected, "key {i}");
1243            }
1244
1245            metadata.destroy().await.unwrap();
1246        });
1247    }
1248
1249    #[test_traced]
1250    fn test_sync_with_no_changes() {
1251        let executor = deterministic::Runner::default();
1252        executor.start(|context| async move {
1253            let cfg = Config {
1254                partition: "test".into(),
1255                codec_config: ((0..).into(), ()),
1256            };
1257            let mut metadata =
1258                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1259                    .await
1260                    .unwrap();
1261
1262            // Put initial data
1263            metadata = metadata
1264                .put_sync(U64::new(1), b"hello".to_vec())
1265                .await
1266                .unwrap();
1267
1268            // Sync again with no changes. This still rewrites because only one blob
1269            // has the new key order.
1270            metadata = metadata.sync().await.unwrap();
1271            let buffer = context.encode();
1272            assert!(buffer.contains("sync_rewrites_total 2"));
1273            assert!(buffer.contains("sync_overwrites_total 0"));
1274
1275            // Sync again - both blobs already contain the latest state
1276            metadata = metadata.sync().await.unwrap();
1277            let buffer = context.encode();
1278            assert!(buffer.contains("sync_rewrites_total 2"));
1279            assert!(buffer.contains("sync_overwrites_total 0"));
1280
1281            // Sync again - should remain a no-op
1282            metadata = metadata.sync().await.unwrap();
1283            let buffer = context.encode();
1284            assert!(buffer.contains("sync_rewrites_total 2"));
1285            assert!(buffer.contains("sync_overwrites_total 0"));
1286
1287            // Restart the metadata store and verify the no-op left durable state
1288            drop(metadata);
1289            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1290                .await
1291                .unwrap();
1292            assert_eq!(metadata.get(&U64::new(1)).unwrap(), b"hello");
1293
1294            metadata.destroy().await.unwrap();
1295        });
1296    }
1297
1298    #[test_traced]
1299    fn test_get_mut_marks_modified() {
1300        let executor = deterministic::Runner::default();
1301        executor.start(|context| async move {
1302            let cfg = Config {
1303                partition: "test".into(),
1304                codec_config: ((0..).into(), ()),
1305            };
1306            let mut metadata =
1307                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1308                    .await
1309                    .unwrap();
1310
1311            // Put initial data
1312            metadata = metadata
1313                .put_sync(U64::new(1), b"hello".to_vec())
1314                .await
1315                .unwrap();
1316
1317            // Sync again to ensure both blobs are populated
1318            metadata = metadata.sync().await.unwrap();
1319
1320            // Use get_mut to modify value
1321            let value = metadata.get_mut(&U64::new(1)).unwrap();
1322            value[0] = b'H';
1323
1324            // Sync should detect the modification and do a rewrite (due to recent key_order_changed)
1325            metadata = metadata.sync().await.unwrap();
1326            let buffer = context.encode();
1327            assert!(buffer.contains("first_sync_rewrites_total 2"));
1328            assert!(buffer.contains("first_sync_overwrites_total 1"));
1329
1330            // Restart the metadata store
1331            drop(metadata);
1332            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1333                .await
1334                .unwrap();
1335
1336            // Verify the change persisted
1337            let value = metadata.get(&U64::new(1)).unwrap();
1338            assert_eq!(value[0], b'H');
1339
1340            metadata.destroy().await.unwrap();
1341        });
1342    }
1343
1344    #[test_traced]
1345    fn test_mixed_operation_sequences() {
1346        let executor = deterministic::Runner::default();
1347        executor.start(|context| async move {
1348            let cfg = Config {
1349                partition: "test".into(),
1350                codec_config: ((0..).into(), ()),
1351            };
1352            let mut metadata =
1353                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1354                    .await
1355                    .unwrap();
1356
1357            let key = U64::new(1);
1358
1359            // Test: put -> remove -> put same key
1360            metadata.put(key.clone(), b"first".to_vec());
1361            metadata.remove(&key);
1362            metadata = metadata
1363                .put_sync(key.clone(), b"second".to_vec())
1364                .await
1365                .unwrap();
1366            let value = metadata.get(&key).unwrap();
1367            assert_eq!(value, b"second");
1368
1369            // Test: put -> get_mut -> remove -> put
1370            metadata.put(key.clone(), b"third".to_vec());
1371            let value = metadata.get_mut(&key).unwrap();
1372            value[0] = b'T';
1373            metadata.remove(&key);
1374            metadata = metadata
1375                .put_sync(key.clone(), b"fourth".to_vec())
1376                .await
1377                .unwrap();
1378            let value = metadata.get(&key).unwrap();
1379            assert_eq!(value, b"fourth");
1380
1381            // Restart the metadata store
1382            drop(metadata);
1383            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1384                .await
1385                .unwrap();
1386
1387            // Verify the changes persisted
1388            let value = metadata.get(&key).unwrap();
1389            assert_eq!(value, b"fourth");
1390
1391            metadata.destroy().await.unwrap();
1392        });
1393    }
1394
1395    #[test_traced]
1396    fn test_overwrite_vs_rewrite() {
1397        let executor = deterministic::Runner::default();
1398        executor.start(|context| async move {
1399            let cfg = Config {
1400                partition: "test".into(),
1401                codec_config: ((0..).into(), ()),
1402            };
1403            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("storage"), cfg)
1404                .await
1405                .unwrap();
1406
1407            // Set up initial data
1408            metadata.put(U64::new(1), vec![1; 10]);
1409            metadata.put(U64::new(2), vec![2; 10]);
1410            metadata = metadata.sync().await.unwrap();
1411
1412            // Same size modification before both blobs are populated
1413            metadata.put(U64::new(1), vec![0xFF; 10]);
1414            metadata = metadata.sync().await.unwrap();
1415            let buffer = context.encode();
1416            assert!(buffer.contains("sync_rewrites_total 2"));
1417            assert!(buffer.contains("sync_overwrites_total 0"));
1418
1419            // Let key order stabilize with another sync
1420            metadata = metadata.sync().await.unwrap();
1421            let buffer = context.encode();
1422            assert!(buffer.contains("sync_rewrites_total 2"));
1423            assert!(buffer.contains("sync_overwrites_total 1"));
1424
1425            // Same size modification after both blobs are populated - should overwrite
1426            metadata.put(U64::new(1), vec![0xAA; 10]);
1427            metadata = metadata.sync().await.unwrap();
1428            let buffer = context.encode();
1429            assert!(buffer.contains("sync_rewrites_total 2"));
1430            assert!(buffer.contains("sync_overwrites_total 2"));
1431
1432            // Different size modification - should rewrite
1433            metadata.put(U64::new(1), vec![0xFF; 20]);
1434            metadata = metadata.sync().await.unwrap();
1435            let buffer = context.encode();
1436            assert!(buffer.contains("sync_rewrites_total 3"));
1437            assert!(buffer.contains("sync_overwrites_total 2"));
1438
1439            // Add new key - should rewrite (key order changed)
1440            metadata.put(U64::new(3), vec![3; 10]);
1441            metadata = metadata.sync().await.unwrap();
1442            let buffer = context.encode();
1443            assert!(buffer.contains("sync_rewrites_total 4"));
1444            assert!(buffer.contains("sync_overwrites_total 2"));
1445
1446            // Stabilize key order
1447            metadata = metadata.sync().await.unwrap();
1448            let buffer = context.encode();
1449            assert!(buffer.contains("sync_rewrites_total 5"));
1450            assert!(buffer.contains("sync_overwrites_total 2"));
1451
1452            // Modify existing key with same size - should overwrite after stabilized
1453            metadata.put(U64::new(2), vec![0xAA; 10]);
1454            metadata = metadata.sync().await.unwrap();
1455            let buffer = context.encode();
1456            assert!(buffer.contains("sync_rewrites_total 5"));
1457            assert!(buffer.contains("sync_overwrites_total 3"));
1458
1459            metadata.destroy().await.unwrap();
1460        });
1461    }
1462
1463    #[test_traced]
1464    fn test_blob_resize() {
1465        let executor = deterministic::Runner::default();
1466        executor.start(|context| async move {
1467            let cfg = Config {
1468                partition: "test".into(),
1469                codec_config: ((0..).into(), ()),
1470            };
1471            let mut metadata =
1472                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1473                    .await
1474                    .unwrap();
1475
1476            // Start with large data
1477            for i in 0..10 {
1478                metadata.put(U64::new(i), vec![i as u8; 100]);
1479            }
1480            metadata = metadata.sync().await.unwrap();
1481
1482            // Stabilize key order
1483            metadata = metadata.sync().await.unwrap();
1484            let buffer = context.encode();
1485            assert!(buffer.contains("first_sync_rewrites_total 2"));
1486            assert!(buffer.contains("first_sync_overwrites_total 0"));
1487
1488            // Remove most data to make blob smaller
1489            for i in 1..10 {
1490                metadata.remove(&U64::new(i));
1491            }
1492            metadata = metadata.sync().await.unwrap();
1493
1494            // Verify the remaining data is still accessible
1495            let value = metadata.get(&U64::new(0)).unwrap();
1496            assert_eq!(value.len(), 100);
1497            assert_eq!(value[0], 0);
1498
1499            // Check that sync properly handles blob resizing
1500            let buffer = context.encode();
1501            assert!(buffer.contains("first_sync_rewrites_total 3"));
1502            assert!(buffer.contains("first_sync_overwrites_total 0"));
1503
1504            // Restart the metadata store
1505            drop(metadata);
1506            let metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1507                .await
1508                .unwrap();
1509
1510            // Verify the changes persisted
1511            let value = metadata.get(&U64::new(0)).unwrap();
1512            assert_eq!(value.len(), 100);
1513            assert_eq!(value[0], 0);
1514
1515            // Verify the removed keys are not present
1516            for i in 1..10 {
1517                assert!(metadata.get(&U64::new(i)).is_none());
1518            }
1519
1520            metadata.destroy().await.unwrap();
1521        });
1522    }
1523
1524    #[test_traced]
1525    fn test_clear_and_repopulate() {
1526        let executor = deterministic::Runner::default();
1527        executor.start(|context| async move {
1528            let cfg = Config {
1529                partition: "test".into(),
1530                codec_config: ((0..).into(), ()),
1531            };
1532            let mut metadata =
1533                Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg.clone())
1534                    .await
1535                    .unwrap();
1536
1537            // Initial data
1538            metadata.put(U64::new(1), b"first".to_vec());
1539            metadata = metadata
1540                .put_sync(U64::new(2), b"second".to_vec())
1541                .await
1542                .unwrap();
1543
1544            // Clear everything
1545            metadata.clear();
1546            metadata = metadata.sync().await.unwrap();
1547
1548            // Verify empty
1549            assert!(metadata.get(&U64::new(1)).is_none());
1550            assert!(metadata.get(&U64::new(2)).is_none());
1551
1552            // Restart the metadata store
1553            drop(metadata);
1554            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1555                .await
1556                .unwrap();
1557
1558            // Verify the changes persisted
1559            assert!(metadata.get(&U64::new(1)).is_none());
1560            assert!(metadata.get(&U64::new(2)).is_none());
1561
1562            // Repopulate with different data
1563            metadata.put(U64::new(3), b"third".to_vec());
1564            metadata = metadata
1565                .put_sync(U64::new(4), b"fourth".to_vec())
1566                .await
1567                .unwrap();
1568
1569            // Verify new data
1570            assert_eq!(metadata.get(&U64::new(3)).unwrap(), b"third");
1571            assert_eq!(metadata.get(&U64::new(4)).unwrap(), b"fourth");
1572            assert!(metadata.get(&U64::new(1)).is_none());
1573            assert!(metadata.get(&U64::new(2)).is_none());
1574
1575            metadata.destroy().await.unwrap();
1576        });
1577    }
1578
1579    fn test_metadata_operations_and_restart(num_operations: usize) -> String {
1580        let executor = deterministic::Runner::default();
1581        executor.start(|mut context| async move {
1582            let cfg = Config {
1583                partition: "test-determinism".into(),
1584                codec_config: ((0..).into(), ()),
1585            };
1586            let mut metadata =
1587                Metadata::<_, U64, Vec<u8>>::init(context.child("storage"), cfg.clone())
1588                    .await
1589                    .unwrap();
1590
1591            // Perform a series of deterministic operations
1592            for i in 0..num_operations {
1593                let key = U64::new(i as u64);
1594                let mut value = vec![0u8; 64];
1595                context.fill_bytes(&mut value);
1596                metadata.put(key, value);
1597
1598                // Sync occasionally
1599                if context.random_bool(0.1) {
1600                    metadata = metadata.sync().await.unwrap();
1601                }
1602
1603                // Update some existing keys
1604                if context.random_bool(0.1) {
1605                    let selected_index = context.random_range(0..=i);
1606                    let update_key = U64::new(selected_index as u64);
1607                    let mut new_value = vec![0u8; 64];
1608                    context.fill_bytes(&mut new_value);
1609                    metadata.put(update_key, new_value);
1610                }
1611
1612                // Remove some keys
1613                if context.random_bool(0.1) {
1614                    let selected_index = context.random_range(0..=i);
1615                    let remove_key = U64::new(selected_index as u64);
1616                    metadata.remove(&remove_key);
1617                }
1618
1619                // Use get_mut occasionally
1620                if context.random_bool(0.1) {
1621                    let selected_index = context.random_range(0..=i);
1622                    let mut_key = U64::new(selected_index as u64);
1623                    if let Some(value) = metadata.get_mut(&mut_key)
1624                        && !value.is_empty()
1625                    {
1626                        value[0] = value[0].wrapping_add(1);
1627                    }
1628                }
1629            }
1630            metadata = metadata.sync().await.unwrap();
1631
1632            // Destroy the metadata store
1633            metadata.destroy().await.unwrap();
1634
1635            context.auditor().state()
1636        })
1637    }
1638
1639    #[test_group("slow")]
1640    #[test_traced]
1641    fn test_determinism() {
1642        let state1 = test_metadata_operations_and_restart(1_000);
1643        let state2 = test_metadata_operations_and_restart(1_000);
1644        assert_eq!(state1, state2);
1645    }
1646
1647    #[test_traced]
1648    fn test_keys_iterator() {
1649        // Initialize the deterministic context
1650        let executor = deterministic::Runner::default();
1651        executor.start(|context| async move {
1652            // Create a metadata store
1653            let cfg = Config {
1654                partition: "test".into(),
1655                codec_config: ((0..).into(), ()),
1656            };
1657            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("storage"), cfg)
1658                .await
1659                .unwrap();
1660
1661            // Add some keys with different prefixes
1662            metadata.put(U64::new(0x1000), b"value1".to_vec());
1663            metadata.put(U64::new(0x1001), b"value2".to_vec());
1664            metadata.put(U64::new(0x1002), b"value3".to_vec());
1665            metadata.put(U64::new(0x2000), b"value4".to_vec());
1666            metadata.put(U64::new(0x2001), b"value5".to_vec());
1667            metadata.put(U64::new(0x3000), b"value6".to_vec());
1668
1669            // Test iterating over all keys
1670            let all_keys: Vec<_> = metadata.keys().cloned().collect();
1671            assert_eq!(all_keys.len(), 6);
1672            assert!(all_keys.contains(&U64::new(0x1000)));
1673            assert!(all_keys.contains(&U64::new(0x3000)));
1674
1675            // Test iterating with prefix 0x10
1676            let prefix = hex!("0x00000000000010");
1677            let prefix_keys: Vec<_> = metadata
1678                .keys()
1679                .filter(|k| k.as_ref().starts_with(&prefix))
1680                .cloned()
1681                .collect();
1682            assert_eq!(prefix_keys.len(), 3);
1683            assert!(prefix_keys.contains(&U64::new(0x1000)));
1684            assert!(prefix_keys.contains(&U64::new(0x1001)));
1685            assert!(prefix_keys.contains(&U64::new(0x1002)));
1686            assert!(!prefix_keys.contains(&U64::new(0x2000)));
1687
1688            // Test iterating with prefix 0x20
1689            let prefix = hex!("0x00000000000020");
1690            let prefix_keys: Vec<_> = metadata
1691                .keys()
1692                .filter(|k| k.as_ref().starts_with(&prefix))
1693                .cloned()
1694                .collect();
1695            assert_eq!(prefix_keys.len(), 2);
1696            assert!(prefix_keys.contains(&U64::new(0x2000)));
1697            assert!(prefix_keys.contains(&U64::new(0x2001)));
1698
1699            // Test with non-matching prefix
1700            let prefix = hex!("0x00000000000040");
1701            let prefix_keys: Vec<_> = metadata
1702                .keys()
1703                .filter(|k| k.as_ref().starts_with(&prefix))
1704                .cloned()
1705                .collect();
1706            assert_eq!(prefix_keys.len(), 0);
1707
1708            metadata.destroy().await.unwrap();
1709        });
1710    }
1711
1712    #[test_traced]
1713    fn test_retain() {
1714        // Initialize the deterministic context
1715        let executor = deterministic::Runner::default();
1716        executor.start(|context| async move {
1717            // Create a metadata store
1718            let cfg = Config {
1719                partition: "test".into(),
1720                codec_config: ((0..).into(), ()),
1721            };
1722            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("first"), cfg)
1723                .await
1724                .unwrap();
1725
1726            // Add some keys with different prefixes
1727            metadata.put(U64::new(0x1000), b"value1".to_vec());
1728            metadata.put(U64::new(0x1001), b"value2".to_vec());
1729            metadata.put(U64::new(0x1002), b"value3".to_vec());
1730            metadata.put(U64::new(0x2000), b"value4".to_vec());
1731            metadata.put(U64::new(0x2001), b"value5".to_vec());
1732            metadata.put(U64::new(0x3000), b"value6".to_vec());
1733
1734            // Check initial metrics
1735            let buffer = context.encode();
1736            assert!(buffer.contains("first_keys 6"));
1737
1738            // Remove keys with prefix 0x10
1739            let prefix = hex!("0x00000000000010");
1740            metadata.retain(|k, _| !k.as_ref().starts_with(&prefix));
1741
1742            // Check metrics after removal
1743            let buffer = context.encode();
1744            assert!(buffer.contains("first_keys 3"));
1745
1746            // Verify remaining keys
1747            assert!(metadata.get(&U64::new(0x1000)).is_none());
1748            assert!(metadata.get(&U64::new(0x1001)).is_none());
1749            assert!(metadata.get(&U64::new(0x1002)).is_none());
1750            assert!(metadata.get(&U64::new(0x2000)).is_some());
1751            assert!(metadata.get(&U64::new(0x2001)).is_some());
1752            assert!(metadata.get(&U64::new(0x3000)).is_some());
1753
1754            // Sync and reopen to ensure persistence
1755            metadata.sync().await.unwrap();
1756            let cfg = Config {
1757                partition: "test".into(),
1758                codec_config: ((0..).into(), ()),
1759            };
1760            let mut metadata = Metadata::<_, U64, Vec<u8>>::init(context.child("second"), cfg)
1761                .await
1762                .unwrap();
1763
1764            // Verify keys are still removed after restart
1765            assert!(metadata.get(&U64::new(0x1000)).is_none());
1766            assert!(metadata.get(&U64::new(0x2000)).is_some());
1767            assert_eq!(metadata.keys().count(), 3);
1768
1769            // Remove non-existing prefix
1770            let prefix = hex!("0x00000000000040");
1771            metadata.retain(|k, _| !k.as_ref().starts_with(&prefix));
1772
1773            // Remove all remaining keys
1774            metadata.retain(|_, _| false);
1775            assert_eq!(metadata.keys().count(), 0);
1776
1777            metadata.destroy().await.unwrap();
1778        });
1779    }
1780}