Skip to main content

commonware_storage/index/partitioned/ordered/
mod.rs

1//! A partitioned index that stores each partition as sorted struct-of-arrays (see the
2//! `self::partition` module).
3//!
4//! The first `P` bytes of the (untranslated) key select a partition; the translator maps the
5//! remaining bytes to the partition-local key. Because the partitions are ordered by prefix and each
6//! partition's entries are sorted by translated key, this index is inherently ordered. It trades
7//! lookup/insert speed for memory density at scale; the unordered variant ([`super::unordered`])
8//! uses hash sub-indices instead and is faster when ordering is not required.
9//!
10//! # Spilling over-full partitions
11//!
12//! Each sorted-array insert is an O(occupancy) memmove, so a partition that grows large makes
13//! inserts expensive. When a partition's array reaches `SPILL_THRESHOLD` entries it converts to a
14//! `BTreeMap` (the `spilled` field) -- a supported alternate representation whose insert, lookup,
15//! and traversal are O(log occupancy). A partition reaches that size two ways:
16//!
17//! - *Adversarial grinding.* An order-preserving translator cannot randomize keys (that would break
18//!   the ordering), so an attacker can grind the key suffix to flood one partition with distinct
19//!   translated keys. Spilling bounds flooding M keys from O(M^2) to O(M log M).
20//! - *Honest high-occupancy growth at low `P`.* With few partitions a uniform workload fills them: a
21//!   `P=1` index (256 partitions) is guaranteed to spill once it holds more than 256*511 = 130,816
22//!   entries, `P=2` past ~33M, while `P=3`'s 16.8M partitions push this past ~8.5B (so P=3 is
23//!   effectively unreachable under honest load).
24//!
25//! A partition also fills when a single key collects many values -- keys that collide on the full
26//! prefix, or repeated inserts of one key. The spill covers this too: it triggers on the total
27//! value count, so a single over-full key still converts the partition and keeps inserts for the
28//! partition's other keys cheap. Values append to the end of a key's run. In the single-key case
29//! this makes inline inserts append-only, and after spilling they remain append-only in the run's
30//! `Vec`. Other inline inserts may shift later key runs, but the spill threshold bounds this cost.
31//! What spilling cannot bound is how many values one key holds, and a lookup must scan all of
32//! them: a key with `M` values costs O(M) per lookup. Every index that resolves collisions pays
33//! this scan (the flat `crate::index::ordered::Index` included); `M` stays near 1 only when the
34//! indexed `P + N`-byte prefix is well-distributed, so use enough prefix bytes and high-entropy
35//! keys.
36//!
37//! A caller-held cursor can temporarily grow an inline partition to or past the spill threshold.
38//! The next index mutation of that partition spills it before access. `insert_and_retain` performs
39//! the check after releasing its internal cursor.
40
41mod cursor;
42mod partition;
43
44pub use self::cursor::Cursor;
45use self::partition::Partition;
46use crate::{
47    index::{
48        partitioned::partition_index_and_sub_key, Cursor as CursorTrait, Factory, Ordered,
49        Unordered,
50    },
51    translator::Translator,
52};
53use commonware_runtime::{
54    telemetry::metrics::{Counter, Gauge, MetricsExt as _},
55    Metrics,
56};
57use std::{
58    collections::{btree_map, hash_map, BTreeMap, HashMap},
59    ops::Bound,
60};
61
62/// Sorted-array length at which a partition converts to a `BTreeMap`, bounding the O(occupancy)
63/// insert memmove to O(log occupancy). A partition reaches this from adversarial distinct-key
64/// grinding or from honest growth once partitions fill: a spill is guaranteed past 256*511 = 130,816
65/// entries at `P=1`, past ~33M at `P=2`, and only past ~8.5B at `P=3` (so P=3 effectively never
66/// spills under honest load). See the module docs.
67const SPILL_THRESHOLD: usize = 512;
68
69/// A partitioned index storing each partition as sorted struct-of-arrays, spilling an over-full
70/// partition to a `BTreeMap` to bound its O(occupancy) insert cost (see `spilled` and the module
71/// docs).
72pub struct Index<T: Translator, V: Send + Sync, const P: usize> {
73    /// Translates the prefix-stripped key bytes into a partition-local key.
74    translator: T,
75
76    /// The `2^(8*P)` partitions, indexed by the `P`-byte key prefix. Each stores its translated
77    /// keys and values as sorted arrays (the inline representation); an emptied partition may
78    /// instead have spilled (see `spilled`).
79    partitions: Box<[Partition<T::Key, V>]>,
80
81    /// Partitions that have spilled out of their sorted arrays (reached `SPILL_THRESHOLD` entries),
82    /// keyed by partition index; each maps translated keys to their value runs. Empty until a
83    /// partition fills, whether from honest growth at low `P` or adversarial grinding.
84    spilled: HashMap<usize, BTreeMap<T::Key, Vec<V>>>,
85
86    /// Sorted-array length at which a partition spills to `spilled`; [SPILL_THRESHOLD] in
87    /// production, lowered by tests to exercise spilling cheaply.
88    threshold: usize,
89
90    /// Metric: distinct translated keys currently held across all partitions.
91    keys: Gauge,
92
93    /// Metric: stored values currently held across all partitions.
94    items: Gauge,
95
96    /// Metric: cumulative values removed (via `remove`, cursor `delete`, or `retain`).
97    pruned: Counter,
98}
99
100impl<T: Translator, V: Send + Sync, const P: usize> Index<T, V, P> {
101    /// Create a new [Index] with the given metrics context and translator.
102    pub fn new(ctx: impl Metrics, translator: T) -> Self {
103        const {
104            assert!(P > 0 && P <= 3, "P must be in 1..=3");
105        }
106        let count = 1usize << (P * 8);
107        let partitions = (0..count)
108            .map(|_| Partition::default())
109            .collect::<Vec<_>>()
110            .into_boxed_slice();
111        Self {
112            translator,
113            partitions,
114            spilled: HashMap::new(),
115            threshold: SPILL_THRESHOLD,
116            keys: ctx.gauge("keys", "Number of translated keys in the index"),
117            items: ctx.gauge("items", "Number of items in the index"),
118            pruned: ctx.counter("pruned", "Number of items pruned"),
119        }
120    }
121
122    /// Create a new [Index] with an explicit spill threshold so tests can exercise spilling without
123    /// inserting [SPILL_THRESHOLD] keys. The threshold must be at least 1: `maybe_spill` relies on
124    /// an already-spilled partition's empty inline array staying strictly below the threshold.
125    #[cfg(test)]
126    pub(crate) fn with_threshold(ctx: impl Metrics, translator: T, threshold: usize) -> Self {
127        assert!(threshold > 0, "spill threshold must be at least 1");
128        let mut index = Self::new(ctx, translator);
129        index.threshold = threshold;
130        index
131    }
132
133    /// Spill partition `i` to the side-table if its sorted array has reached the threshold.
134    fn maybe_spill(&mut self, i: usize) {
135        if self.partitions[i].len() < self.threshold {
136            return;
137        }
138        let inner: BTreeMap<T::Key, Vec<V>> = self.partitions[i].drain_runs().into_iter().collect();
139        self.spilled.insert(i, inner);
140    }
141
142    /// The `BTreeMap` of spilled partition `i`, or `None` if `i` has not spilled. The empty-map
143    /// check skips hashing `i` in the common case where no partition has ever spilled.
144    fn spilled_partition(&self, i: usize) -> Option<&BTreeMap<T::Key, Vec<V>>> {
145        if self.spilled.is_empty() {
146            return None;
147        }
148        self.spilled.get(&i)
149    }
150
151    /// The values for translated key `k` in partition `i` (empty if absent), from whichever
152    /// representation the partition currently uses.
153    fn partition_values(&self, i: usize, k: &T::Key) -> &[V] {
154        if !self.partitions[i].is_empty() {
155            return self.partitions[i].values(k);
156        }
157        self.spilled_partition(i)
158            .and_then(|inner| inner.get(k))
159            .map_or(&[], Vec::as_slice)
160    }
161
162    /// Values of the smallest key in partition `i` (None if the partition is empty).
163    fn partition_first(&self, i: usize) -> Option<&[V]> {
164        self.partitions[i].first_values().or_else(|| {
165            self.spilled_partition(i)?
166                .first_key_value()
167                .map(|(_, v)| v.as_slice())
168        })
169    }
170
171    /// Values of the largest key in partition `i` (None if the partition is empty).
172    fn partition_last(&self, i: usize) -> Option<&[V]> {
173        self.partitions[i].last_values().or_else(|| {
174            self.spilled_partition(i)?
175                .last_key_value()
176                .map(|(_, v)| v.as_slice())
177        })
178    }
179
180    /// Whether the index currently holds no keys.
181    fn is_empty(&self) -> bool {
182        self.keys.get() == 0
183    }
184
185    /// Values of the smallest key strictly greater than `k` in partition `i`.
186    fn partition_next_after(&self, i: usize, k: &T::Key) -> Option<&[V]> {
187        self.partitions[i].next_values_after(k).or_else(|| {
188            self.spilled_partition(i)?
189                .range((Bound::Excluded(*k), Bound::Unbounded))
190                .next()
191                .map(|(_, v)| v.as_slice())
192        })
193    }
194
195    /// Values of the largest key strictly less than `k` in partition `i`.
196    fn partition_prev_before(&self, i: usize, k: &T::Key) -> Option<&[V]> {
197        self.partitions[i].prev_values_before(k).or_else(|| {
198            self.spilled_partition(i)?
199                .range((Bound::Unbounded, Bound::Excluded(*k)))
200                .next_back()
201                .map(|(_, v)| v.as_slice())
202        })
203    }
204
205    /// Number of partitions currently spilled to the side-table.
206    #[cfg(test)]
207    pub(crate) fn spilled_count(&self) -> usize {
208        self.spilled.len()
209    }
210}
211
212impl<T: Translator, V: Send + Sync, const P: usize> Factory<T> for Index<T, V, P> {
213    fn new(ctx: impl Metrics, translator: T) -> Self {
214        Self::new(ctx, translator)
215    }
216}
217
218impl<T: Translator, V: Send + Sync, const P: usize> Unordered for Index<T, V, P> {
219    type Value = V;
220    type Cursor<'a>
221        = Cursor<'a, T::Key, V>
222    where
223        Self: 'a;
224
225    fn get<'a>(&'a self, key: &[u8]) -> impl Iterator<Item = &'a V> + Send + 'a
226    where
227        V: 'a,
228    {
229        let (i, sub) = partition_index_and_sub_key::<P>(key);
230        let k = self.translator.transform(sub);
231        self.partition_values(i, &k).iter()
232    }
233
234    fn get_many<'a, K: AsRef<[u8]>>(&'a self, keys: &[K], mut visit: impl FnMut(usize, &'a V))
235    where
236        V: 'a,
237    {
238        // Probe in (partition, translated-key) order so consecutive probes hit the same partition
239        // (one region of the 2^(8*P)-entry partition array) and the same value run within it,
240        // instead of scattering across partitions in input order.
241        let mut order: Vec<(usize, T::Key, usize)> = keys
242            .iter()
243            .enumerate()
244            .map(|(key_idx, key)| {
245                let (partition, sub) = partition_index_and_sub_key::<P>(key.as_ref());
246                (partition, self.translator.transform(sub), key_idx)
247            })
248            .collect();
249        order.sort_unstable();
250        for (partition, translated, key_idx) in order {
251            for value in self.partition_values(partition, &translated) {
252                visit(key_idx, value);
253            }
254        }
255    }
256
257    fn get_mut<'a>(&'a mut self, key: &[u8]) -> Option<Self::Cursor<'a>> {
258        let (i, sub) = partition_index_and_sub_key::<P>(key);
259        let k = self.translator.transform(sub);
260        self.maybe_spill(i);
261        if !self.partitions[i].is_empty() {
262            let run = self.partitions[i].run_range(&k);
263            if run.is_empty() {
264                return None;
265            }
266            return Some(Cursor::soa(
267                &mut self.partitions[i],
268                k,
269                run,
270                &self.keys,
271                &self.items,
272                &self.pruned,
273            ));
274        }
275
276        // Hand out a spilled cursor if the partition has spilled and holds `k`.
277        if self
278            .spilled_partition(i)
279            .is_some_and(|inner| inner.contains_key(&k))
280        {
281            return Some(Cursor::spilled(
282                &mut self.spilled,
283                i,
284                k,
285                &self.keys,
286                &self.items,
287                &self.pruned,
288            ));
289        }
290
291        // Partition is genuinely empty.
292        None
293    }
294
295    fn get_mut_or_insert<'a>(
296        &'a mut self,
297        key: &[u8],
298        value: Self::Value,
299    ) -> Option<Self::Cursor<'a>> {
300        let (i, sub) = partition_index_and_sub_key::<P>(key);
301        let k = self.translator.transform(sub);
302        self.maybe_spill(i);
303        if !self.partitions[i].is_empty() {
304            let run = self.partitions[i].run_range(&k);
305            if !run.is_empty() {
306                return Some(Cursor::soa(
307                    &mut self.partitions[i],
308                    k,
309                    run,
310                    &self.keys,
311                    &self.items,
312                    &self.pruned,
313                ));
314            }
315            self.partitions[i].insert_at(run.end, k, value);
316            self.keys.inc();
317            self.items.inc();
318            self.maybe_spill(i);
319            return None;
320        }
321
322        // Partition i is empty. If it's because it has spilled, serve or create the key in its
323        // `BTreeMap`.
324        if let Some(inner) = self.spilled_partition(i) {
325            if inner.contains_key(&k) {
326                return Some(Cursor::spilled(
327                    &mut self.spilled,
328                    i,
329                    k,
330                    &self.keys,
331                    &self.items,
332                    &self.pruned,
333                ));
334            }
335            self.spilled.get_mut(&i).unwrap().insert(k, vec![value]);
336            self.keys.inc();
337            self.items.inc();
338            return None;
339        }
340
341        // Partition i is genuinely empty: start a fresh sorted array.
342        self.partitions[i].insert_at(0, k, value);
343        self.keys.inc();
344        self.items.inc();
345        self.maybe_spill(i);
346
347        None
348    }
349
350    fn insert(&mut self, key: &[u8], value: Self::Value) {
351        let (i, sub) = partition_index_and_sub_key::<P>(key);
352        let k = self.translator.transform(sub);
353        self.maybe_spill(i);
354        if !self.partitions[i].is_empty() {
355            let run = self.partitions[i].run_range(&k);
356            let new_key = run.is_empty();
357            self.partitions[i].insert_at(run.end, k, value);
358            self.items.inc();
359            if new_key {
360                self.keys.inc();
361            }
362            self.maybe_spill(i);
363            return;
364        }
365
366        // Route into the spilled partition's `BTreeMap`.
367        if !self.spilled.is_empty() {
368            if let hash_map::Entry::Occupied(mut partition) = self.spilled.entry(i) {
369                match partition.get_mut().entry(k) {
370                    btree_map::Entry::Occupied(mut run) => run.get_mut().push(value),
371                    btree_map::Entry::Vacant(run) => {
372                        run.insert(vec![value]);
373                        self.keys.inc();
374                    }
375                }
376                self.items.inc();
377                return;
378            }
379        }
380
381        // Genuinely empty partition: start a fresh sorted array.
382        self.partitions[i].insert_at(0, k, value);
383        self.items.inc();
384        self.keys.inc();
385        self.maybe_spill(i);
386    }
387
388    fn insert_and_retain(
389        &mut self,
390        key: &[u8],
391        value: Self::Value,
392        should_retain: impl Fn(&Self::Value) -> bool,
393    ) {
394        let (i, _) = partition_index_and_sub_key::<P>(key);
395        if let Some(mut cursor) = self.get_mut(key) {
396            cursor.retain(&should_retain);
397            if should_retain(&value) {
398                cursor.insert(value);
399            }
400        } else if should_retain(&value) {
401            self.insert(key, value);
402        }
403        self.maybe_spill(i);
404    }
405
406    fn remove(&mut self, key: &[u8]) {
407        let (i, sub) = partition_index_and_sub_key::<P>(key);
408        let k = self.translator.transform(sub);
409        self.maybe_spill(i);
410        if !self.partitions[i].is_empty() {
411            let run = self.partitions[i].run_range(&k);
412            if run.is_empty() {
413                return;
414            }
415            let n = run.len();
416            self.partitions[i].remove_run(run);
417            self.keys.dec();
418            self.items.dec_by(n as i64);
419            self.pruned.inc_by(n as u64);
420            return;
421        }
422        // Partition i is empty here; if spilled, remove from its `BTreeMap` (and drop the
423        // partition entry, reverting to an empty sorted array, once its last key is gone).
424        if !self.spilled.is_empty() {
425            if let hash_map::Entry::Occupied(mut partition) = self.spilled.entry(i) {
426                if let Some(vals) = partition.get_mut().remove(&k) {
427                    let n = vals.len();
428                    self.keys.dec();
429                    self.items.dec_by(n as i64);
430                    self.pruned.inc_by(n as u64);
431                    if partition.get().is_empty() {
432                        partition.remove();
433                    }
434                }
435            }
436        }
437    }
438
439    #[cfg(test)]
440    fn keys(&self) -> usize {
441        self.keys.get() as usize
442    }
443
444    #[cfg(test)]
445    fn items(&self) -> usize {
446        self.items.get() as usize
447    }
448
449    #[cfg(test)]
450    fn pruned(&self) -> usize {
451        self.pruned.get() as usize
452    }
453}
454
455impl<T: Translator, V: Send + Sync, const P: usize> Ordered for Index<T, V, P> {
456    fn prev_translated_key<'a>(
457        &'a self,
458        key: &[u8],
459    ) -> Option<(impl Iterator<Item = &'a V> + Send + 'a, bool)>
460    where
461        V: 'a,
462    {
463        // Skip the all-partitions scan when there is nothing to find.
464        if self.is_empty() {
465            return None;
466        }
467
468        // The largest translated key strictly less than `k`: within the partition first, then the
469        // last key of the nearest lower partition, else cycle to the global last key.
470        let (i, sub) = partition_index_and_sub_key::<P>(key);
471        let k = self.translator.transform(sub);
472        if let Some(vals) = self.partition_prev_before(i, &k) {
473            return Some((vals.iter(), false));
474        }
475        for p in (0..i).rev() {
476            if let Some(vals) = self.partition_last(p) {
477                return Some((vals.iter(), false));
478            }
479        }
480        for p in (0..self.partitions.len()).rev() {
481            if let Some(vals) = self.partition_last(p) {
482                return Some((vals.iter(), true));
483            }
484        }
485        None
486    }
487
488    fn next_translated_key<'a>(
489        &'a self,
490        key: &[u8],
491    ) -> Option<(impl Iterator<Item = &'a V> + Send + 'a, bool)>
492    where
493        V: 'a,
494    {
495        // Skip the all-partitions scan when there is nothing to find.
496        if self.is_empty() {
497            return None;
498        }
499
500        // The smallest translated key strictly greater than `k`: within the partition first, then
501        // the first key of the nearest higher partition, else cycle to the global first key.
502        let (i, sub) = partition_index_and_sub_key::<P>(key);
503        let k = self.translator.transform(sub);
504        if let Some(vals) = self.partition_next_after(i, &k) {
505            return Some((vals.iter(), false));
506        }
507        for p in i + 1..self.partitions.len() {
508            if let Some(vals) = self.partition_first(p) {
509                return Some((vals.iter(), false));
510            }
511        }
512        for p in 0..self.partitions.len() {
513            if let Some(vals) = self.partition_first(p) {
514                return Some((vals.iter(), true));
515            }
516        }
517        None
518    }
519
520    fn first_translated_key<'a>(&'a self) -> Option<impl Iterator<Item = &'a V> + Send + 'a>
521    where
522        V: 'a,
523    {
524        // Skip the all-partitions scan when there is nothing to find.
525        if self.is_empty() {
526            return None;
527        }
528
529        // Scan partitions in ascending order for the global first key.
530        for p in 0..self.partitions.len() {
531            if let Some(vals) = self.partition_first(p) {
532                return Some(vals.iter());
533            }
534        }
535        None
536    }
537
538    fn last_translated_key<'a>(&'a self) -> Option<impl Iterator<Item = &'a V> + Send + 'a>
539    where
540        V: 'a,
541    {
542        // Skip the all-partitions scan when there is nothing to find.
543        if self.is_empty() {
544            return None;
545        }
546
547        // Scan partitions in descending order for the global last key.
548        for p in (0..self.partitions.len()).rev() {
549            if let Some(vals) = self.partition_last(p) {
550                return Some(vals.iter());
551            }
552        }
553        None
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560    use crate::translator::OneCap;
561    use commonware_formatting::hex;
562    use commonware_macros::test_traced;
563    use commonware_runtime::{deterministic, Runner};
564
565    fn new_index(context: deterministic::Context) -> Index<OneCap, u64, 1> {
566        Index::new(context, OneCap)
567    }
568
569    /// Index with a tiny spill threshold: a partition spills once it holds two entries. With
570    /// `OneCap` + P=1 the key byte selects the partition and the next byte is the translated key,
571    /// so keys sharing a first byte land in one partition.
572    fn new_index_spilling(context: deterministic::Context) -> Index<OneCap, u64, 1> {
573        Index::with_threshold(context, OneCap, 2)
574    }
575
576    #[test_traced]
577    fn test_empty_and_sparse_nav() {
578        deterministic::Runner::default().start(|context| async move {
579            let mut index = new_index(context);
580
581            // Empty index: every ordered navigation returns None (via the empty fast path, without
582            // scanning all partitions).
583            assert!(index.first_translated_key().is_none());
584            assert!(index.last_translated_key().is_none());
585            assert!(index.prev_translated_key(&[0x80, 0x00]).is_none());
586            assert!(index.next_translated_key(&[0x80, 0x00]).is_none());
587
588            // Two keys in widely separated partitions (0x05 and 0xF0): neighbor scans must still
589            // cross the large empty gap between them.
590            index.insert(&[0x05, 0x01], 1);
591            index.insert(&[0xF0, 0x02], 2);
592            assert_eq!(index.keys(), 2);
593            assert_eq!(
594                index
595                    .first_translated_key()
596                    .unwrap()
597                    .copied()
598                    .collect::<Vec<_>>(),
599                vec![1]
600            );
601            assert_eq!(
602                index
603                    .last_translated_key()
604                    .unwrap()
605                    .copied()
606                    .collect::<Vec<_>>(),
607                vec![2]
608            );
609
610            // Forward across the gap, then wrap from the global last key.
611            let (it, wrapped) = index.next_translated_key(&[0x05, 0x01]).unwrap();
612            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![2], false));
613            let (it, wrapped) = index.next_translated_key(&[0xF0, 0x02]).unwrap();
614            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![1], true));
615
616            // Backward across the gap, then wrap from the global first key.
617            let (it, wrapped) = index.prev_translated_key(&[0xF0, 0x02]).unwrap();
618            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![1], false));
619            let (it, wrapped) = index.prev_translated_key(&[0x05, 0x01]).unwrap();
620            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![2], true));
621
622            // A query landing in an empty partition between the two finds both neighbors.
623            let (it, wrapped) = index.prev_translated_key(&[0x80, 0x00]).unwrap();
624            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![1], false));
625            let (it, wrapped) = index.next_translated_key(&[0x80, 0x00]).unwrap();
626            assert_eq!((it.copied().collect::<Vec<_>>(), wrapped), (vec![2], false));
627
628            // Removing all keys returns to the empty fast path.
629            index.remove(&[0x05, 0x01]);
630            index.remove(&[0xF0, 0x02]);
631            assert_eq!(index.keys(), 0);
632            assert!(index.prev_translated_key(&[0x80, 0x00]).is_none());
633            assert!(index.next_translated_key(&[0x80, 0x00]).is_none());
634        });
635    }
636
637    #[test_traced]
638    fn test_spill_transition() {
639        deterministic::Runner::default().start(|context| async move {
640            let mut index = new_index_spilling(context);
641            // Distinct translated keys in one partition (prefix 0x10).
642            index.insert(&[0x10, 0x01], 1);
643            assert_eq!(index.spilled_count(), 0);
644            index.insert(&[0x10, 0x02], 2); // second entry crosses the threshold -> spills
645            assert_eq!(index.spilled_count(), 1);
646            index.insert(&[0x10, 0x03], 3); // routed straight into the spilled map
647            assert_eq!(index.spilled_count(), 1);
648            assert_eq!(index.keys(), 3);
649            assert_eq!(index.items(), 3);
650
651            // Values are served correctly from the spilled representation in append order.
652            assert_eq!(
653                index.get(&[0x10, 0x01]).copied().collect::<Vec<_>>(),
654                vec![1]
655            );
656            index.insert(&[0x10, 0x02], 22);
657            assert_eq!(
658                index.get(&[0x10, 0x02]).copied().collect::<Vec<_>>(),
659                vec![2, 22]
660            );
661            assert_eq!(index.items(), 4);
662
663            // A different prefix lands in its own (still inline) partition.
664            index.insert(&[0x20, 0x05], 5);
665            assert_eq!(index.spilled_count(), 1);
666            assert_eq!(
667                index.get(&[0x20, 0x05]).copied().collect::<Vec<_>>(),
668                vec![5]
669            );
670        });
671    }
672
673    #[test_traced]
674    fn test_spill_after_cursor_growth() {
675        deterministic::Runner::default().start(|context| async move {
676            let mut index = new_index_spilling(context);
677            let key = [0x10, 0x01];
678
679            index.insert(&key, 1);
680            {
681                let mut cursor = index.get_mut(&key).unwrap();
682                assert_eq!(cursor.next().copied(), Some(1));
683                assert_eq!(cursor.next(), None);
684                cursor.insert(2);
685            }
686            assert_eq!(index.spilled_count(), 0);
687
688            // The next index mutation spills the over-full inline partition.
689            index.insert(&key, 3);
690            assert_eq!(index.spilled_count(), 1);
691            assert_eq!(index.get(&key).copied().collect::<Vec<_>>(), vec![1, 2, 3]);
692
693            // Mutations that own their cursor can spill as soon as they release it.
694            let other = [0x20, 0x01];
695            index.insert(&other, 4);
696            index.insert_and_retain(&other, 5, |_| true);
697            assert_eq!(index.spilled_count(), 2);
698            assert_eq!(index.get(&other).copied().collect::<Vec<_>>(), vec![4, 5]);
699
700            // `get_mut` spills an over-full partition before handing out a cursor, so the cursor
701            // serves the spilled representation.
702            let third = [0x30, 0x01];
703            index.insert(&third, 6);
704            {
705                let mut cursor = index.get_mut(&third).unwrap();
706                assert_eq!(cursor.next().copied(), Some(6));
707                assert_eq!(cursor.next(), None);
708                cursor.insert(7);
709            }
710            assert_eq!(index.spilled_count(), 2);
711            {
712                let mut cursor = index.get_mut(&third).unwrap();
713                assert_eq!(cursor.next().copied(), Some(6));
714                assert_eq!(cursor.next().copied(), Some(7));
715                assert_eq!(cursor.next(), None);
716            }
717            assert_eq!(index.spilled_count(), 3);
718            assert_eq!(index.get(&third).copied().collect::<Vec<_>>(), vec![6, 7]);
719
720            // `remove` spills an over-full partition before access, even for an absent key.
721            let fourth = [0x40, 0x01];
722            index.insert(&fourth, 8);
723            {
724                let mut cursor = index.get_mut(&fourth).unwrap();
725                assert_eq!(cursor.next().copied(), Some(8));
726                assert_eq!(cursor.next(), None);
727                cursor.insert(9);
728            }
729            assert_eq!(index.spilled_count(), 3);
730            index.remove(&[0x40, 0x02]);
731            assert_eq!(index.spilled_count(), 4);
732            assert_eq!(index.get(&fourth).copied().collect::<Vec<_>>(), vec![8, 9]);
733        });
734    }
735
736    #[test_traced]
737    fn test_spill_after_get_mut_or_insert_cursor_growth() {
738        deterministic::Runner::default().start(|context| async move {
739            let mut index = new_index_spilling(context);
740            let key = [0x10, 0x01];
741
742            index.insert(&key, 1);
743            {
744                let mut cursor = index.get_mut_or_insert(&key, 2).unwrap();
745                assert_eq!(cursor.next().copied(), Some(1));
746                assert_eq!(cursor.next(), None);
747                cursor.insert(2);
748            }
749            assert_eq!(index.spilled_count(), 0);
750
751            // The next replay-style update spills before returning another collision cursor.
752            assert!(index.get_mut_or_insert(&key, 3).is_some());
753            assert_eq!(index.spilled_count(), 1);
754            assert_eq!(index.get(&key).copied().collect::<Vec<_>>(), vec![1, 2]);
755        });
756    }
757
758    #[test_traced]
759    fn test_spill_nav() {
760        deterministic::Runner::default().start(|context| async move {
761            let mut index = new_index_spilling(context);
762            // Partition 0x10: keys 0x01, 0x02 -> spills. Partition 0x20: key 0x05 -> inline.
763            // Partition 0x30: keys 0x07, 0x08 -> spills. Nav must cross spilled<->inline boundaries.
764            index.insert(&[0x10, 0x01], 1);
765            index.insert(&[0x10, 0x02], 2);
766            index.insert(&[0x20, 0x05], 5);
767            index.insert(&[0x30, 0x07], 7);
768            index.insert(&[0x30, 0x08], 8);
769            assert_eq!(index.spilled_count(), 2); // 0x10 and 0x30; 0x20 stays inline
770
771            assert_eq!(index.first_translated_key().unwrap().next(), Some(&1));
772            assert_eq!(index.last_translated_key().unwrap().next(), Some(&8));
773
774            // Within a spilled partition.
775            let (mut it, wrapped) = index.next_translated_key(&[0x10, 0x01]).unwrap();
776            assert!(!wrapped);
777            assert_eq!(it.next(), Some(&2));
778            // Spilled -> inline boundary.
779            let (mut it, wrapped) = index.next_translated_key(&[0x10, 0x02]).unwrap();
780            assert!(!wrapped);
781            assert_eq!(it.next(), Some(&5));
782            // Inline -> spilled boundary.
783            let (mut it, wrapped) = index.next_translated_key(&[0x20, 0x05]).unwrap();
784            assert!(!wrapped);
785            assert_eq!(it.next(), Some(&7));
786            // Spilled -> inline boundary, backwards.
787            let (mut it, wrapped) = index.prev_translated_key(&[0x30, 0x07]).unwrap();
788            assert!(!wrapped);
789            assert_eq!(it.next(), Some(&5));
790            // Inline -> spilled boundary, backwards.
791            let (mut it, wrapped) = index.prev_translated_key(&[0x20, 0x05]).unwrap();
792            assert!(!wrapped);
793            assert_eq!(it.next(), Some(&2));
794            // Wrap-around from the global last key.
795            let (mut it, wrapped) = index.next_translated_key(&[0x30, 0x08]).unwrap();
796            assert!(wrapped);
797            assert_eq!(it.next(), Some(&1));
798        });
799    }
800
801    #[test_traced]
802    fn test_spill_despill_on_full_drain() {
803        deterministic::Runner::default().start(|context| async move {
804            let mut index = new_index_spilling(context);
805            index.insert(&[0x10, 0x01], 1);
806            index.insert(&[0x10, 0x02], 2); // spills
807            assert_eq!(index.spilled_count(), 1);
808
809            index.remove(&[0x10, 0x01]);
810            assert_eq!(index.spilled_count(), 1); // 0x02 still present
811            index.remove(&[0x10, 0x02]);
812            assert_eq!(index.spilled_count(), 0); // last key removed -> de-spilled
813            assert_eq!(index.keys(), 0);
814
815            // The partition reverts to an inline sorted array.
816            index.insert(&[0x10, 0x09], 9);
817            assert_eq!(index.spilled_count(), 0);
818            assert_eq!(
819                index.get(&[0x10, 0x09]).copied().collect::<Vec<_>>(),
820                vec![9]
821            );
822        });
823    }
824
825    #[test_traced]
826    fn test_spill_full_lifecycle() {
827        deterministic::Runner::default().start(|context| async move {
828            let mut index = new_index_spilling(context);
829
830            // Empty.
831            assert_eq!(index.spilled_count(), 0);
832            assert_eq!(index.keys(), 0);
833            assert_eq!(index.items(), 0);
834
835            // Empty -> inline: one entry stays below the threshold (2).
836            index.insert(&[0x10, 0x01], 1);
837            assert_eq!(index.spilled_count(), 0);
838
839            // Inline -> spilled: a second distinct key crosses the threshold.
840            index.insert(&[0x10, 0x02], 2);
841            assert_eq!(index.spilled_count(), 1);
842            assert_eq!(index.keys(), 2);
843            assert_eq!(index.items(), 2);
844
845            // Spilled -> empty, draining both keys through a cursor over the spilled representation
846            // (the cursor de-spill path); the partition reverts only once its last key is gone.
847            {
848                let mut cursor = index.get_mut(&[0x10, 0x01]).unwrap();
849                assert_eq!(cursor.next().copied(), Some(1));
850                cursor.delete();
851            }
852            assert_eq!(index.spilled_count(), 1); // 0x02 still present
853            {
854                let mut cursor = index.get_mut(&[0x10, 0x02]).unwrap();
855                assert_eq!(cursor.next().copied(), Some(2));
856                cursor.delete();
857            }
858            assert_eq!(index.spilled_count(), 0); // de-spilled back to empty
859            assert_eq!(index.keys(), 0);
860            assert_eq!(index.items(), 0);
861
862            // Empty -> inline -> spilled a second time: a de-spilled partition is fully reusable.
863            index.insert(&[0x10, 0x03], 3);
864            assert_eq!(index.spilled_count(), 0);
865            index.insert(&[0x10, 0x04], 4);
866            assert_eq!(index.spilled_count(), 1);
867            assert_eq!(
868                index.get(&[0x10, 0x03]).copied().collect::<Vec<_>>(),
869                vec![3]
870            );
871            assert_eq!(
872                index.get(&[0x10, 0x04]).copied().collect::<Vec<_>>(),
873                vec![4]
874            );
875
876            // Spilled -> empty again, this time via `remove` (the other de-spill path).
877            index.remove(&[0x10, 0x03]);
878            assert_eq!(index.spilled_count(), 1); // 0x04 still present
879            index.remove(&[0x10, 0x04]);
880            assert_eq!(index.spilled_count(), 0);
881            assert_eq!(index.keys(), 0);
882            assert_eq!(index.items(), 0);
883
884            // Every removed value was counted once: 2 via cursor delete + 2 via remove.
885            assert_eq!(index.pruned(), 4);
886        });
887    }
888
889    #[test_traced]
890    fn test_spill_get_mut_or_insert() {
891        deterministic::Runner::default().start(|context| async move {
892            let mut index = new_index_spilling(context);
893            index.insert(&[0x10, 0x01], 1);
894            index.insert(&[0x10, 0x02], 2); // second distinct key crosses the threshold -> spills
895            assert_eq!(index.spilled_count(), 1);
896            assert_eq!(index.keys(), 2);
897            assert_eq!(index.items(), 2);
898
899            // Existing key in a spilled partition: returns a cursor over its values; the passed
900            // value is not inserted.
901            {
902                let mut cursor = index.get_mut_or_insert(&[0x10, 0x01], 99).unwrap();
903                assert_eq!(cursor.next().copied(), Some(1));
904                assert!(cursor.next().is_none());
905            }
906            assert_eq!(index.keys(), 2);
907            assert_eq!(index.items(), 2);
908            assert_eq!(
909                index.get(&[0x10, 0x01]).copied().collect::<Vec<_>>(),
910                vec![1]
911            );
912
913            // Absent key in a spilled partition: inserts it as a new key and returns None (the
914            // partition stays spilled).
915            assert!(index.get_mut_or_insert(&[0x10, 0x03], 3).is_none());
916            assert_eq!(index.spilled_count(), 1);
917            assert_eq!(index.keys(), 3);
918            assert_eq!(index.items(), 3);
919            assert_eq!(
920                index.get(&[0x10, 0x03]).copied().collect::<Vec<_>>(),
921                vec![3]
922            );
923        });
924    }
925
926    #[test_traced]
927    #[should_panic(expected = "must call Cursor::next()")]
928    fn test_spill_cursor_delete_before_next_panics() {
929        deterministic::Runner::default().start(|context| async move {
930            let mut index = new_index_spilling(context);
931            index.insert(&[0x10, 0x01], 1);
932            index.insert(&[0x10, 0x02], 2); // spills
933            let mut cursor = index.get_mut(&[0x10, 0x01]).unwrap(); // cursor over the spilled partition
934            cursor.delete();
935        });
936    }
937
938    #[test_traced]
939    fn test_soa_basic() {
940        deterministic::Runner::default().start(|context| async move {
941            let mut index = new_index(context);
942            assert_eq!(index.keys(), 0);
943
944            let key = b"duplicate".as_slice();
945            index.insert(key, 1);
946            index.insert(key, 2);
947            index.insert(key, 3);
948            assert_eq!(index.keys(), 1);
949            assert_eq!(index.items(), 3);
950            assert_eq!(index.get(key).copied().collect::<Vec<_>>(), vec![1, 2, 3]);
951
952            {
953                let mut cursor = index.get_mut(key).unwrap();
954                assert_eq!(*cursor.next().unwrap(), 1);
955                assert_eq!(*cursor.next().unwrap(), 2);
956                assert_eq!(*cursor.next().unwrap(), 3);
957                assert!(cursor.next().is_none());
958            }
959
960            index.insert(key, 3);
961            index.insert(key, 4);
962            index.retain(key, |i| *i != 3);
963            assert_eq!(index.get(key).copied().collect::<Vec<_>>(), vec![1, 2, 4]);
964
965            index.retain(key, |_| false);
966            assert_eq!(
967                index.get(key).copied().collect::<Vec<_>>(),
968                Vec::<u64>::new()
969            );
970            assert_eq!(index.keys(), 0);
971            assert!(index.get_mut(key).is_none());
972
973            // No-op on a missing key.
974            index.retain(key, |_| false);
975        });
976    }
977
978    #[test_traced]
979    fn test_soa_cursor_find() {
980        deterministic::Runner::default().start(|context| async move {
981            let mut index = new_index(context);
982            let key = b"test_key";
983            for v in [10u64, 20, 30, 40] {
984                index.insert(key, v);
985            }
986
987            {
988                let mut cursor = index.get_mut(key).unwrap();
989                assert!(cursor.find(|&v| v == 30));
990                cursor.update(35);
991            }
992            let values: Vec<u64> = index.get(key).copied().collect();
993            assert!(values.contains(&35) && !values.contains(&30));
994
995            {
996                let mut cursor = index.get_mut(key).unwrap();
997                assert!(!cursor.find(|&v| v == 100));
998                assert!(cursor.next().is_none());
999            }
1000
1001            {
1002                let mut cursor = index.get_mut(key).unwrap();
1003                assert!(cursor.find(|&v| v == 20));
1004                cursor.delete();
1005            }
1006            let values: Vec<u64> = index.get(key).copied().collect();
1007            assert!(!values.contains(&20));
1008            assert_eq!(values.len(), 3);
1009        });
1010    }
1011
1012    #[test_traced]
1013    fn test_soa_get_many_and_partitions() {
1014        deterministic::Runner::default().start(|context| async move {
1015            let mut index = new_index(context);
1016            // "ab"/"abX" share a partition+translated key; "zz" is a different partition.
1017            index.insert(b"ab", 1);
1018            index.insert(b"ab", 2);
1019            index.insert(b"abX", 3);
1020            index.insert(b"zz", 4);
1021
1022            let keys: Vec<&[u8]> = vec![b"zz", b"missing", b"ab", b"zz"];
1023            let mut visits: Vec<Vec<u64>> = vec![Vec::new(); keys.len()];
1024            index.get_many(&keys, |key_idx, value| visits[key_idx].push(*value));
1025            assert_eq!(visits[0], vec![4]);
1026            assert!(visits[1].is_empty());
1027            assert_eq!(visits[2], vec![1, 2, 3]);
1028            assert_eq!(visits[3], vec![4]);
1029        });
1030    }
1031
1032    #[test_traced]
1033    fn test_soa_insert_and_retain() {
1034        deterministic::Runner::default().start(|context| async move {
1035            let mut index = new_index(context);
1036            // Keep both: new value appends to the run.
1037            index.insert(b"k", 1u64);
1038            index.insert_and_retain(b"k", 2, |_| true);
1039            assert_eq!(index.get(b"k").copied().collect::<Vec<_>>(), vec![1, 2]);
1040
1041            // Drop the new value: no-op.
1042            index.insert_and_retain(b"k", 9, |v| *v != 9);
1043            assert_eq!(index.get(b"k").copied().collect::<Vec<_>>(), vec![1, 2]);
1044
1045            // Drop everything.
1046            index.insert_and_retain(b"k", 9, |_| false);
1047            assert!(index.get_mut(b"k").is_none());
1048            assert_eq!(index.keys(), 0);
1049
1050            // Vacant key: insert only if retained.
1051            index.insert_and_retain(b"new", 7, |_| true);
1052            assert_eq!(index.get(b"new").copied().collect::<Vec<_>>(), vec![7]);
1053            assert_eq!(index.keys(), 1);
1054        });
1055    }
1056
1057    #[test_traced]
1058    fn test_soa_remove() {
1059        deterministic::Runner::default().start(|context| async move {
1060            let mut index = new_index(context);
1061            index.insert(b"k", 1u64);
1062            index.insert(b"k", 2);
1063            index.insert(b"other", 3);
1064            assert_eq!(index.items(), 3);
1065            assert_eq!(index.keys(), 2);
1066
1067            index.remove(b"k");
1068            assert!(index.get_mut(b"k").is_none());
1069            assert_eq!(index.keys(), 1);
1070            assert_eq!(index.items(), 1);
1071            assert_eq!(index.pruned(), 2);
1072            assert_eq!(index.get(b"other").copied().collect::<Vec<_>>(), vec![3]);
1073
1074            index.remove(b"missing"); // no-op
1075            assert_eq!(index.keys(), 1);
1076        });
1077    }
1078
1079    #[test_traced]
1080    fn test_soa_ordered() {
1081        deterministic::Runner::default().start(|context| async move {
1082            let mut index = new_index(context);
1083            assert!(index.first_translated_key().is_none());
1084            assert!(index.last_translated_key().is_none());
1085            assert!(index.next_translated_key(b"key").is_none());
1086            assert!(index.prev_translated_key(b"key").is_none());
1087
1088            // With OneCap + P=1, the full key orders as (prefix byte, first sub-key byte).
1089            let k1 = &hex!("0x0b02AA"); // -> partition 0b, sub-key 02
1090            let k2 = &hex!("0x1c04CC"); // -> partition 1c, sub-key 04
1091            let k2_collides = &hex!("0x1c0411"); // same (1c, 04) as k2
1092            let k3 = &hex!("0x2d06EE"); // -> partition 2d, sub-key 06
1093            index.insert(k1, 1);
1094            index.insert(k2, 21);
1095            index.insert(k2_collides, 22);
1096            index.insert(k3, 3);
1097            assert_eq!(index.keys(), 3);
1098
1099            assert_eq!(index.first_translated_key().unwrap().next(), Some(&1));
1100            assert_eq!(index.last_translated_key().unwrap().next(), Some(&3));
1101
1102            // From before the first key: the first key, not wrapped.
1103            let (mut it, wrapped) = index.next_translated_key(&[0x00]).unwrap();
1104            assert!(!wrapped);
1105            assert_eq!(it.next(), Some(&1));
1106            assert_eq!(it.next(), None);
1107
1108            // From k1's bucket: jumps partitions to k2's collision run.
1109            let (mut it, wrapped) = index.next_translated_key(&hex!("0x0b02F2")).unwrap();
1110            assert!(!wrapped);
1111            assert_eq!(it.next(), Some(&21));
1112            assert_eq!(it.next(), Some(&22));
1113            assert_eq!(it.next(), None);
1114
1115            // From the last key: cycles to the first.
1116            let (mut it, wrapped) = index.next_translated_key(k3).unwrap();
1117            assert!(wrapped);
1118            assert_eq!(it.next(), Some(&1));
1119
1120            // From the first key going backwards: cycles to the last.
1121            let (mut it, wrapped) = index.prev_translated_key(k1).unwrap();
1122            assert!(wrapped);
1123            assert_eq!(it.next(), Some(&3));
1124
1125            // Previous bucket below 1d is 1c's collision run.
1126            let (mut it, wrapped) = index.prev_translated_key(&hex!("0x1d0102")).unwrap();
1127            assert!(!wrapped);
1128            assert_eq!(it.next(), Some(&21));
1129            assert_eq!(it.next(), Some(&22));
1130            assert_eq!(it.next(), None);
1131        });
1132    }
1133
1134    #[test_traced]
1135    fn test_soa_ordered_exhaustive_traversal() {
1136        deterministic::Runner::default().start(|context| async move {
1137            let mut index = new_index(context);
1138
1139            // A grid of (prefix, sub-key) keys spanning several partitions, including the edge
1140            // bytes 0x00/0xFF, each a distinct translated key (OneCap + P=1 orders by
1141            // (prefix, first sub-key byte)). `keys` is built in ascending order.
1142            let prefixes = [0x00u8, 0x05, 0xAA, 0xFF];
1143            let subkeys = [0x00u8, 0x80, 0xFF];
1144            let mut keys: Vec<[u8; 2]> = Vec::new();
1145            for &p in &prefixes {
1146                for &s in &subkeys {
1147                    keys.push([p, s]);
1148                }
1149            }
1150            let value_of = |k: &[u8; 2]| ((k[0] as u64) << 8) | k[1] as u64;
1151            let n = keys.len();
1152
1153            // Insert scrambled to exercise sorted-array maintenance regardless of insertion order.
1154            let mut scrambled = keys.clone();
1155            scrambled.reverse();
1156            scrambled.rotate_left(5);
1157            for k in &scrambled {
1158                index.insert(k, value_of(k));
1159            }
1160            assert_eq!(index.keys(), n);
1161
1162            assert_eq!(
1163                index.first_translated_key().unwrap().next(),
1164                Some(&value_of(&keys[0]))
1165            );
1166            assert_eq!(
1167                index.last_translated_key().unwrap().next(),
1168                Some(&value_of(&keys[n - 1]))
1169            );
1170
1171            // For every key, `next` is its successor and `prev` its predecessor, wrapping at the
1172            // ends. This walks run_starting_at / run_ending_at across every partition boundary.
1173            for i in 0..n {
1174                let next = value_of(&keys[(i + 1) % n]);
1175                let (mut it, wrapped) = index.next_translated_key(&keys[i]).unwrap();
1176                assert_eq!(wrapped, i + 1 == n, "next wrap at index {i}");
1177                assert_eq!(it.next(), Some(&next), "next at {i}");
1178                assert_eq!(it.next(), None);
1179
1180                let prev = value_of(&keys[(i + n - 1) % n]);
1181                let (mut it, wrapped) = index.prev_translated_key(&keys[i]).unwrap();
1182                assert_eq!(wrapped, i == 0, "prev wrap at index {i}");
1183                assert_eq!(it.next(), Some(&prev), "prev at {i}");
1184                assert_eq!(it.next(), None);
1185            }
1186        });
1187    }
1188}