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        storage::{push_displaced, Cursor as CursorImpl, IndexEntry, Overflow, Values},
8        Cursor as CursorTrait, Unordered,
9    },
10    translator::Translator,
11};
12use commonware_runtime::{
13    telemetry::metrics::{Counter, Gauge, MetricsExt as _},
14    Metrics,
15};
16use std::collections::{
17    hash_map::{Entry, OccupiedEntry, VacantEntry},
18    HashMap,
19};
20
21/// The initial capacity of the internal hashmap. This is a guess at the number of unique keys we
22/// will encounter. The hashmap will grow as needed, but this is a good starting point (covering the
23/// entire [crate::translator::OneCap] range).
24const INITIAL_CAPACITY: usize = 256;
25
26/// Implementation of [IndexEntry] for [OccupiedEntry].
27impl<K: Send + Sync, V: Send + Sync> IndexEntry<V> for OccupiedEntry<'_, K, V> {
28    type Key = K;
29
30    fn key(&self) -> &K {
31        OccupiedEntry::key(self)
32    }
33
34    fn get_mut(&mut self) -> &mut V {
35        self.get_mut()
36    }
37
38    fn remove(self) {
39        OccupiedEntry::remove(self);
40    }
41}
42
43/// A [crate::index::Cursor] over the values associated with a translated key.
44pub type Cursor<'a, K, V, S> = CursorImpl<'a, K, V, OccupiedEntry<'a, K, V>, S>;
45
46/// A memory-efficient index that uses an unordered map internally to map translated keys to
47/// arbitrary values.
48///
49/// Each translated key maps directly to its most recently inserted value. Conflicting values (from
50/// key collisions or repeated insertions) live in a separate overflow map, keeping the common
51/// (collision-free) case compact.
52pub struct Index<T: Translator, V: Send + Sync> {
53    translator: T,
54    map: HashMap<T::Key, V, T>,
55    overflow: Overflow<T::Key, V, T>,
56
57    keys: Gauge,
58    items: Gauge,
59    pruned: Counter,
60}
61
62impl<T: Translator, V: Send + Sync> Index<T, V> {
63    /// Create a new entry in the index.
64    fn create(keys: &Gauge, items: &Gauge, vacant: VacantEntry<'_, T::Key, V>, v: V) {
65        keys.inc();
66        items.inc();
67        vacant.insert(v);
68    }
69
70    /// Create a new index with the given translator and metrics registry.
71    pub fn new(ctx: impl Metrics, translator: T) -> Self {
72        Self {
73            translator: translator.clone(),
74            overflow: HashMap::with_hasher(translator.clone()),
75            map: HashMap::with_capacity_and_hasher(INITIAL_CAPACITY, translator),
76            keys: ctx.gauge("keys", "Number of translated keys in the index"),
77            items: ctx.gauge("items", "Number of items in the index"),
78            pruned: ctx.counter("pruned", "Number of items pruned"),
79        }
80    }
81}
82
83impl<T: Translator, V: Send + Sync> super::Factory<T> for Index<T, V> {
84    fn new(ctx: impl commonware_runtime::Metrics, translator: T) -> Self {
85        Self::new(ctx, translator)
86    }
87}
88
89impl<T: Translator, V: Send + Sync> Unordered for Index<T, V> {
90    type Value = V;
91    type Cursor<'a>
92        = Cursor<'a, T::Key, V, T>
93    where
94        Self: 'a;
95
96    fn get<'a>(&'a self, key: &[u8]) -> impl Iterator<Item = &'a V> + 'a
97    where
98        V: 'a,
99    {
100        let k = self.translator.transform(key);
101        Values::new(self.map.get(&k), &self.overflow, k)
102    }
103
104    fn get_mut<'a>(&'a mut self, key: &[u8]) -> Option<Self::Cursor<'a>> {
105        let k = self.translator.transform(key);
106        match self.map.entry(k) {
107            Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
108                entry,
109                &mut self.overflow,
110                &self.keys,
111                &self.items,
112                &self.pruned,
113            )),
114            Entry::Vacant(_) => None,
115        }
116    }
117
118    fn get_mut_or_insert<'a>(&'a mut self, key: &[u8], value: V) -> Option<Self::Cursor<'a>> {
119        let k = self.translator.transform(key);
120        match self.map.entry(k) {
121            Entry::Occupied(entry) => Some(Cursor::<'_, T::Key, V, T>::new(
122                entry,
123                &mut self.overflow,
124                &self.keys,
125                &self.items,
126                &self.pruned,
127            )),
128            Entry::Vacant(entry) => {
129                Self::create(&self.keys, &self.items, entry, value);
130                None
131            }
132        }
133    }
134
135    fn insert(&mut self, key: &[u8], v: V) {
136        let k = self.translator.transform(key);
137        match self.map.entry(k) {
138            Entry::Occupied(mut entry) => {
139                // The newest value is stored inline; the displaced value joins the end of the
140                // overflow chain.
141                let old = std::mem::replace(entry.get_mut(), v);
142                push_displaced(&mut self.overflow, k, old);
143                self.items.inc();
144            }
145            Entry::Vacant(entry) => {
146                Self::create(&self.keys, &self.items, entry, v);
147            }
148        }
149    }
150
151    fn insert_and_retain(&mut self, key: &[u8], value: V, should_retain: impl Fn(&V) -> bool) {
152        let k = self.translator.transform(key);
153        match self.map.entry(k) {
154            Entry::Occupied(mut entry) => {
155                // Optimized fast path for the common case of no overflow chain.
156                #[allow(clippy::map_entry)]
157                if !self.overflow.contains_key(&k) {
158                    match (should_retain(entry.get()), should_retain(&value)) {
159                        // Keep both, with the new value placed at the end of the overflow chain.
160                        (true, true) => {
161                            self.overflow.insert(k, vec![value]);
162                            self.items.inc();
163                        }
164                        // Drop the existing value, keep the new one: replace in place.
165                        (false, true) => {
166                            *entry.get_mut() = value;
167                            self.pruned.inc();
168                        }
169                        // Drop both: remove the key entirely.
170                        (false, false) => {
171                            entry.remove();
172                            self.keys.dec();
173                            self.items.dec();
174                            self.pruned.inc();
175                        }
176                        // Keep the existing value, drop the new one: nothing to do.
177                        (true, false) => {}
178                    }
179                    return;
180                }
181
182                // Slow path: the key has conflicting values; walk them with a cursor.
183                let mut cursor = Cursor::<'_, T::Key, V, T>::new(
184                    entry,
185                    &mut self.overflow,
186                    &self.keys,
187                    &self.items,
188                    &self.pruned,
189                );
190
191                // Drop anything that should not be retained.
192                cursor.retain(&should_retain);
193
194                // Add the new value only if it should be retained.
195                if should_retain(&value) {
196                    cursor.insert(value);
197                }
198            }
199            Entry::Vacant(entry) => {
200                // Create the entry only if the value should be retained.
201                if should_retain(&value) {
202                    Self::create(&self.keys, &self.items, entry, value);
203                }
204            }
205        }
206    }
207
208    fn remove(&mut self, key: &[u8]) {
209        let k = self.translator.transform(key);
210        if self.map.remove(&k).is_some() {
211            // To ensure metrics are accurate, account for all conflicting values in the chain.
212            self.keys.dec();
213            self.items.dec();
214            self.pruned.inc();
215            if !self.overflow.is_empty() {
216                if let Some(chain) = self.overflow.remove(&k) {
217                    self.items.dec_by(chain.len() as i64);
218                    self.pruned.inc_by(chain.len() as u64);
219                }
220            }
221        }
222    }
223
224    #[cfg(test)]
225    fn keys(&self) -> usize {
226        self.map.len()
227    }
228
229    #[cfg(test)]
230    fn items(&self) -> usize {
231        self.items.get() as usize
232    }
233
234    #[cfg(test)]
235    fn pruned(&self) -> usize {
236        self.pruned.get() as usize
237    }
238}