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