Skip to main content

yo_index/
scan.rs

1//! Walking the whole index while it is being written to.
2//!
3//! `KEYS` can walk the index in one go and be done with it. `SCAN` cannot: it
4//! hands the client a number, the client goes away and does something else, and
5//! then it comes back and expects the walk to carry on. Between those two calls
6//! the index may have doubled its directory and split any number of segments,
7//! and the promise `SCAN` makes has to survive all of it.
8//!
9//! The promise is one sided and worth stating exactly, because half of what
10//! makes it implementable is what it does not say. A key that is there for the
11//! whole walk is returned at least once. A key added or removed partway through
12//! may or may not appear. A key may appear twice. That is Redis's contract and
13//! it is the contract here.
14//!
15//! # The prefix does not move
16//!
17//! Redis walks a power of two table and doubles it by adding a bit at the top of
18//! the bucket index, so a bucket that was `i` becomes `i` and `i + n`. That is
19//! why its cursor counts in reverse binary: it is the only order in which the
20//! two halves of a split bucket stay next to each other.
21//!
22//! This index doubles the other way round. [`Index::dir_index`] takes the top
23//! `global_depth` bits below the tag, and doubling the directory copies each
24//! entry to two neighbouring slots, so a directory index `d` becomes `2d` and
25//! `2d + 1`. A bit is added at the bottom, not the top.
26//!
27//! That makes the cursor simple, because there is a number that does not move at
28//! all. Take the full 48 bits the directory could ever use, left aligned, and
29//! call it the prefix. It is a function of the key's hash and nothing else, so
30//! it is the same number before a doubling and after one, and the directory
31//! index at any depth is just the top `global_depth` bits of it. Walk in
32//! increasing prefix order and the boundary between what has been seen and what
33//! has not is a number that means the same thing in every version of the index
34//! this walk will ever see.
35//!
36//! # What a split does
37//!
38//! A segment covers a contiguous run of prefixes. Splitting it cuts that run in
39//! half and gives the top half to a new segment, which is a change to where the
40//! keys live and not to any key's prefix.
41//!
42//! If the cursor is partway through a segment when it splits, the walk resumes
43//! in the half that still holds the cursor's prefix, finishes it, and then
44//! starts the other half from its first bucket. Keys in the top half that had
45//! already been returned are returned again. That is the duplicate the contract
46//! allows, and it is the price of never having to stop the world.
47//!
48//! # The shape of the number
49//!
50//! ```text
51//!  63                              16 15 14 13      6 5        0
52//! +----------------------------------+-----+---------+----------+
53//! |         directory prefix         |  0  | stripe  |  bucket  |
54//! +----------------------------------+-----+---------+----------+
55//! ```
56//!
57//! The bucket within a segment comes off the bottom of the hash and the prefix
58//! comes off the top, so the two never overlap and a split cannot move a key
59//! from one bucket to another. That leaves ten bits in the middle, and eight of
60//! them carry the stripe number.
61//!
62//! The stripe means nothing to the index. A database above this crate is
63//! several indexes and a walk of it is a walk of each one in turn, so the
64//! cursor a client holds has to say which one it had got to as well as where it
65//! had got to in it. This is where that number lives, because the bit budget is
66//! shared and a field handed out in two places is a field that collides. Two
67//! bits are left over and a segment is 64 buckets today, so the bucket field
68//! can double twice without any cursor a client is holding meaning something
69//! different.
70//!
71//! Zero is both the start and the end, which is Redis's convention and is not an
72//! ambiguity in practice: a walk that has finished says zero, and a client that
73//! says zero is starting a new one.
74
75/// How far a scan has got, and the number the client holds between calls.
76///
77/// It is a position in the keyspace and not a position in memory. Two calls a
78/// week apart with the same cursor resume at the same place, even if every
79/// segment in the index has split in between.
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
81pub struct Cursor(u64);
82
83/// Bits the directory can ever use, which is the index's `MAX_DEPTH`.
84pub(crate) const PREFIX_BITS: u32 = 48;
85
86/// Where the prefix sits in the cursor, leaving room below it for the bucket.
87pub(crate) const PREFIX_SHIFT: u32 = 16;
88
89/// Bits of bucket index, which is `log2` of [`SEGMENT_BUCKETS`](super::SEGMENT_BUCKETS).
90pub(crate) const BUCKET_BITS: u32 = 6;
91
92/// Bits of stripe number, which is how many stripes a database can be cut into.
93pub const STRIPE_BITS: u32 = 8;
94
95/// Where the stripe number sits, which is directly above the bucket.
96const STRIPE_SHIFT: u32 = BUCKET_BITS;
97
98/// The stripe number on its own, in place.
99const STRIPE_MASK: u64 = ((1 << STRIPE_BITS) - 1) << STRIPE_SHIFT;
100
101impl Cursor {
102    /// The start of a walk, and the same value as the end of one.
103    pub const START: Cursor = Cursor(0);
104
105    /// The cursor a client sent, whatever it sent.
106    ///
107    /// Any number is a valid cursor. A made up one resumes somewhere arbitrary
108    /// and answers keys from there, which is what Redis does and is the only
109    /// behaviour that does not require the server to remember every cursor it
110    /// has ever handed out.
111    #[must_use]
112    pub const fn from_raw(raw: u64) -> Cursor {
113        Cursor(raw)
114    }
115
116    /// The number to hand the client.
117    #[must_use]
118    pub const fn raw(self) -> u64 {
119        self.0
120    }
121
122    /// Whether the walk is over.
123    #[must_use]
124    pub const fn is_end(self) -> bool {
125        self.0 == 0
126    }
127
128    /// Which index of several this cursor was walking.
129    ///
130    /// The index itself never reads this and never writes it. It is here
131    /// because the number a client holds has one bit budget and the layer above
132    /// needs a field in it, and a field handed out in two places is a field
133    /// that collides. See the module docs for the rest of the shape.
134    #[must_use]
135    pub const fn stripe(self) -> usize {
136        ((self.0 & STRIPE_MASK) >> STRIPE_SHIFT) as usize
137    }
138
139    /// The same cursor with a stripe number written into it.
140    ///
141    /// A number too big for the field is masked down rather than refused, and
142    /// nothing can hand one over: the field holds
143    /// [`STRIPE_BITS`] bits and that is what the width above it is capped at.
144    #[must_use]
145    pub const fn with_stripe(self, stripe: usize) -> Cursor {
146        Cursor((self.0 & !STRIPE_MASK) | (((stripe as u64) << STRIPE_SHIFT) & STRIPE_MASK))
147    }
148
149    /// The same cursor with the stripe number taken back out.
150    ///
151    /// What the index is handed, so that a walk of one stripe cannot tell it is
152    /// one of several. Which also means that a cursor holding nothing but a
153    /// stripe number is the start of that stripe.
154    #[must_use]
155    pub const fn without_stripe(self) -> Cursor {
156        Cursor(self.0 & !STRIPE_MASK)
157    }
158
159    /// The prefix half, which says which segment.
160    #[must_use]
161    pub(crate) const fn prefix(self) -> u64 {
162        (self.0 >> PREFIX_SHIFT) & ((1 << PREFIX_BITS) - 1)
163    }
164
165    /// The bucket half, which says where in the segment.
166    #[must_use]
167    pub(crate) const fn bucket(self) -> usize {
168        (self.0 & ((1 << BUCKET_BITS) - 1)) as usize
169    }
170
171    /// Put the two halves back together.
172    ///
173    /// A prefix that has run off the top of its 48 bits means the last segment
174    /// is done, which is the end of the walk and therefore zero.
175    #[must_use]
176    pub(crate) const fn at(prefix: u64, bucket: usize) -> Cursor {
177        if prefix >= (1 << PREFIX_BITS) {
178            return Cursor::START;
179        }
180        Cursor((prefix << PREFIX_SHIFT) | (bucket as u64 & ((1 << BUCKET_BITS) - 1)))
181    }
182
183    /// The prefix of a key, which is the part of its hash the directory reads.
184    ///
185    /// Left aligned into the full 48 bits rather than into `global_depth` of
186    /// them, which is the whole trick: this number is the same before a doubling
187    /// and after one.
188    ///
189    /// Only the tests need this. The walk itself never goes from a key to a
190    /// cursor, it only ever goes forward from the cursor it was handed, so this
191    /// is the statement of the invariant rather than a step in the code.
192    #[cfg(test)]
193    #[must_use]
194    pub(crate) const fn prefix_of(hash: u64) -> u64 {
195        (hash >> (super::index::DIR_BITS - PREFIX_BITS)) & ((1 << PREFIX_BITS) - 1)
196    }
197}
198
199impl From<u64> for Cursor {
200    fn from(raw: u64) -> Cursor {
201        Cursor::from_raw(raw)
202    }
203}
204
205impl From<Cursor> for u64 {
206    fn from(c: Cursor) -> u64 {
207        c.raw()
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn the_two_halves_survive_the_round_trip() {
217        for prefix in [0u64, 1, 255, (1 << PREFIX_BITS) - 1] {
218            for bucket in [0usize, 1, 63] {
219                let c = Cursor::at(prefix, bucket);
220                assert_eq!(c.prefix(), prefix, "prefix {prefix} bucket {bucket}");
221                assert_eq!(c.bucket(), bucket, "prefix {prefix} bucket {bucket}");
222            }
223        }
224    }
225
226    #[test]
227    fn a_prefix_past_the_end_is_the_end() {
228        assert_eq!(Cursor::at(1 << PREFIX_BITS, 0), Cursor::START);
229        assert!(Cursor::at(1 << PREFIX_BITS, 7).is_end());
230        // And zero with a bucket in it is not the end, because a walk that has
231        // done one bucket of the first segment has not finished.
232        assert!(!Cursor::at(0, 1).is_end());
233    }
234
235    #[test]
236    fn a_prefix_is_the_directory_index_at_every_depth() {
237        // What the index does at depth g, spelled out here rather than reached
238        // through a private method, so the two are checked against each other.
239        let hash = 0x1234_5678_9abc_def0u64;
240        let prefix = Cursor::prefix_of(hash);
241        for g in 1..=16u32 {
242            let dir_bits = super::super::index::DIR_BITS;
243            let want = (hash >> (dir_bits - g)) & ((1 << g) - 1);
244            assert_eq!(prefix >> (PREFIX_BITS - g), want, "depth {g}");
245        }
246    }
247
248    #[test]
249    fn a_stripe_number_sits_beside_the_other_two_halves() {
250        let c = Cursor::at(0x1234, 42).with_stripe(200);
251        assert_eq!(c.prefix(), 0x1234);
252        assert_eq!(c.bucket(), 42);
253        assert_eq!(c.stripe(), 200);
254        assert_eq!(c.without_stripe(), Cursor::at(0x1234, 42));
255        // And a walk of one stripe is handed a cursor that has never heard of
256        // stripes, which for the first stripe is the ordinary start.
257        assert_eq!(Cursor::START.with_stripe(0), Cursor::START);
258        assert!(!Cursor::START.with_stripe(1).is_end());
259        assert!(Cursor::START.with_stripe(1).without_stripe().is_end());
260    }
261
262    #[test]
263    fn the_cursor_a_client_holds_is_just_a_number() {
264        let c = Cursor::from_raw(0x0001_0000_0000_002a);
265        assert_eq!(u64::from(c), 0x0001_0000_0000_002a);
266        assert_eq!(Cursor::from(7u64).bucket(), 7);
267    }
268}