Skip to main content

commonware_storage/index/
unordered.rs

1//! A memory-efficient index that uses an unordered map internally to map translated keys to
2//! arbitrary values. If you require ordering over the map's keys, consider
3//! [crate::index::ordered::Index] instead.
4
5use crate::{
6    index::{
7        Cursor as CursorTrait, Unordered,
8        storage::{Cursor as CursorImpl, IndexEntry, Overflow, Values, push_displaced},
9    },
10    translator::Translator,
11};
12use commonware_runtime::{
13    Metrics,
14    telemetry::metrics::{Counter, Gauge, MetricsExt as _},
15};
16use std::collections::{
17    HashMap,
18    hash_map::{Entry, OccupiedEntry, VacantEntry},
19};
20
21/// Implementation of [IndexEntry] for [OccupiedEntry].
22impl<K: Send + Sync, V: Send + Sync> IndexEntry<V> for OccupiedEntry<'_, K, V> {
23    type Key = K;
24
25    fn key(&self) -> &K {
26        OccupiedEntry::key(self)
27    }
28
29    fn get_mut(&mut self) -> &mut V {
30        self.get_mut()
31    }
32
33    fn remove(self) {
34        OccupiedEntry::remove(self);
35    }
36}
37
38/// A [crate::index::Cursor] over the values associated with a translated key.
39pub type Cursor<'a, K, V, S> = CursorImpl<'a, K, V, OccupiedEntry<'a, K, V>, S>;
40
41/// A memory-efficient index that uses an unordered map internally to map translated keys to
42/// arbitrary values.
43///
44/// Each translated key maps directly to its most recently inserted value. Conflicting values (from
45/// key collisions or repeated insertions) live in a separate overflow map, keeping the common
46/// (collision-free) case compact.
47pub struct Index<T: Translator, V: Send + Sync> {
48    translator: T,
49    map: HashMap<T::Key, V, T>,
50    overflow: Overflow<T::Key, V, T>,
51
52    keys: Gauge,
53    items: Gauge,
54    pruned: Counter,
55}
56
57impl<T: Translator, V: Send + Sync> Index<T, V> {
58    /// Create a new entry in the index.
59    fn create(keys: &Gauge, items: &Gauge, vacant: VacantEntry<'_, T::Key, V>, v: V) {
60        keys.inc();
61        items.inc();
62        vacant.insert(v);
63    }
64
65    /// Create a new index with the given translator and metrics registry. The maps start without
66    /// capacity and grow as needed, so unused indices (e.g. empty partitions) cost no memory.
67    pub fn new(ctx: impl Metrics, translator: T) -> Self {
68        Self {
69            translator: translator.clone(),
70            overflow: HashMap::with_hasher(translator.clone()),
71            map: HashMap::with_hasher(translator),
72            keys: ctx.gauge("keys", "Number of translated keys in the index"),
73            items: ctx.gauge("items", "Number of items in the index"),
74            pruned: ctx.counter("pruned", "Number of items pruned"),
75        }
76    }
77
78    /// Create an empty index with this index's translator and metric handles. Parallel
79    /// snapshot-build workers use it for their partition slots.
80    #[commonware_macros::stability(ALPHA)]
81    pub(crate) fn empty(&self) -> Self {
82        Self {
83            translator: self.translator.clone(),
84            overflow: HashMap::with_hasher(self.translator.clone()),
85            map: HashMap::with_hasher(self.translator.clone()),
86            keys: self.keys.clone(),
87            items: self.items.clone(),
88            pruned: self.pruned.clone(),
89        }
90    }
91
92    /// Move `other`'s contents into self, which must be empty. Wholesale moves are what let
93    /// [`Self::empty`] build-worker slots install without re-inserting each entry.
94    /// Metrics need no adjustment, since `other` updated self's handles directly.
95    ///
96    /// # Panics
97    ///
98    /// Panics if self is not empty.
99    #[commonware_macros::stability(ALPHA)]
100    pub(crate) fn absorb(&mut self, other: Self) {
101        assert!(
102            self.map.is_empty() && self.overflow.is_empty(),
103            "absorb target must be empty"
104        );
105        self.map = other.map;
106        self.overflow = other.overflow;
107    }
108
109    /// Visit every value held by the index (inline and overflow), in unspecified order.
110    #[commonware_macros::stability(ALPHA)]
111    pub(crate) fn for_each_value(&self, mut f: impl FnMut(&V)) {
112        for v in self.map.values() {
113            f(v);
114        }
115        for chain in self.overflow.values() {
116            for v in chain {
117                f(v);
118            }
119        }
120    }
121}
122
123impl<T: Translator, V: Send + Sync> super::Factory for Index<T, V> {
124    type Translator = T;
125
126    fn new(ctx: impl commonware_runtime::Metrics, translator: T) -> Self {
127        Self::new(ctx, translator)
128    }
129}
130
131impl<T: Translator, V: Send + Sync> Unordered for Index<T, V> {
132    type Value = V;
133    type Cursor<'a>
134        = Cursor<'a, T::Key, V, T>
135    where
136        Self: 'a;
137
138    fn get<'a>(&'a self, key: &[u8]) -> impl Iterator<Item = &'a V> + 'a
139    where
140        V: 'a,
141    {
142        let k = self.translator.transform(key);
143        Values::new(self.map.get(&k), &self.overflow, k)
144    }
145
146    fn get_mut<'a>(&'a mut self, key: &[u8]) -> Option<Self::Cursor<'a>> {
147        let k = self.translator.transform(key);
148        match self.map.entry(k) {
149            Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
150                entry,
151                &mut self.overflow,
152                &self.keys,
153                &self.items,
154                &self.pruned,
155            )),
156            Entry::Vacant(_) => None,
157        }
158    }
159
160    fn get_mut_or_insert<'a>(&'a mut self, key: &[u8], value: V) -> Option<Self::Cursor<'a>> {
161        let k = self.translator.transform(key);
162        match self.map.entry(k) {
163            Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
164                entry,
165                &mut self.overflow,
166                &self.keys,
167                &self.items,
168                &self.pruned,
169            )),
170            Entry::Vacant(entry) => {
171                Self::create(&self.keys, &self.items, entry, value);
172                None
173            }
174        }
175    }
176
177    fn insert(&mut self, key: &[u8], v: V) {
178        let k = self.translator.transform(key);
179        match self.map.entry(k) {
180            Entry::Occupied(mut entry) => {
181                // The newest value is stored inline; the displaced value joins the end of the
182                // overflow chain.
183                let old = std::mem::replace(entry.get_mut(), v);
184                push_displaced(&mut self.overflow, k, old);
185                self.items.inc();
186            }
187            Entry::Vacant(entry) => {
188                Self::create(&self.keys, &self.items, entry, v);
189            }
190        }
191    }
192
193    fn insert_and_retain(&mut self, key: &[u8], value: V, should_retain: impl Fn(&V) -> bool) {
194        let k = self.translator.transform(key);
195        match self.map.entry(k) {
196            Entry::Occupied(mut entry) => {
197                // Optimized fast path for the common case of no overflow chain.
198                #[allow(clippy::map_entry)]
199                if !self.overflow.contains_key(&k) {
200                    match (should_retain(entry.get()), should_retain(&value)) {
201                        // Keep both, with the new value placed at the end of the overflow chain.
202                        (true, true) => {
203                            self.overflow.insert(k, vec![value]);
204                            self.items.inc();
205                        }
206                        // Drop the existing value, keep the new one: replace in place.
207                        (false, true) => {
208                            *entry.get_mut() = value;
209                            self.pruned.inc();
210                        }
211                        // Drop both: remove the key entirely.
212                        (false, false) => {
213                            entry.remove();
214                            self.keys.dec();
215                            self.items.dec();
216                            self.pruned.inc();
217                        }
218                        // Keep the existing value, drop the new one: nothing to do.
219                        (true, false) => {}
220                    }
221                    return;
222                }
223
224                // Slow path: the key has conflicting values; walk them with a cursor.
225                let mut cursor = Cursor::<'_, T::Key, V, T>::new(
226                    entry,
227                    &mut self.overflow,
228                    &self.keys,
229                    &self.items,
230                    &self.pruned,
231                );
232
233                // Drop anything that should not be retained.
234                cursor.retain(&should_retain);
235
236                // Add the new value only if it should be retained.
237                if should_retain(&value) {
238                    cursor.insert(value);
239                }
240            }
241            Entry::Vacant(entry) => {
242                // Create the entry only if the value should be retained.
243                if should_retain(&value) {
244                    Self::create(&self.keys, &self.items, entry, value);
245                }
246            }
247        }
248    }
249
250    fn remove(&mut self, key: &[u8]) {
251        let k = self.translator.transform(key);
252        if self.map.remove(&k).is_some() {
253            // To ensure metrics are accurate, account for all conflicting values in the chain.
254            self.keys.dec();
255            self.items.dec();
256            self.pruned.inc();
257            if !self.overflow.is_empty()
258                && let Some(chain) = self.overflow.remove(&k)
259            {
260                self.items.dec_by(chain.len() as i64);
261                self.pruned.inc_by(chain.len() as u64);
262            }
263        }
264    }
265
266    #[cfg(test)]
267    fn keys(&self) -> usize {
268        self.keys.get() as usize
269    }
270
271    #[cfg(test)]
272    fn items(&self) -> usize {
273        self.items.get() as usize
274    }
275
276    #[cfg(test)]
277    fn pruned(&self) -> usize {
278        self.pruned.get() as usize
279    }
280}