Skip to main content

yo_index/
map.rs

1//! The raw map: an index and an arena wired together.
2//!
3//! This is the smallest thing that is actually a key value store, and it is the
4//! thing M0's exit gate measures against aki's `f1raw` numbers. There is no
5//! record header yet beyond two lengths, no TTL, no type byte, no version. All
6//! of that arrives in M1 and replaces [`Record`] without the index noticing,
7//! which is the point of keeping the two crates apart.
8//!
9//! Layout of one record in the arena:
10//!
11//! ```text
12//! +--------+--------+-----------+-------------+
13//! | klen   | vlen   | key bytes | value bytes |
14//! | u32 LE | u32 LE | klen      | vlen        |
15//! +--------+--------+-----------+-------------+
16//! ```
17//!
18//! Key and value live in one allocation so that a hit is one cache miss for the
19//! bucket and one for the record, not three.
20
21use crate::index::{Index, Keys};
22use crate::scan::Cursor;
23use crate::tagged::Tagged;
24use yo_arena::Arena;
25use yo_common::{Addr, Space, bytes_eq, wyhash};
26
27/// Bytes of length prefix in front of a record.
28const HDR: usize = 8;
29
30/// The least a single [`RawMap::compact_step`] walks.
31///
32/// A segment is two megabytes and evacuating one in a single call was a stop
33/// the world pause in the middle of a batch. At 64 byte values that is around
34/// twenty six thousand records, each one an index probe, a copy and an index
35/// write, and the replies behind it wait for all of them. It is why the write
36/// rows had a p99 of 3.9 milliseconds against Redis at 0.8 while the p50 was
37/// in line: the median command paid nothing and one command in a few thousand
38/// paid for the whole segment.
39///
40/// Sixty four kilobytes is a thirty second of a segment, which puts the worst
41/// call at a few hundred records. Smaller would be smoother and would spend
42/// more of the total on the fixed cost of picking up where the last call left
43/// off; this is the smallest size at which that overhead is still noise.
44///
45/// The budget is spent on how far the cursor moves and not on how many records
46/// move, because a segment can be entirely dead. Charging only for records
47/// that move would let one call walk two megabytes of headers for free, which
48/// is the pause this exists to prevent, just without the copying.
49const EVAC_FLOOR: usize = 64 * 1024;
50
51/// The most, which is a whole segment.
52///
53/// The cap is here so that the scaling below has an end, not because a segment
54/// is a good amount of work to do at once. Reaching it means the collector is
55/// sixteen times past the line it starts at, at which point the pause is the
56/// smaller problem.
57const EVAC_CEILING: usize = yo_arena::SEGMENT_SIZE;
58
59/// How much a caller is willing to pay for the memory a sweep gives back.
60///
61/// Not how hard to work but which trades to accept, which is the part that
62/// turned out to matter: the difference between the three is entirely in which
63/// segment gets picked, and picking badly costs a hundred times more than the
64/// work itself.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66enum Sweep {
67    /// Only when the store as a whole is dirty enough to be worth a sweep.
68    Ordinary,
69    /// However clean the store is overall, as long as this segment is worth
70    /// emptying on its own.
71    Hard,
72}
73
74/// A segment that is partway through being evacuated, and how far it got.
75#[derive(Clone, Copy)]
76struct Evac {
77    seg: usize,
78    off: usize,
79}
80
81/// What compaction has done to a map over its life.
82///
83/// The write amplification of value separation, in the two parts it is actually
84/// made of. Every record the walk steps over costs a liveness probe whether it
85/// is live or not, and every live one it finds costs a copy on top of that, so a
86/// segment full of dead records and a segment full of live ones are different
87/// amounts of work for the same number of bytes. One counter cannot tell those
88/// apart, which is why there are three.
89///
90/// Counted here rather than in the caller because this is the only place that
91/// knows a record moved, and the numbers are wanted per store rather than per
92/// command. They never reset, including across [`RawMap::clear`].
93#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
94pub struct Compaction {
95    /// Records the walk has stepped over, live and dead together.
96    pub walked: u64,
97    /// The ones that were still live and had to be copied somewhere else.
98    pub moved: u64,
99    /// What those copies came to, headers and keys included.
100    pub bytes: u64,
101}
102
103struct Record;
104
105impl Record {
106    #[inline]
107    fn lens(bytes: &[u8]) -> (usize, usize) {
108        let k = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
109        let v = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as usize;
110        (k, v)
111    }
112}
113
114/// Arena backed record access, which is what the index probes through.
115struct Records<'a> {
116    arena: &'a Arena,
117}
118
119impl Keys for Records<'_> {
120    #[inline]
121    fn hash_at(&self, addr: Addr) -> u64 {
122        let (klen, _) = Record::lens(self.arena.get(addr, HDR));
123        let bytes = self.arena.get(addr, HDR + klen);
124        wyhash(&bytes[HDR..], 0)
125    }
126
127    #[inline]
128    fn eq_at(&self, addr: Addr, key: &[u8]) -> bool {
129        let bytes = self.arena.get(addr, HDR);
130        let (klen, _) = Record::lens(bytes);
131        if klen != key.len() {
132            return false;
133        }
134        let bytes = self.arena.get(addr, HDR + klen);
135        bytes_eq(&bytes[HDR..], key)
136    }
137}
138
139/// A single shard's key value map: bytes in, bytes out, nothing else.
140///
141/// Not `Sync`, and deliberately so. One of these is inside one stripe, and the
142/// lock around the stripe is what decides which thread has it, which is `05`
143/// section 1's whole argument: one owner at a time means no atomics on the hot
144/// path.
145///
146/// ```
147/// let mut m = yo_index::RawMap::new();
148/// assert_eq!(m.set(b"k", b"v"), None);
149/// assert_eq!(m.get(b"k"), Some(&b"v"[..]));
150/// assert_eq!(m.set(b"k", b"w").is_some(), true);
151/// assert_eq!(m.get(b"k"), Some(&b"w"[..]));
152/// assert_eq!(m.del(b"k"), true);
153/// assert_eq!(m.get(b"k"), None);
154/// ```
155pub struct RawMap {
156    index: Index,
157    arena: Arena,
158    /// Where the last `compact_step` stopped, if it stopped partway.
159    evac: Option<Evac>,
160    /// How many times anything in here has been written to.
161    ///
162    /// A caller that resolved a key once and wants to skip resolving it again
163    /// needs to know whether anything could have moved in between, and the
164    /// honest answer is any write at all. Every method that takes `&mut self`
165    /// bumps this, including the in place ones, so the question a caller asks is
166    /// "has this map been written since" and not "has this map been written in a
167    /// way I thought would matter".
168    ///
169    /// It lives here rather than in the caller because there are eleven places
170    /// in `yo-kv` that write to a map and one place here that could be missed,
171    /// and a missed invalidation is a stale answer rather than a slow one.
172    ///
173    /// [`RawMap::value_at_mut`] is the one exception and it is argued for where
174    /// it is written. Everything else, including the in place ones, bumps this.
175    writes: u64,
176    /// The records the caller marked when it wrote them.
177    ///
178    /// A second index of a subset of the keys, which exists so that a caller
179    /// looking for one of them does not have to walk past the ones it is not
180    /// looking for. The only thing that uses it is expiry: a key with a deadline
181    /// is rare in most databases, and both the active expire cycle and the
182    /// `volatile-*` eviction policies were sampling the whole map to find one.
183    ///
184    /// It is here and not in `yo-kv` because this is the only thing that knows
185    /// where a record is. An overwrite can move one, a delete takes one away,
186    /// and compaction moves them between segments, and all three are in this
187    /// file. A set of addresses kept anywhere else would go stale on the third.
188    ///
189    /// What "marked" means is entirely the caller's business. This holds
190    /// addresses and has never heard of a deadline.
191    tagged: Tagged,
192    /// What compaction has cost so far.
193    compaction: Compaction,
194}
195
196impl RawMap {
197    /// An empty map.
198    pub fn new() -> RawMap {
199        RawMap {
200            index: Index::new(),
201            arena: Arena::new(),
202            evac: None,
203            writes: 0,
204            tagged: Tagged::new(),
205            compaction: Compaction::default(),
206        }
207    }
208
209    /// What compaction has done to this map since it was made.
210    ///
211    /// A running total and not a rate, so two reads either side of a load say
212    /// what that load cost. See [`Compaction`] for what the three numbers are
213    /// and why they are not one.
214    #[inline]
215    #[must_use]
216    pub const fn compaction(&self) -> Compaction {
217        self.compaction
218    }
219
220    /// How many times this map has been written to.
221    ///
222    /// Two reads of this with the same value either side of some work mean
223    /// nothing in the map moved, so an address or a slot resolved before the
224    /// first read is still the right one after the second. It never goes
225    /// backwards, including across [`RawMap::clear`].
226    #[inline]
227    #[must_use]
228    pub const fn writes(&self) -> u64 {
229        self.writes
230    }
231
232    /// How many keys are stored.
233    #[inline]
234    pub fn len(&self) -> usize {
235        self.index.len()
236    }
237
238    /// Whether the map is empty.
239    #[inline]
240    pub fn is_empty(&self) -> bool {
241        self.index.is_empty()
242    }
243
244    /// Throw everything away and give the memory back.
245    ///
246    /// A fresh index and a fresh arena rather than a walk that deletes each key
247    /// in turn. Deleting one at a time would leave an arena the size of the
248    /// data that used to be in it and an index still grown to fit it, and the
249    /// one thing a client that has just said `FLUSHALL` is entitled to expect is
250    /// the memory back.
251    pub fn clear(&mut self) {
252        // Carried across the reset and bumped, because a counter that went back
253        // to zero here could land on a value a memo was already holding and
254        // read as "nothing moved" on the one call where everything did.
255        let writes = self.writes;
256        let compaction = self.compaction;
257        *self = RawMap::new();
258        self.writes = writes + 1;
259        // Carried for a plainer reason: it is what this store has spent, and a
260        // `FLUSHALL` does not give any of it back.
261        self.compaction = compaction;
262    }
263
264    /// The hash this map files `key` under.
265    ///
266    /// Public because the batch walk in `04` section 3 hashes on the first walk
267    /// and looks up on the second, and the alternative is hashing every key
268    /// twice to keep the seed a private detail.
269    #[inline]
270    #[must_use]
271    pub fn hash_of(key: &[u8]) -> u64 {
272        wyhash(key, 0)
273    }
274
275    /// Ask the cache for the bucket `hash` will be looked up in.
276    #[inline]
277    pub fn prefetch(&self, hash: u64) {
278        self.index.prefetch(hash);
279    }
280
281    /// The value stored under `key`.
282    #[inline]
283    pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
284        self.get_hashed(Self::hash_of(key), key)
285    }
286
287    /// The value stored under `key`, whose hash the caller already has.
288    ///
289    /// The second walk's entry point. `hash` has to be [`RawMap::hash_of`] of
290    /// this key: a hash from somewhere else is not unsafe, it just misses.
291    #[inline]
292    pub fn get_hashed(&self, hash: u64, key: &[u8]) -> Option<&[u8]> {
293        let addr = self.index.get(hash, key, &Records { arena: &self.arena })?;
294        Some(self.value_at(addr))
295    }
296
297    /// Where `key`'s record is, for a caller that has to look at it twice.
298    ///
299    /// A `GET` has to know whether the key is past its deadline before it can
300    /// answer, and then has to read the value it just decided about. Asking
301    /// [`RawMap::get`] twice is two hashes and two probes for one record, and a
302    /// probe is the expensive half of a command. This hands back the address
303    /// instead, and [`RawMap::value_at`] reads it with no probe at all.
304    ///
305    /// The address is good until the next write to this map. Anything that
306    /// inserts, deletes or compacts can move a record, and an address held
307    /// across one of those reads whatever is at that spot now. Hold it for the
308    /// length of one command and no longer.
309    #[inline]
310    pub fn find(&self, key: &[u8]) -> Option<Addr> {
311        self.find_hashed(Self::hash_of(key), key)
312    }
313
314    /// [`RawMap::find`] for a caller that already hashed the key.
315    #[inline]
316    pub fn find_hashed(&self, hash: u64, key: &[u8]) -> Option<Addr> {
317        self.index.get(hash, key, &Records { arena: &self.arena })
318    }
319
320    /// The value at an address this map handed out, with no probe.
321    ///
322    /// See [`RawMap::find`] for how long an address is worth holding.
323    #[inline]
324    #[must_use]
325    pub fn value_at(&self, addr: Addr) -> &[u8] {
326        let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
327        &self.arena.get(addr, HDR + klen + vlen)[HDR + klen..]
328    }
329
330    /// The value at an address, to be overwritten in place, without counting as
331    /// a write.
332    ///
333    /// This is the one method taking a mutable borrow that leaves
334    /// [`RawMap::writes`] where it was, and that is a deliberate exception to
335    /// the rule stated on the counter rather than an oversight in it.
336    ///
337    /// It is sound because nothing moves. The record already exists, the caller
338    /// already holds its address, there is no allocation and no index write, so
339    /// every address and every number read out of a record before the call is
340    /// still right afterwards. That is a stronger guarantee than the counter is
341    /// asking about, and it is one this method can actually make.
342    ///
343    /// It exists because the conservative answer costs more here than it
344    /// protects. The eviction clock is written back on nearly every read, under
345    /// eight of the ten policies including the default, so counting it as a write
346    /// would invalidate the caller's memo on every single command rather than on
347    /// every write. That is a measured nineteen nanoseconds a command on single
348    /// key `SADD`, given up to avoid thinking once about three bytes written
349    /// inside a record that is not going anywhere.
350    ///
351    /// The length cannot change, for the same reason it cannot in
352    /// [`RawMap::value_mut`], and an address is only good until the next real
353    /// write, for the same reason it is in [`RawMap::find`].
354    #[inline]
355    pub fn value_at_mut(&mut self, addr: Addr) -> &mut [u8] {
356        let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
357        &mut self.arena.get_mut(addr, HDR + klen + vlen)[HDR + klen..]
358    }
359
360    /// The value stored under `key`, to be overwritten where it lies.
361    ///
362    /// The length cannot change, which is the whole reason this is safe to
363    /// offer. `INCR` on an integer encoded string is a probe, an add and a
364    /// store, and the store is eight bytes back into the record it came from
365    /// (`08` section 2). Going through [`RawMap::set`] instead would write a
366    /// fresh record and free the old one on every increment, which is an arena
367    /// append and a dead byte per operation for a value whose size never moves.
368    ///
369    /// There is no reader to tear. A map belongs to one shard thread and is not
370    /// `Sync`, so the only code that can observe a half written value is the
371    /// code doing the writing. When a replica stream or a snapshot reader starts
372    /// walking the arena from another thread, this becomes an epoch question and
373    /// the write becomes an install rather than an overwrite.
374    #[inline]
375    pub fn value_mut(&mut self, key: &[u8]) -> Option<&mut [u8]> {
376        self.value_mut_hashed(Self::hash_of(key), key)
377    }
378
379    /// [`RawMap::value_mut`] for a caller that already hashed the key.
380    #[inline]
381    pub fn value_mut_hashed(&mut self, hash: u64, key: &[u8]) -> Option<&mut [u8]> {
382        self.writes += 1;
383        let addr = self.index.get(hash, key, &Records { arena: &self.arena })?;
384        let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
385        Some(&mut self.arena.get_mut(addr, HDR + klen + vlen)[HDR + klen..])
386    }
387
388    /// Store `val` under `key`, returning the length of the value it replaced.
389    pub fn set(&mut self, key: &[u8], val: &[u8]) -> Option<usize> {
390        self.set_with(
391            key,
392            val.len(),
393            |_| {},
394            |buf| {
395                buf.copy_from_slice(val);
396                false
397            },
398        )
399    }
400
401    /// The largest record this map can store, key and value and header together.
402    ///
403    /// A value past this belongs in the log region rather than the arena, which
404    /// is `06` section 2's business and not this crate's.
405    #[inline]
406    #[must_use]
407    pub const fn max_record() -> usize {
408        yo_arena::MAX_ALLOC
409    }
410
411    /// Bytes of record header in front of the key.
412    #[inline]
413    #[must_use]
414    pub const fn header_len() -> usize {
415        HDR
416    }
417
418    /// Store a `vlen` byte value under `key`, written by `fill`.
419    ///
420    /// The same thing [`RawMap::set`] does, except that the caller writes
421    /// straight into the record instead of building the value somewhere else
422    /// first and having it copied in. A string with a one byte encoding tag in
423    /// front of it would otherwise be assembled in a scratch buffer and then
424    /// memcpy'd again, and two copies for one `SET` is one too many on a path
425    /// that is trying to be ten times faster than Redis.
426    ///
427    /// `fill` is handed exactly `vlen` bytes of uninitialised-looking storage.
428    /// It is arena memory that has been handed out before and freed, so its
429    /// contents are arbitrary and every byte of it must be written. What it
430    /// answers is whether this record should be marked, which is what
431    /// [`RawMap::sample_tagged`] later draws from. A caller with no use for that
432    /// answers `false` and pays a branch.
433    ///
434    /// `peek` is handed the value that was already under `key`, if there was
435    /// one, before anything is written over it. It exists because the caller
436    /// keeps counts that depend on what the old value was, and this is the only
437    /// place those bytes can be read for free: both paths through here have
438    /// already loaded the old record's header to find out how long it is, so the
439    /// value is in cache and would otherwise cost a second lookup to see. A
440    /// caller with nothing to ask passes an empty closure and pays nothing.
441    ///
442    /// # Panics
443    ///
444    /// If the whole record would exceed [`RawMap::max_record`].
445    pub fn set_with<P, F>(&mut self, key: &[u8], vlen: usize, peek: P, fill: F) -> Option<usize>
446    where
447        P: FnOnce(&[u8]),
448        F: FnOnce(&mut [u8]) -> bool,
449    {
450        self.writes += 1;
451        assert!(key.len() <= u32::MAX as usize, "key too long");
452        assert!(vlen <= u32::MAX as usize, "value too long");
453        let total = HDR + key.len() + vlen;
454        let h = wyhash(key, 0);
455
456        // A key that is already here, in a record exactly the size the new value
457        // needs, is written over where it lies. No allocation, no dead bytes, no
458        // index write, and nothing for compaction to collect later.
459        //
460        // This used to say the in place path had to wait for epochs, because a
461        // reader that had already resolved the address would see a torn value.
462        // That was never a rule this map kept: `value_mut` is the same write and
463        // `INCR` has been doing it since the day it was written, for the same
464        // reason given there. A map belongs to one shard thread and is not
465        // `Sync`, so the only code that can see a half written value is the code
466        // writing it. When a replica stream or a snapshot reader starts walking
467        // the arena from another thread, both of these become an install rather
468        // than an overwrite, together.
469        //
470        // Exactly the size and not merely small enough. A shorter value in a
471        // longer record would leave the header disagreeing with the space the
472        // record occupies, and compaction walks a segment by stepping over each
473        // record by the length in its header, so the walk would land in the
474        // middle of the next one.
475        //
476        // Overwriting a key with a value the same size as the last one is what
477        // half of the world's caches do, and it is what every SET benchmark
478        // does. On gamingpc it was 25 percent of SET throughput at pipeline 16
479        // and 37 percent of MSET, all of it spent making garbage and then
480        // collecting it.
481        if let Some(addr) = self.index.get(h, key, &Records { arena: &self.arena }) {
482            let (klen, old_vlen) = Record::lens(self.arena.get(addr, HDR));
483            debug_assert_eq!(klen, key.len(), "the index matched a different key");
484            // Before `fill`, because the in place path writes over exactly the
485            // bytes `peek` is being handed. Once, and here rather than next to
486            // the free below, because this is the branch that knows the key was
487            // there and both paths out of it go past this line.
488            peek(&self.arena.get(addr, HDR + klen + old_vlen)[HDR + klen..]);
489            if old_vlen == vlen {
490                let rec = self.arena.get_mut(addr, total);
491                let tag = fill(&mut rec[HDR + klen..]);
492                // The record did not move, so this is the only thing that can
493                // have changed about where it stands: `PERSIST` on a key whose
494                // value is the same length is exactly this branch.
495                self.retag(addr, tag);
496                return Some(vlen);
497            }
498        }
499
500        let (addr, buf) = self
501            .arena
502            .alloc(total)
503            .expect("record is larger than a segment");
504        buf[0..4].copy_from_slice(&(key.len() as u32).to_le_bytes());
505        buf[4..8].copy_from_slice(&(vlen as u32).to_le_bytes());
506        // The arena hands back a run padded up to its alignment, so index to
507        // `total` rather than to the end of the slice.
508        buf[HDR..HDR + key.len()].copy_from_slice(key);
509        let tag = fill(&mut buf[HDR + key.len()..total]);
510
511        let old = {
512            let recs = Records { arena: &self.arena };
513            self.index.insert(h, key, addr, &recs)
514        };
515        // After the insert and not before, because the address the old record
516        // was at is only known once the index has handed it back, and tagging
517        // the new one first would put both in the set for the width of the call
518        // if they happened to be the same address, which they cannot be, but the
519        // order that does not depend on that is the one to write.
520        if let Some(prev) = old {
521            self.tagged.remove(prev);
522        }
523        if tag {
524            self.tagged.insert(addr);
525        } else {
526            // Nothing to take out. `addr` is a run the arena has just handed
527            // back, and nothing is ever freed while it is still marked: a delete
528            // unmarks before it frees, an overwrite unmarks the record it
529            // replaces on the line above, and compaction moves the mark before
530            // it frees the copy it moved from. So a fresh address is never in
531            // the set, and this is the common path, which is every `SET` on a
532            // database that has any deadline in it at all.
533            debug_assert!(
534                !self.tagged.contains(addr),
535                "the arena handed out an address that is still marked"
536            );
537        }
538        match old {
539            Some(prev) => {
540                let (pk, pv) = Record::lens(self.arena.get(prev, HDR));
541                self.arena.free(prev, HDR + pk + pv);
542                Some(pv)
543            }
544            None => None,
545        }
546    }
547
548    /// Put `addr` in the marked set, or take it out, to match `tag`.
549    ///
550    /// For the in place path, which is the one where the record was already
551    /// there and could already have been marked. It cannot tell whether the mark
552    /// changed without asking, because a deadline is eight bytes in the record
553    /// and a value eight bytes shorter with a deadline is the same length as a
554    /// value without one, so a write that lands in place is not proof that the
555    /// mark stayed put.
556    ///
557    /// On a database where nothing is marked the ask is one comparison against a
558    /// zero length, which is what the overwhelming majority of servers pay.
559    #[inline]
560    fn retag(&mut self, addr: Addr, tag: bool) {
561        if tag {
562            self.tagged.insert(addr);
563        } else {
564            self.tagged.remove(addr);
565        }
566    }
567
568    /// Remove `key`, returning whether it was there.
569    #[inline]
570    pub fn del(&mut self, key: &[u8]) -> bool {
571        self.del_with(key, |_| {})
572    }
573
574    /// Remove `key`, showing its value to `peek` first, and return whether it
575    /// was there.
576    ///
577    /// The sibling of [`RawMap::set_with`], and it exists for the same reason.
578    /// This already reads the record's header to find out how long it is before
579    /// handing the bytes back to the arena, so the value is in cache and a
580    /// caller who keeps a count that depends on what was removed can read it
581    /// here for the price of a closure call. Asking with a [`RawMap::get`] first
582    /// would be a second lookup for a question this one already knows the answer
583    /// to. `peek` is not called when the key was not there.
584    pub fn del_with<P: FnOnce(&[u8])>(&mut self, key: &[u8], peek: P) -> bool {
585        self.writes += 1;
586        let h = wyhash(key, 0);
587        let addr = {
588            let recs = Records { arena: &self.arena };
589            self.index.remove(h, key, &recs)
590        };
591        match addr {
592            Some(a) => {
593                let (k, v) = Record::lens(self.arena.get(a, HDR));
594                peek(&self.arena.get(a, HDR + k + v)[HDR + k..]);
595                self.tagged.remove(a);
596                self.arena.free(a, HDR + k + v);
597                true
598            }
599            None => false,
600        }
601    }
602
603    /// Whether `key` is present.
604    #[inline]
605    pub fn contains(&self, key: &[u8]) -> bool {
606        let h = wyhash(key, 0);
607        self.index.contains(h, key, &Records { arena: &self.arena })
608    }
609
610    /// The key and the value at an address this map handed out.
611    ///
612    /// The pair rather than either one alone, because they are one contiguous
613    /// read: the header says how long the key is and the value starts where the
614    /// key ends, so asking for both costs what asking for one costs.
615    #[inline]
616    #[must_use]
617    pub fn entry_at(&self, addr: Addr) -> (&[u8], &[u8]) {
618        let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
619        let bytes = self.arena.get(addr, HDR + klen + vlen);
620        (&bytes[HDR..HDR + klen], &bytes[HDR + klen..])
621    }
622
623    /// Walk a batch of the map, and say where the next batch starts.
624    ///
625    /// This is `SCAN`. `budget` is how many entries the caller would like, and
626    /// it is a floor and not a ceiling: the walk stops at the first bucket
627    /// boundary past it, so a batch of ten can come back with fifteen. Redis's
628    /// `COUNT` behaves the same way and for the same reason, which is that a
629    /// bucket is the smallest unit a cursor can name.
630    ///
631    /// A budget of zero still does one bucket, so a caller that keeps passing
632    /// the cursor back always finishes rather than spinning on the same number.
633    ///
634    /// The guarantee, in full: a key that is present for the whole walk is
635    /// handed to `out` at least once. A key added or removed partway through may
636    /// or may not appear, and a key may appear twice. The reasoning is in
637    /// [`Cursor`], and the part worth knowing here is that none of it depends on
638    /// the map holding still between calls.
639    pub fn scan(&self, from: Cursor, budget: usize, mut out: impl FnMut(&[u8], &[u8])) -> Cursor {
640        // The index and the arena are separate fields, so the walk can hold one
641        // and the closure the other. That is what keeps this allocation free:
642        // there is no list of addresses in between.
643        let arena = &self.arena;
644        let mut at = from;
645        let mut seen = 0usize;
646        loop {
647            at = self.index.scan(at, |addr| {
648                let (klen, vlen) = Record::lens(arena.get(addr, HDR));
649                let bytes = arena.get(addr, HDR + klen + vlen);
650                out(&bytes[HDR..HDR + klen], &bytes[HDR + klen..]);
651                seen += 1;
652            });
653            if at.is_end() || seen >= budget {
654                return at;
655            }
656        }
657    }
658
659    /// Entries picked at random, for eviction sampling, until `out` says stop.
660    ///
661    /// The key, the value and the address of each, because a caller choosing a
662    /// victim needs all three: the value to score it, the key to delete it, and
663    /// the address to delete it by without a second probe. `out` answers whether
664    /// to keep going. [`Index::sample`] is where the argument for all of it lives,
665    /// including why the budget is the caller's and why this can hand back
666    /// nothing at all.
667    pub fn sample(&self, r: u64, mut out: impl FnMut(&[u8], &[u8], Addr) -> bool) {
668        let arena = &self.arena;
669        self.index.sample(r, |addr| {
670            let (klen, vlen) = Record::lens(arena.get(addr, HDR));
671            let bytes = arena.get(addr, HDR + klen + vlen);
672            out(&bytes[HDR..HDR + klen], &bytes[HDR + klen..], addr)
673        });
674    }
675
676    /// The index, for stats and for compaction.
677    pub fn index(&self) -> &Index {
678        &self.index
679    }
680
681    /// The arena, for stats and for compaction.
682    pub fn arena(&self) -> &Arena {
683        &self.arena
684    }
685
686    /// Bytes held by index structure plus arena segments.
687    pub fn memory_bytes(&self) -> usize {
688        self.index.memory_bytes()
689            + self.arena.reserved_bytes() as usize
690            + self.tagged.memory_bytes()
691    }
692
693    /// How many records are marked.
694    ///
695    /// Exact, and kept exact by every write path, so a caller can branch on a
696    /// zero here rather than starting a sweep that was never going to find
697    /// anything.
698    #[inline]
699    #[must_use]
700    pub fn tagged_len(&self) -> usize {
701        self.tagged.len()
702    }
703
704    /// Whether the record at `addr` is marked.
705    ///
706    /// For a test and for a debug assertion. Nothing on a hot path asks this:
707    /// the mark is written from the record's own bytes, so anything holding the
708    /// record already knows.
709    #[must_use]
710    pub fn is_tagged(&self, addr: Addr) -> bool {
711        self.tagged.contains(addr)
712    }
713
714    /// Walk marked records from wherever `r` lands, until `out` says stop.
715    ///
716    /// [`RawMap::sample`] for the marked subset, and the reason the subset
717    /// exists. A database of ten million keys where a thousand carry a deadline
718    /// gives the expire cycle a thousand candidates to draw from instead of ten
719    /// million, and the cycle stops costing anything at all in the case that
720    /// matters most, which is the one where the answer is that there is nothing
721    /// to do.
722    pub fn sample_tagged(&self, r: u64, mut out: impl FnMut(&[u8], &[u8], Addr) -> bool) {
723        let arena = &self.arena;
724        self.tagged.sample(r, |addr| {
725            let (klen, vlen) = Record::lens(arena.get(addr, HDR));
726            let bytes = arena.get(addr, HDR + klen + vlen);
727            out(&bytes[HDR..HDR + klen], &bytes[HDR + klen..], addr)
728        });
729    }
730
731    /// Move every live record out of `seg` and into the current segment, then
732    /// put the segment back on the arena's free list.
733    ///
734    /// Copy, rewrite the index entry, done. No forwarding pointers and no read
735    /// barrier, which is the F2 shape from `05` section 3.2 and is what an
736    /// allocation having exactly one referent buys.
737    ///
738    /// The walk is over the segment and not over the index. Both find the same
739    /// records, and the index walk is the one written in the spec, but it reads
740    /// the whole index to compact two megabytes: fine when this only ran in a
741    /// test, wrong once the event loop calls it, because the pause would then
742    /// grow with the size of the database rather than with the size of a
743    /// segment. Walking the segment costs one index probe per record in it and
744    /// does not care how many keys exist elsewhere.
745    ///
746    /// Records sit back to back from the header to the segment's bump, each one
747    /// rounded up to the arena's alignment, and every arena allocation is a
748    /// record, so the next one is always a known distance away. A record is
749    /// live when the index still points at this copy of it, and dead when it
750    /// points somewhere else or at nothing, which is exactly what an overwrite
751    /// and a delete leave behind.
752    ///
753    /// The reclaim at the end is the part that makes the space usable again.
754    /// Moving the records out only makes a segment empty, and an empty segment
755    /// that nothing ever bumps through again is still two megabytes the process
756    /// is holding.
757    pub fn compact_segment(&mut self, seg: usize) -> usize {
758        self.writes += 1;
759        if seg == self.arena.current_segment() {
760            // Its bump is a cursor, not a checkpoint, and reclaiming it would
761            // take the ground out from under the next allocation.
762            return 0;
763        }
764        let (moved, _) = self.evacuate(seg, yo_arena::HEADER_SIZE, usize::MAX);
765        self.arena.reclaim(seg);
766        moved
767    }
768
769    /// Walk `seg` from `from`, moving live records out, and stop once the walk
770    /// has covered `budget` bytes of it. Says how many records moved and where
771    /// to start again.
772    ///
773    /// The record that straddles the budget is finished rather than cut in
774    /// half, so the walk can go a little past what was asked for. The overrun
775    /// is one record and the budget is thousands of bytes.
776    ///
777    /// Nothing here reclaims. A segment is only empty once the walk reaches the
778    /// bump, and the caller is the one that knows whether it did.
779    fn evacuate(&mut self, seg: usize, from: usize, budget: usize) -> (usize, usize) {
780        let base = (seg as u64) << yo_arena::SEGMENT_SHIFT;
781        let bump = self.arena.recorded_bump(seg) as usize;
782        let stop = from.saturating_add(budget).min(bump);
783
784        let mut moved = 0;
785        let mut off = from;
786        while off < stop {
787            let old = Addr::new(Space::Arena, base + off as u64);
788            // Ask the arena before reading anything as a record. A run that was
789            // freed and put on a size class list has the arena's links written
790            // over the front of it, so the lengths this walk steps by are not
791            // there to read, and the run has to come off its list before the
792            // segment goes back: a reclaimed segment gives its pages away and
793            // is bumped through again, so a run of it still on a list would be
794            // handed to one caller while another writes over it. The span the
795            // arena gives back is already rounded to its alignment.
796            if let Some(span) = self.arena.listed_at(old) {
797                off += span;
798                self.compaction.walked += 1;
799                self.arena.unlist(old);
800                continue;
801            }
802            let (klen, vlen) = Record::lens(self.arena.get(old, HDR));
803            let total = HDR + klen + vlen;
804            off += total.next_multiple_of(yo_arena::ALIGN);
805            self.compaction.walked += 1;
806
807            let hash = {
808                let bytes = self.arena.get(old, HDR + klen);
809                wyhash(&bytes[HDR..], 0)
810            };
811            let live = {
812                let bytes = self.arena.get(old, HDR + klen);
813                let key = &bytes[HDR..];
814                let recs = Records { arena: &self.arena };
815                self.index.get(hash, key, &recs) == Some(old)
816            };
817            if !live {
818                continue;
819            }
820
821            let new = self.arena.copy_within(old, total);
822            let bytes = self.arena.get(new, HDR + klen);
823            let key = &bytes[HDR..];
824            let recs = Records { arena: &self.arena };
825            let ok = self.index.relocate(hash, key, new, &recs);
826            debug_assert!(ok, "compaction lost an entry the index just handed us");
827            // The one place a record moves without anybody writing to it, and
828            // therefore the one place the tagged set would go stale if this line
829            // were not here.
830            if self.tagged.remove(old) {
831                self.tagged.insert(new);
832            }
833            // Discarded rather than freed. The space is inside the segment
834            // being emptied and must not be offered to anybody before the
835            // segment comes back whole.
836            self.arena.discard(old, total);
837            moved += 1;
838            self.compaction.moved += 1;
839            self.compaction.bytes += total as u64;
840        }
841        (moved, off)
842    }
843
844    /// How much to walk on this call, given how far behind the collector is.
845    ///
846    /// A fixed budget has to be either a good pause or a good collection rate
847    /// and it cannot be both. At 64 kilobytes a segment takes thirty two calls,
848    /// and a pipelined flood of writes makes garbage faster than one call per
849    /// batch gets it back: measured with variable sized values at pipeline 16,
850    /// the tail came down from 2.6 milliseconds to 1.6 and the process held 18
851    /// MB more, because segments queued up waiting their turn to be walked.
852    ///
853    /// So the floor is what a command can be asked to wait for, and the depth
854    /// of that queue is what says how much more than the floor is needed to
855    /// keep up. One candidate is a store that is keeping up and pays the floor.
856    /// Nine is a store nine segments behind, and it walks nine slices.
857    ///
858    /// The queue and not the dead byte total. Dead bytes were tried first,
859    /// measured against the point compaction starts at, and that ratio cannot
860    /// see a backlog at all: the threshold is a fraction of what the arena
861    /// holds, so a collector that falls behind grows the arena, which raises
862    /// the threshold, which puts the ratio back where it was. It sat at the
863    /// floor through the whole flood and the 18 MB stayed exactly where it was.
864    /// A count of segments has no such denominator.
865    ///
866    /// Linear in the depth and not squared. This is a controller in a loop with
867    /// its own input, and a term that grows faster than the error is how one of
868    /// those starts to oscillate.
869    fn budget(&self) -> usize {
870        let behind = self.arena.candidate_count().max(1);
871        EVAC_FLOOR.saturating_mul(behind).min(EVAC_CEILING)
872    }
873
874    /// Do one bounded slice of compaction, and say how many records moved.
875    ///
876    /// `None` means there was no candidate and there is nothing in flight. It
877    /// is not the same as `Some(0)`, which is a slice that walked only records
878    /// that had already been overwritten: that one made progress and cost
879    /// something, and a caller deciding whether to go round again needs to be
880    /// told so.
881    ///
882    /// This is the whole maintenance contract: a bounded amount of work per
883    /// call, so a caller that runs it once per batch never pays for a full pass
884    /// over the arena and never pays for a whole segment either. Finding out
885    /// there is nothing to do is one comparison against the running dead byte
886    /// total.
887    ///
888    /// A segment takes as many calls as it takes. Each one picks up where the
889    /// last stopped and only the call that reaches the end gives the two
890    /// megabytes back, so the space comes back in one lump at the end while the
891    /// cost of getting it back is spread over the batches in between. That is
892    /// the trade: a segment stays around a little longer than it used to, and
893    /// no single command waits for the whole of it.
894    ///
895    /// The segment in flight is finished before another is chosen, rather than
896    /// asking which segment is worst on every call. Otherwise a segment that is
897    /// three quarters evacuated could be put down in favour of a worse one and
898    /// never picked up, and the arena would fill with segments that are nearly
899    /// empty and never reclaimed.
900    pub fn compact_step(&mut self) -> Option<usize> {
901        self.compact(Sweep::Ordinary)
902    }
903
904    /// One slice of compaction for a store that has run out of room.
905    ///
906    /// The same work, choosing between segments the way
907    /// [`Arena::any_candidate`](yo_arena::Arena::any_candidate) chooses rather
908    /// than the way [`Arena::worst_candidate`](yo_arena::Arena::worst_candidate)
909    /// does, so a store that is clean overall still collects the parts of it
910    /// that are not. The reason is written on `any_candidate`.
911    ///
912    /// A segment already in flight is finished first either way, so switching
913    /// between this and [`RawMap::compact_step`] cannot leave a segment half
914    /// evacuated forever.
915    pub fn compact_hard(&mut self) -> Option<usize> {
916        self.compact(Sweep::Hard)
917    }
918
919    fn compact(&mut self, sweep: Sweep) -> Option<usize> {
920        self.writes += 1;
921        let (seg, from) = match self.evac {
922            Some(e) => (e.seg, e.off),
923            None => {
924                let pick = match sweep {
925                    Sweep::Ordinary => self.arena.worst_candidate()?,
926                    Sweep::Hard => self.arena.any_candidate()?,
927                };
928                (pick, yo_arena::HEADER_SIZE)
929            }
930        };
931        if seg == self.arena.current_segment() {
932            self.evac = None;
933            return Some(0);
934        }
935
936        // After the choice and not before it. The count is a walk over the
937        // segment headers, and a store with nothing to collect should not pay
938        // for one on every batch to be told there is nothing to collect.
939        let budget = self.budget();
940        let (moved, off) = self.evacuate(seg, from, budget);
941        if off >= self.arena.recorded_bump(seg) as usize {
942            self.arena.reclaim(seg);
943            self.evac = None;
944        } else {
945            self.evac = Some(Evac { seg, off });
946        }
947        Some(moved)
948    }
949}
950
951impl Default for RawMap {
952    fn default() -> RawMap {
953        RawMap::new()
954    }
955}
956
957#[cfg(test)]
958mod tests {
959    use super::*;
960    use std::collections::{HashMap, HashSet};
961
962    /// `key:` and the index zero padded to twelve digits.
963    ///
964    /// Written out by hand rather than with `format!`, which produces the same
965    /// bytes. Formatting is a lot of machinery for twelve digits, and Miri pays
966    /// per operation rather than per instruction, so under the interpreter one
967    /// `format!` costs a couple of milliseconds. `grows_through_many_splits`
968    /// calls this once per set, get, delete and contains, which is ten thousand
969    /// calls on its own, and that is twenty seconds of the ninety five this
970    /// crate's Miri shard used to take.
971    fn key(i: usize) -> Vec<u8> {
972        let mut k = *b"key:000000000000";
973        let mut n = i;
974        let mut p = k.len() - 1;
975        while n > 0 {
976            k[p] = b'0' + (n % 10) as u8;
977            n /= 10;
978            p -= 1;
979        }
980        k.to_vec()
981    }
982
983    /// `v` and the index, unpadded, which is what `format!("v{i}")` gives.
984    fn val(i: usize) -> Vec<u8> {
985        let mut v = vec![b'v'];
986        if i == 0 {
987            v.push(b'0');
988            return v;
989        }
990        let start = v.len();
991        let mut n = i;
992        while n > 0 {
993            v.push(b'0' + (n % 10) as u8);
994            n /= 10;
995        }
996        v[start..].reverse();
997        v
998    }
999
1000    // Miri is a few hundred times slower than the machine, so the counts below
1001    // shrink under it. They stay large enough to force directory doublings,
1002    // segment splits and overflow chains, which is what these tests are for.
1003    // Only the scale goes away, not the coverage.
1004    // Three thousand and not fewer. `splits() > 4` is the assertion and the
1005    // splits go 1, 1, 2, 3, 3, 5 at 800, 1200, 1500, 2000, 2500 and 3000 keys,
1006    // so this is already the smallest count that grows the directory the number
1007    // of times the test asks about.
1008    #[cfg(miri)]
1009    const GROW_N: usize = 3_000;
1010    #[cfg(not(miri))]
1011    const GROW_N: usize = 200_000;
1012
1013    #[cfg(miri)]
1014    const ADVERSARIAL_N: u64 = 1_000;
1015    #[cfg(not(miri))]
1016    const ADVERSARIAL_N: u64 = 50_000;
1017
1018    // Big values so that a handful of records fills a 2 MiB segment and
1019    // compaction has something to do without a hundred thousand writes.
1020    #[cfg(miri)]
1021    const COMPACT_VAL: usize = 65_536;
1022    #[cfg(miri)]
1023    const COMPACT_N: usize = 200;
1024    #[cfg(not(miri))]
1025    const COMPACT_VAL: usize = 1024;
1026    #[cfg(not(miri))]
1027    const COMPACT_N: usize = 8_000;
1028
1029    #[test]
1030    fn set_get_del() {
1031        let mut m = RawMap::new();
1032        assert!(m.is_empty());
1033        assert_eq!(m.set(b"a", b"1"), None);
1034        assert_eq!(m.get(b"a"), Some(&b"1"[..]));
1035        assert_eq!(m.len(), 1);
1036        assert_eq!(m.set(b"a", b"22"), Some(1));
1037        assert_eq!(m.get(b"a"), Some(&b"22"[..]));
1038        assert_eq!(m.len(), 1);
1039        assert!(m.del(b"a"));
1040        assert!(!m.del(b"a"));
1041        assert_eq!(m.get(b"a"), None);
1042        assert!(m.is_empty());
1043    }
1044
1045    #[test]
1046    fn a_value_can_be_overwritten_where_it_lies() {
1047        let mut m = RawMap::new();
1048        m.set(b"n", &7u64.to_le_bytes());
1049        m.set(b"other", b"untouched");
1050        let before = m.arena().live_bytes();
1051
1052        let v = m.value_mut(b"n").expect("the key is there");
1053        v.copy_from_slice(&8u64.to_le_bytes());
1054
1055        assert_eq!(m.get(b"n"), Some(&8u64.to_le_bytes()[..]));
1056        assert_eq!(m.get(b"other"), Some(&b"untouched"[..]));
1057        // The point of the whole method: no second record and nothing dead.
1058        assert_eq!(m.arena().live_bytes(), before);
1059        assert_eq!(m.len(), 2);
1060
1061        assert!(m.value_mut(b"missing").is_none());
1062    }
1063
1064    /// A key overwritten with a value the same size stays in the record it is
1065    /// already in, and one overwritten with a different size does not.
1066    ///
1067    /// The first is the shape every SET benchmark and half the world's caches
1068    /// have: the same keys, the same value size, over and over. Writing a fresh
1069    /// record for each of those makes a dead one to go with it, and compaction
1070    /// then spends a quarter of the server's write throughput copying live
1071    /// records out from between them.
1072    #[test]
1073    fn an_overwrite_of_the_same_size_makes_no_garbage() {
1074        let mut m = RawMap::new();
1075        m.set(b"k", b"12345678");
1076        m.set(b"other", b"untouched");
1077        let live = m.arena().live_bytes();
1078        let dead = m.arena().dead_bytes_total();
1079
1080        for i in 0..1000u32 {
1081            let v = format!("{i:08}");
1082            assert_eq!(m.set(b"k", v.as_bytes()), Some(8));
1083        }
1084
1085        assert_eq!(m.get(b"k"), Some(&b"00000999"[..]));
1086        assert_eq!(m.get(b"other"), Some(&b"untouched"[..]));
1087        assert_eq!(m.len(), 2);
1088        assert_eq!(m.arena().live_bytes(), live, "a thousand writes, no growth");
1089        assert_eq!(m.arena().dead_bytes_total(), dead, "and nothing dead");
1090
1091        // A different length cannot go in the same hole, because the record has
1092        // to be as long as its header says it is.
1093        assert_eq!(m.set(b"k", b"123456789"), Some(8));
1094        assert_eq!(m.get(b"k"), Some(&b"123456789"[..]));
1095        assert!(
1096            m.arena().dead_bytes_total() > dead,
1097            "the old record is dead"
1098        );
1099    }
1100
1101    /// An expiring value and a plain one are different record lengths, so the
1102    /// one does not get written over the other.
1103    ///
1104    /// This is the case the in place path has to refuse rather than the case it
1105    /// is for, and it is the one that would corrupt a record if it took it: the
1106    /// value here is a keyspace record, whose deadline is inside the value, so
1107    /// two values of the same visible length are two different record lengths.
1108    #[test]
1109    fn a_longer_value_moves_and_the_index_follows_it() {
1110        let mut m = RawMap::new();
1111        m.set(b"k", b"aaaa");
1112        let first = m
1113            .index()
1114            .get(RawMap::hash_of(b"k"), b"k", &Records { arena: m.arena() });
1115
1116        m.set(b"k", b"aaaaaaaa");
1117        let second = m
1118            .index()
1119            .get(RawMap::hash_of(b"k"), b"k", &Records { arena: m.arena() });
1120
1121        assert_ne!(first, second, "a longer value needs a new record");
1122        assert_eq!(m.get(b"k"), Some(&b"aaaaaaaa"[..]));
1123    }
1124
1125    #[test]
1126    fn empty_key_and_empty_value() {
1127        let mut m = RawMap::new();
1128        m.set(b"", b"");
1129        assert_eq!(m.get(b""), Some(&b""[..]));
1130        m.set(b"x", b"");
1131        assert_eq!(m.get(b"x"), Some(&b""[..]));
1132        assert_eq!(m.len(), 2);
1133    }
1134
1135    #[test]
1136    fn grows_through_many_splits() {
1137        let mut m = RawMap::new();
1138        const N: usize = GROW_N;
1139        for i in 0..N {
1140            m.set(&key(i), &val(i));
1141        }
1142        assert_eq!(m.len(), N);
1143        assert!(
1144            m.index().splits() > 4,
1145            "expected real growth, saw {} splits",
1146            m.index().splits()
1147        );
1148        for i in 0..N {
1149            assert_eq!(
1150                m.get(&key(i)),
1151                Some(val(i).as_slice()),
1152                "lost key {i} after {} splits",
1153                m.index().splits()
1154            );
1155        }
1156        for i in (0..N).step_by(3) {
1157            assert!(m.del(&key(i)), "delete missed key {i}");
1158        }
1159        for i in 0..N {
1160            assert_eq!(
1161                m.contains(&key(i)),
1162                i % 3 != 0,
1163                "wrong presence for key {i}"
1164            );
1165        }
1166    }
1167
1168    #[test]
1169    fn compaction_preserves_everything() {
1170        let mut m = RawMap::new();
1171        // Enough to fill several arena segments with 1 KiB values.
1172        let val = vec![b'z'; COMPACT_VAL];
1173        const N: usize = COMPACT_N;
1174        for i in 0..N {
1175            m.set(&key(i), &val);
1176        }
1177        // Kill half, which pushes the early segments over the dead ratio.
1178        for i in (0..N).step_by(2) {
1179            m.del(&key(i));
1180        }
1181        let candidates = m.arena().compaction_candidates();
1182        assert!(
1183            !candidates.is_empty(),
1184            "expected at least one segment past the dead ratio"
1185        );
1186        for seg in candidates {
1187            m.compact_segment(seg);
1188        }
1189        for i in 0..N {
1190            let want = if i % 2 == 0 { None } else { Some(val.clone()) };
1191            assert_eq!(m.get(&key(i)).map(|v| v.to_vec()), want, "key {i}");
1192        }
1193    }
1194
1195    /// A mark follows its record wherever the record goes.
1196    ///
1197    /// The whole reason the marked set lives in this file. Compaction moves a
1198    /// record to a new address without anybody writing to it, so a set of
1199    /// addresses kept by a caller would be pointing at freed space afterwards,
1200    /// and the sample would read whatever the arena handed out next.
1201    #[test]
1202    fn compaction_carries_the_marks_with_it() {
1203        let mut m = RawMap::new();
1204        let val = vec![b'z'; COMPACT_VAL];
1205        const N: usize = COMPACT_N;
1206        for i in 0..N {
1207            m.set_with(
1208                &key(i),
1209                val.len(),
1210                |_| {},
1211                |b| {
1212                    b.copy_from_slice(&val);
1213                    i % 3 == 0
1214                },
1215            );
1216        }
1217        let want = (0..N).filter(|i| i % 3 == 0).count();
1218        assert_eq!(m.tagged_len(), want);
1219
1220        for i in (0..N).step_by(2) {
1221            m.del(&key(i));
1222        }
1223        let want = (0..N).filter(|i| i % 3 == 0 && i % 2 == 1).count();
1224        assert_eq!(m.tagged_len(), want, "a delete takes the mark with it");
1225
1226        for seg in m.arena().compaction_candidates() {
1227            m.compact_segment(seg);
1228        }
1229        assert_eq!(
1230            m.tagged_len(),
1231            want,
1232            "and compaction moves it rather than losing it"
1233        );
1234
1235        // Every mark points at a record that is still there and is one of the
1236        // ones that was marked, which is what a stale address would fail.
1237        let mut seen = 0;
1238        m.sample_tagged(0, |k, _, addr| {
1239            assert!(m.get(k).is_some(), "a mark on a key that is gone");
1240            let i: usize = std::str::from_utf8(&k[4..]).unwrap().parse().unwrap();
1241            assert!(
1242                i.is_multiple_of(3) && !i.is_multiple_of(2),
1243                "key {i} was never marked"
1244            );
1245            assert!(m.is_tagged(addr));
1246            seen += 1;
1247            true
1248        });
1249        assert_eq!(seen, want);
1250    }
1251
1252    /// A mark goes on and comes off with the record's own bytes, which is how
1253    /// PERSIST works: the value is the same length, so the record does not move
1254    /// and only the mark changes.
1255    #[test]
1256    fn a_mark_goes_on_and_comes_off_in_place() {
1257        let mut m = RawMap::new();
1258        let mark = |m: &mut RawMap, on: bool| {
1259            m.set_with(
1260                b"k",
1261                1,
1262                |_| {},
1263                |b| {
1264                    b[0] = b'v';
1265                    on
1266                },
1267            )
1268        };
1269        mark(&mut m, true);
1270        assert_eq!(m.tagged_len(), 1);
1271        mark(&mut m, true);
1272        assert_eq!(m.tagged_len(), 1, "marking twice is marking once");
1273        mark(&mut m, false);
1274        assert_eq!(m.tagged_len(), 0);
1275        mark(&mut m, true);
1276        assert_eq!(m.tagged_len(), 1);
1277        assert!(m.del(b"k"));
1278        assert_eq!(m.tagged_len(), 0);
1279    }
1280
1281    /// A store rewriting keys at mixed sizes gets its space back from the free
1282    /// lists rather than from a collector copying every live record around it.
1283    ///
1284    /// This is tamnd/yo#518. The harness draws its value length uniformly from
1285    /// 1 to 1024, so the in place path in [`RawMap::set_with`] almost never
1286    /// fires and every write left a hole nothing could use. Compaction was then
1287    /// the only way space came back, and it copies the live records that share
1288    /// a segment with the hole, which at the ratio it starts at is three bytes
1289    /// copied for every byte written. On the eight core box that read 138
1290    /// thousand sets a second against 605 thousand for a fixed length, from the
1291    /// value length and nothing else.
1292    ///
1293    /// The lengths repeat across passes, which is the property that matters:
1294    /// what a pass frees is what the next pass asks for. The assertion is on
1295    /// bytes copied and not on throughput, because copying is the cost and a
1296    /// test cannot time anything. With the lists off it copies 67 MB to write
1297    /// 23 MB, which is the 2.85 the server measured. With them on it copies
1298    /// nothing at all, because no segment ever reaches the ratio. The bar is
1299    /// set at half rather than at zero so that a change which leaves a little
1300    /// for the collector to do is not a failure.
1301    #[test]
1302    fn rewriting_at_mixed_sizes_reuses_instead_of_copying() {
1303        let mut m = RawMap::new();
1304        const N: usize = COMPACT_N;
1305        // Twelve lengths spread over the harness's range, all of them under the
1306        // arena's largest reusable class.
1307        let len = |i: usize| 64 + (i % 12) * 96;
1308        let write = |m: &mut RawMap, pass: usize| {
1309            for i in 0..N {
1310                m.set(&key(i), &vec![b'z'; len(i + pass)]);
1311                m.compact_step();
1312            }
1313        };
1314
1315        write(&mut m, 0);
1316        let after_first = m.arena().reserved_bytes();
1317        let start = m.compaction().bytes;
1318
1319        let mut written = 0u64;
1320        for pass in 1..6 {
1321            write(&mut m, pass);
1322            written += (0..N).map(|i| len(i + pass) as u64).sum::<u64>();
1323        }
1324        let copied = m.compaction().bytes - start;
1325
1326        assert!(
1327            copied < written / 2,
1328            "copied {copied} bytes to write {written}, which is the shape the \
1329             free lists exist to fix"
1330        );
1331        assert!(
1332            m.arena().reserved_bytes() <= after_first * 2,
1333            "held {} after six passes against {after_first} after one",
1334            m.arena().reserved_bytes()
1335        );
1336        for i in 0..N {
1337            assert_eq!(m.get(&key(i)).map(<[u8]>::len), Some(len(i + 5)), "key {i}");
1338        }
1339    }
1340
1341    /// The bug this exists for: overwriting a key writes a new record and only
1342    /// counts the old one dead, so without compaction a server that rewrites
1343    /// the same keys holds every version of every one of them forever. Measured
1344    /// on a real server before this, 400000 sets over 100000 keys came to 742
1345    /// bytes a key for 64 byte values.
1346    #[test]
1347    fn rewriting_the_same_keys_stops_growing() {
1348        let mut m = RawMap::new();
1349        let val = vec![b'z'; COMPACT_VAL];
1350        const N: usize = COMPACT_N;
1351
1352        for i in 0..N {
1353            m.set(&key(i), &val);
1354            m.compact_step();
1355        }
1356        let after_first_pass = m.arena().reserved_bytes();
1357
1358        // Nine more passes over the same keys, writing the same amount of data
1359        // nine more times and keeping exactly as much of it.
1360        for _ in 0..9 {
1361            for i in 0..N {
1362                m.set(&key(i), &val);
1363                m.compact_step();
1364            }
1365        }
1366        let after_ten = m.arena().reserved_bytes();
1367
1368        assert!(
1369            after_ten <= after_first_pass * 2,
1370            "held {after_ten} after ten passes against {after_first_pass} after one, \
1371             which is the grow forever shape"
1372        );
1373        assert!(
1374            after_ten < m.arena().live_bytes() * 2,
1375            "held {after_ten} for {} live, which is more than the ratio allows",
1376            m.arena().live_bytes()
1377        );
1378        for i in 0..N {
1379            assert_eq!(
1380                m.get(&key(i)).map(<[u8]>::to_vec),
1381                Some(val.clone()),
1382                "key {i}"
1383            );
1384        }
1385    }
1386
1387    /// A segment is evacuated over several calls, and it comes back only on the
1388    /// call whose walk reaches the end of it.
1389    ///
1390    /// This is what the budget is for. One call used to copy every live record
1391    /// in two megabytes, around twenty six thousand of them at 64 byte values,
1392    /// and the whole batch of replies queued behind it waited for all of them.
1393    /// That is where a p99 of 3.9 milliseconds on the write rows came from
1394    /// while the p50 was in line with Redis: the median command paid nothing
1395    /// and one command in a few thousand paid for a segment.
1396    ///
1397    /// The loop is also what catches a walk that restarts instead of resuming.
1398    /// A restart would move records and look like progress, and it would spend
1399    /// every call re-walking the dead space it made on the last one, so the
1400    /// cursor would never reach the bump and the segment would never come back.
1401    #[test]
1402    fn a_segment_comes_back_over_several_calls() {
1403        let mut m = RawMap::new();
1404        let val = vec![b'z'; COMPACT_VAL];
1405        const N: usize = COMPACT_N;
1406        for i in 0..N {
1407            m.set(&key(i), &val);
1408        }
1409        // Every other key, so the early segments are well past the dead ratio
1410        // and there is still a live half to copy out.
1411        for i in (0..N).step_by(2) {
1412            m.del(&key(i));
1413        }
1414
1415        let rec = (HDR + key(0).len() + COMPACT_VAL).next_multiple_of(yo_arena::ALIGN);
1416        let per_call = m.budget() / rec + 1;
1417        let free = m.arena().free_segments();
1418
1419        let moved = m.compact_step().expect("half of it is dead");
1420        assert!(
1421            moved <= per_call,
1422            "one call moved {moved} records and the budget is {per_call}"
1423        );
1424        assert_eq!(
1425            m.arena().free_segments(),
1426            free,
1427            "a segment came back before the walk reached the end of it"
1428        );
1429
1430        let mut calls = 1;
1431        while m.arena().free_segments() == free {
1432            m.compact_step()
1433                .expect("the segment in flight is not finished");
1434            calls += 1;
1435            assert!(calls < 1000, "the walk is not getting any further along");
1436        }
1437        assert!(calls > 2, "the whole segment came back in {calls} calls");
1438
1439        for i in 0..N {
1440            let want = if i % 2 == 0 { None } else { Some(val.clone()) };
1441            assert_eq!(m.get(&key(i)).map(<[u8]>::to_vec), want, "key {i}");
1442        }
1443    }
1444
1445    /// A store barely holding any garbage collects nothing until it is asked to.
1446    ///
1447    /// The global ratio is the reason [`RawMap::compact_hard`] exists. A server
1448    /// under a memory limit needs the pages back whether or not the store as a
1449    /// whole is dirty enough to be worth a sweep, and a server that is not under
1450    /// one should not pay for copying that buys it a few kilobytes.
1451    ///
1452    /// The per segment ratio is a different question and the hard path keeps it.
1453    /// What is being asked for here is a store that is clean overall and has one
1454    /// part of it that is not, which is why the deletes are a run and not a
1455    /// stride: records land in the order they were written, so a run of them
1456    /// empties out the segments it lands in rather than taking a tenth off every
1457    /// segment and leaving none of them worth moving.
1458    #[test]
1459    fn a_store_with_little_dead_in_it_only_collects_when_pushed() {
1460        let mut m = RawMap::new();
1461        let val = vec![b'z'; COMPACT_VAL];
1462        const N: usize = COMPACT_N;
1463        const DEAD: usize = N / 10;
1464        for i in 0..N {
1465            m.set(&key(i), &val);
1466        }
1467        // A tenth of the keys, which is under the eighth of everything held that
1468        // compaction normally waits for.
1469        for i in 0..DEAD {
1470            m.del(&key(i));
1471        }
1472
1473        assert_eq!(m.compact_step(), None, "not worth collecting");
1474        let free = m.arena().free_segments();
1475        let mut calls = 0;
1476        while m.arena().free_segments() == free {
1477            assert!(
1478                m.compact_hard().is_some(),
1479                "there is a segment holding something dead"
1480            );
1481            calls += 1;
1482            assert!(calls < 1000, "the walk is not getting any further along");
1483        }
1484        // Everything still reads back, which is the thing that matters: the
1485        // records that were live in the segment that came back were moved and
1486        // their index entries were moved with them.
1487        for i in 0..N {
1488            let want = if i < DEAD { None } else { Some(val.clone()) };
1489            assert_eq!(m.get(&key(i)).map(<[u8]>::to_vec), want, "key {i}");
1490        }
1491    }
1492
1493    /// A store with a little dead spread thinly through it collects nothing,
1494    /// however hard it is asked.
1495    ///
1496    /// One key in fifty, so no segment is anywhere near worth emptying. There is
1497    /// no pressure high enough to make copying forty nine bytes to get one back
1498    /// the right move, because a caller under pressure has something cheaper it
1499    /// could be doing with the same effort.
1500    #[test]
1501    fn a_barely_dead_store_collects_nothing_however_hard_it_is_asked() {
1502        let mut m = RawMap::new();
1503        let val = vec![b'z'; COMPACT_VAL];
1504        const N: usize = COMPACT_N;
1505        for i in 0..N {
1506            m.set(&key(i), &val);
1507        }
1508        for i in (0..N).step_by(50) {
1509            m.del(&key(i));
1510        }
1511
1512        assert_eq!(m.compact_step(), None, "not worth collecting");
1513        assert_eq!(m.compact_hard(), None, "fifty bytes moved for one back");
1514    }
1515
1516    /// Compaction says what it walked past and what it had to copy.
1517    ///
1518    /// The two are separate because they cost different things and because the
1519    /// gap between them is the useful part: a walk that steps over a thousand
1520    /// records and copies two got its segment back cheaply, and one that copies
1521    /// nine hundred of them paid nearly the price of the writes twice over.
1522    #[test]
1523    fn compaction_counts_what_it_walked_and_what_it_moved() {
1524        let mut m = RawMap::new();
1525        let val = vec![b'z'; COMPACT_VAL];
1526        const N: usize = COMPACT_N;
1527        for i in 0..N {
1528            m.set(&key(i), &val);
1529        }
1530        assert_eq!(
1531            m.compaction(),
1532            Compaction::default(),
1533            "a load with nothing dead in it has nothing to collect"
1534        );
1535
1536        // Half of them dead, so a walk over a segment should find about half of
1537        // what it steps over still live.
1538        for i in (0..N).step_by(2) {
1539            m.del(&key(i));
1540        }
1541        for _ in 0..200 {
1542            m.compact_step();
1543        }
1544        let c = m.compaction();
1545        assert!(c.walked > 0, "the walk did not step over anything");
1546        assert!(c.moved > 0, "everything it stepped over was dead");
1547        assert!(c.moved < c.walked, "nothing it stepped over was dead");
1548        assert!(
1549            c.bytes >= c.moved * COMPACT_VAL as u64,
1550            "{} records moved and only {} bytes with them",
1551            c.moved,
1552            c.bytes
1553        );
1554
1555        // What a store has spent is not something a flush gives back.
1556        m.clear();
1557        assert_eq!(m.compaction(), c, "the bill was thrown away with the data");
1558    }
1559
1560    /// The budget grows with how far behind the collector is.
1561    ///
1562    /// A store with one segment waiting pays the floor, which is the pause a
1563    /// command can be asked to wait for. One with a queue of them walks a slice
1564    /// per segment in the queue, which is what keeps a pipelined write flood
1565    /// from outrunning one call per batch and leaving the process holding the
1566    /// segments that never got their turn.
1567    #[test]
1568    fn the_budget_scales_with_the_backlog() {
1569        let mut m = RawMap::new();
1570        let val = vec![b'z'; COMPACT_VAL];
1571        const N: usize = COMPACT_N;
1572        for i in 0..N {
1573            m.set(&key(i), &val);
1574        }
1575        assert_eq!(m.budget(), EVAC_FLOOR, "nothing is waiting yet");
1576
1577        for i in 0..N {
1578            m.del(&key(i));
1579        }
1580        let flooded = m.budget();
1581        assert!(
1582            flooded >= EVAC_FLOOR * m.arena().candidate_count(),
1583            "{} segments are waiting and the budget is {flooded}",
1584            m.arena().candidate_count()
1585        );
1586        assert!(
1587            flooded > EVAC_FLOOR,
1588            "every segment is dead and the budget is still the floor"
1589        );
1590        assert!(flooded <= EVAC_CEILING, "walked past a whole segment");
1591    }
1592
1593    /// A segment that is partway through being evacuated is finished before a
1594    /// worse one is started.
1595    ///
1596    /// Writes keep coming while a segment is being walked and they make dead
1597    /// space elsewhere, so the answer to "which segment is worst" moves around
1598    /// underneath a walk that takes thirty calls. Asking it again on every call
1599    /// would let a segment be put down at nine tenths done in favour of one
1600    /// that is slightly worse, and the arena would fill up with segments that
1601    /// are nearly empty and never reclaimed.
1602    ///
1603    /// Here the first quarter of the keyspace is deleted so that the segment at
1604    /// the front is the only candidate, one call starts on it, and then the
1605    /// back half goes too so that another segment ties with it mid walk. The
1606    /// tie goes to the later segment, so a walk that asked again would move to
1607    /// it and leave the first one part done.
1608    #[test]
1609    fn the_segment_in_flight_is_finished_first() {
1610        let mut m = RawMap::new();
1611        let val = vec![b'z'; COMPACT_VAL];
1612        const N: usize = COMPACT_N;
1613        for i in 0..N {
1614            m.set(&key(i), &val);
1615        }
1616        for i in 0..N / 4 {
1617            m.del(&key(i));
1618        }
1619
1620        let free = m.arena().free_segments();
1621        let first = m.arena().worst_candidate().expect("the front is all dead");
1622        m.compact_step().expect("there is a candidate");
1623
1624        for i in N / 2..N {
1625            m.del(&key(i));
1626        }
1627        let worse = m.arena().worst_candidate().expect("the back is all dead");
1628        assert_ne!(worse, first, "the test needs the answer to have moved");
1629
1630        while m.arena().free_segments() == free {
1631            m.compact_step()
1632                .expect("the segment in flight is not finished");
1633        }
1634        assert!(
1635            m.arena().is_free(first),
1636            "the segment that was in flight is not the one that came back"
1637        );
1638        assert!(
1639            !m.arena().is_free(worse),
1640            "the walk moved to the segment that tied with it partway through"
1641        );
1642    }
1643
1644    /// A segment that compaction emptied is bumped through again rather than
1645    /// sitting there holding two megabytes.
1646    #[test]
1647    fn an_emptied_segment_is_used_again() {
1648        let mut m = RawMap::new();
1649        let val = vec![b'z'; COMPACT_VAL];
1650        const N: usize = COMPACT_N;
1651        for i in 0..N {
1652            m.set(&key(i), &val);
1653        }
1654        for i in (0..N).step_by(2) {
1655            m.del(&key(i));
1656        }
1657
1658        let before = m.arena().segment_count();
1659        let seg = m.arena().worst_candidate().expect("half of it is dead");
1660        m.compact_segment(seg);
1661        assert_eq!(
1662            m.arena().free_segments(),
1663            1,
1664            "the segment did not come back"
1665        );
1666
1667        // Write until the free segment has to be taken, and the count is where
1668        // it was rather than one higher.
1669        for i in N..N * 2 {
1670            m.set(&key(i), &val);
1671            if m.arena().free_segments() == 0 {
1672                break;
1673            }
1674        }
1675        assert_eq!(
1676            m.arena().segment_count(),
1677            before,
1678            "asked the system for memory while holding an empty segment"
1679        );
1680    }
1681
1682    #[test]
1683    fn adversarial_keys_that_share_low_bits() {
1684        // Keys chosen so that many land in the same bucket index. The point is
1685        // that overflow chaining and splitting both still work when the hash is
1686        // not being kind.
1687        let mut m = RawMap::new();
1688        let mut inserted = Vec::new();
1689        for i in 0..ADVERSARIAL_N {
1690            let k = i.to_le_bytes().to_vec();
1691            m.set(&k, b"v");
1692            inserted.push(k);
1693        }
1694        for k in &inserted {
1695            assert_eq!(m.get(k), Some(&b"v"[..]));
1696        }
1697        assert_eq!(m.len(), inserted.len());
1698    }
1699
1700    /// Whatever memoizes against this counter is only correct if every way of
1701    /// moving something in the map moves it too. A method that mutates and does
1702    /// not is not a slow memo, it is a wrong answer, so this asserts on the whole
1703    /// `&mut self` surface rather than on the ones that look like they matter.
1704    ///
1705    /// The single exception is pinned by the test below this one, so a method
1706    /// added without a decision about which side it falls on fails here.
1707    #[test]
1708    fn every_way_of_writing_moves_the_counter() {
1709        let mut m = RawMap::new();
1710        let mut last = m.writes();
1711        let mut moved = |m: &RawMap, what: &str| {
1712            assert!(m.writes() > last, "{what} did not move the counter");
1713            last = m.writes();
1714        };
1715
1716        m.set(b"k", b"v");
1717        moved(&m, "set");
1718        m.set_with(
1719            b"k",
1720            1,
1721            |_| {},
1722            |b| {
1723                b[0] = b'w';
1724                false
1725            },
1726        );
1727        moved(&m, "set_with");
1728        m.value_mut(b"k");
1729        moved(&m, "value_mut");
1730        m.value_mut_hashed(RawMap::hash_of(b"k"), b"k");
1731        moved(&m, "value_mut_hashed");
1732        m.compact_step();
1733        moved(&m, "compact_step");
1734        m.compact_segment(0);
1735        moved(&m, "compact_segment");
1736        m.del(b"k");
1737        moved(&m, "del");
1738    }
1739
1740    /// The exception, pinned so that it stays a decision rather than becoming a
1741    /// habit. An in place stamp leaves the counter alone, and everything the
1742    /// caller resolved before it is still right after it.
1743    #[test]
1744    fn sampling_hands_back_real_entries_and_stops_when_told() {
1745        let mut m = RawMap::new();
1746        for i in 0..2000u32 {
1747            m.set(format!("k{i}").as_bytes(), format!("v{i}").as_bytes());
1748        }
1749
1750        // Whatever it hands over is really in the map, key and value together,
1751        // and the address it gives is the address that key resolves to.
1752        let mut count = 0usize;
1753        m.sample(0x1234_5678_9abc_def0, |key, val, addr| {
1754            assert_eq!(m.get(key), Some(val));
1755            assert_eq!(m.find(key), Some(addr));
1756            count += 1;
1757            count < 5
1758        });
1759        assert_eq!(count, 5, "it did not stop when it was told to");
1760
1761        // A caller that never says stop still terminates, because the segment is
1762        // the bound and not the caller.
1763        let mut all = 0usize;
1764        m.sample(0, |_, _, _| {
1765            all += 1;
1766            true
1767        });
1768        assert!(all > 0, "it found nothing in a map of two thousand keys");
1769        assert!(
1770            all < m.len(),
1771            "one segment and not the whole map, got {all} of {}",
1772            m.len()
1773        );
1774    }
1775
1776    #[test]
1777    fn sampling_a_sparse_map_still_finds_something() {
1778        // The case a sampler that looked in one bucket would get wrong. Two keys
1779        // in a map sized for two thousand is sixty two empty buckets for every
1780        // two that are worth looking in.
1781        let mut m = RawMap::new();
1782        for i in 0..2000u32 {
1783            m.set(format!("k{i}").as_bytes(), b"v");
1784        }
1785        for i in 0..1998u32 {
1786            m.del(format!("k{i}").as_bytes());
1787        }
1788        assert_eq!(m.len(), 2);
1789
1790        // Not every draw lands in the segment those two are in, so this is about
1791        // whether it ever finds them rather than whether it always does.
1792        let mut found = 0usize;
1793        for r in 0..200u64 {
1794            m.sample(r.wrapping_mul(0x9e37_79b9_7f4a_7c15), |_, _, _| {
1795                found += 1;
1796                true
1797            });
1798        }
1799        assert!(found > 0, "two hundred draws and it never found either key");
1800    }
1801
1802    #[test]
1803    fn stamping_a_value_in_place_is_not_a_write() {
1804        let mut m = RawMap::new();
1805        m.set(b"k", b"hello");
1806        let addr = m.find(b"k").expect("just stored");
1807        let before = m.writes();
1808
1809        m.value_at_mut(addr)[0] = b'j';
1810
1811        assert_eq!(m.writes(), before, "a stamp counted as a write");
1812        assert_eq!(m.get(b"k"), Some(&b"jello"[..]));
1813        // And the address the caller was holding still means what it meant, which
1814        // is the guarantee the counter would otherwise be asked about.
1815        assert_eq!(m.find(b"k"), Some(addr));
1816        assert_eq!(m.value_at(addr), b"jello");
1817    }
1818
1819    /// `clear` replaces the map with a fresh one, and a fresh one starts at
1820    /// zero. A memo taken at write 3 against a map that went back to 0 and
1821    /// climbed to 3 again would read as still valid on the one call where every
1822    /// key in the map had been thrown away.
1823    #[test]
1824    fn clearing_does_not_send_the_counter_backwards() {
1825        let mut m = RawMap::new();
1826        for i in 0..10u32 {
1827            m.set(&i.to_le_bytes(), b"v");
1828        }
1829        let before = m.writes();
1830        m.clear();
1831        assert!(m.writes() > before, "clear went backwards or stood still");
1832    }
1833
1834    /// Enough keys to have split several times, so a walk crosses segments of
1835    /// different local depths rather than staying inside one.
1836    #[cfg(miri)]
1837    const SCAN_N: usize = 400;
1838    #[cfg(not(miri))]
1839    const SCAN_N: usize = 20_000;
1840
1841    /// How many keys go in between one call of a growing walk and the next.
1842    ///
1843    /// This is the number that decides what that test costs, and not `SCAN_N`,
1844    /// which is why it is its own constant. The walk takes eight keys a call
1845    /// and this puts keys back in behind it, so at 64 the map grows eight times
1846    /// faster than the walk eats it and the loop runs until the directory has
1847    /// doubled its way out from under the whole thing. Shrinking the starting
1848    /// population without shrinking this leaves the ratio where it was and the
1849    /// test still runs for twenty minutes interpreted.
1850    ///
1851    /// Eight is one call's worth, so the map still grows during the walk and
1852    /// the directory still doubles, which is the assertion. What goes away is
1853    /// the number of times over.
1854    #[cfg(miri)]
1855    const GREW_PER_CALL: usize = 8;
1856    #[cfg(not(miri))]
1857    const GREW_PER_CALL: usize = 64;
1858
1859    #[test]
1860    fn a_walk_of_an_empty_map_ends_on_the_first_call() {
1861        let m = RawMap::new();
1862        let mut seen = 0;
1863        let at = m.scan(Cursor::START, 1000, |_, _| seen += 1);
1864        assert_eq!(seen, 0);
1865        assert!(
1866            at.is_end(),
1867            "an empty map took more than one call to finish"
1868        );
1869    }
1870
1871    /// The plain case, and the one every other guarantee is stated against: no
1872    /// writes during the walk, so every key comes back once and no key comes
1873    /// back twice.
1874    #[test]
1875    fn a_quiet_walk_returns_every_key_exactly_once() {
1876        let mut m = RawMap::new();
1877        for i in 0..SCAN_N {
1878            m.set(&key(i), &val(i));
1879        }
1880
1881        let mut counts: HashMap<Vec<u8>, usize> = HashMap::new();
1882        let mut at = Cursor::START;
1883        let mut calls = 0;
1884        loop {
1885            at = m.scan(at, 1, |k, v| {
1886                // Both borrows are shared, so the walk can look the key up
1887                // while it is handing it over. The pair arriving together is
1888                // the point: a bucket walk that read the header of one record
1889                // and the body of the next would still pass a key only check.
1890                assert_eq!(m.get(k), Some(v), "the value came back on the wrong key");
1891                *counts.entry(k.to_vec()).or_default() += 1;
1892            });
1893            calls += 1;
1894            assert!(calls < 1_000_000, "the cursor is not advancing");
1895            if at.is_end() {
1896                break;
1897            }
1898        }
1899
1900        assert_eq!(
1901            counts.len(),
1902            SCAN_N,
1903            "the walk missed keys or invented them"
1904        );
1905        for i in 0..SCAN_N {
1906            assert_eq!(counts.get(&key(i)).copied(), Some(1), "key {i}");
1907        }
1908    }
1909
1910    /// A budget is a floor and not a ceiling, and asking for everything at once
1911    /// is one call.
1912    #[test]
1913    fn a_budget_big_enough_finishes_in_one_call() {
1914        let mut m = RawMap::new();
1915        for i in 0..SCAN_N {
1916            m.set(&key(i), &val(i));
1917        }
1918
1919        let mut seen = 0;
1920        let at = m.scan(Cursor::START, usize::MAX, |_, _| seen += 1);
1921        assert_eq!(seen, SCAN_N);
1922        assert!(at.is_end());
1923    }
1924
1925    /// The guarantee that matters: the map grows underneath the walk, the
1926    /// directory doubles and segments split, and a key that was there the whole
1927    /// time still comes back.
1928    ///
1929    /// Written the way a client uses it, which is a cursor held across calls
1930    /// with other work happening in between, because the failure this is looking
1931    /// for is a cursor that means one thing before a split and another after.
1932    #[test]
1933    fn a_walk_survives_the_map_growing_underneath_it() {
1934        let mut m = RawMap::new();
1935        // The keys that are there throughout. Named apart from the ones added
1936        // during the walk so the two are easy to tell apart in the assertion.
1937        for i in 0..SCAN_N {
1938            m.set(&key(i), &val(i));
1939        }
1940        let depth_before = m.index().global_depth();
1941
1942        let mut seen: HashSet<Vec<u8>> = HashSet::new();
1943        let mut at = Cursor::START;
1944        let mut added = SCAN_N;
1945        loop {
1946            at = m.scan(at, 8, |k, _| {
1947                seen.insert(k.to_vec());
1948            });
1949            if at.is_end() {
1950                break;
1951            }
1952            // Between one call and the next, which is where a client would be.
1953            for _ in 0..GREW_PER_CALL {
1954                m.set(&key(added), &val(added));
1955                added += 1;
1956            }
1957        }
1958
1959        assert!(
1960            m.index().global_depth() > depth_before,
1961            "the directory never doubled, so this test proved nothing"
1962        );
1963        for i in 0..SCAN_N {
1964            assert!(
1965                seen.contains(&key(i)),
1966                "key {i} was there throughout and never came back"
1967            );
1968        }
1969    }
1970
1971    /// Deletes during a walk are the other half of the same guarantee. A key
1972    /// that survives to the end still comes back, whatever happened to its
1973    /// neighbours.
1974    #[test]
1975    fn a_walk_survives_keys_being_deleted_underneath_it() {
1976        let mut m = RawMap::new();
1977        for i in 0..SCAN_N {
1978            m.set(&key(i), &val(i));
1979        }
1980
1981        let mut seen: HashSet<Vec<u8>> = HashSet::new();
1982        let mut at = Cursor::START;
1983        let mut next_gone = 1;
1984        loop {
1985            at = m.scan(at, 8, |k, _| {
1986                seen.insert(k.to_vec());
1987            });
1988            if at.is_end() {
1989                break;
1990            }
1991            // Every odd key goes, a few at a time. The even ones are what the
1992            // assertion is about.
1993            for _ in 0..16 {
1994                if next_gone < SCAN_N {
1995                    m.del(&key(next_gone));
1996                    next_gone += 2;
1997                }
1998            }
1999        }
2000
2001        for i in (0..SCAN_N).step_by(2) {
2002            assert!(
2003                seen.contains(&key(i)),
2004                "key {i} was never deleted and never came back"
2005            );
2006        }
2007    }
2008
2009    /// A cursor names a place in the keyspace and not a place in memory, so a
2010    /// walk started partway through returns everything from there on.
2011    ///
2012    /// The prefix is what says where that is. Starting at prefix `p` resumes in
2013    /// the segment holding `p`, which begins at or before it, so every key whose
2014    /// own prefix is `p` or higher is still ahead of the walk.
2015    #[test]
2016    fn a_walk_that_starts_partway_returns_everything_from_there_on() {
2017        let mut m = RawMap::new();
2018        for i in 0..SCAN_N {
2019            m.set(&key(i), &val(i));
2020        }
2021
2022        let half = 1u64 << (crate::scan::PREFIX_BITS - 1);
2023        let mut seen: HashSet<Vec<u8>> = HashSet::new();
2024        let at = m.scan(Cursor::at(half, 0), usize::MAX, |k, _| {
2025            seen.insert(k.to_vec());
2026        });
2027        assert!(at.is_end());
2028
2029        let mut expected = 0;
2030        for i in 0..SCAN_N {
2031            let k = key(i);
2032            if Cursor::prefix_of(RawMap::hash_of(&k)) >= half {
2033                expected += 1;
2034                assert!(
2035                    seen.contains(&k),
2036                    "key {i} is past the cursor and did not come back"
2037                );
2038            }
2039        }
2040        // Both halves of the keyspace have keys in them, or the assertion above
2041        // is checking nothing.
2042        assert!(
2043            expected > 0 && expected < SCAN_N,
2044            "the split point was degenerate"
2045        );
2046    }
2047}