Skip to main content

yo_kv/
orderkey.rs

1//! Order keys for a list, so that an insert between two elements never has to
2//! move a third one.
3//!
4//! A list element can be addressed two ways. By position, which is what a client
5//! asks for, and by a sort key, which is what an index stores. Addressing by
6//! position is what aki did and it is why `LINSERT` came out at 87 operations a
7//! second against a rival's 6,671: putting an element at position 5 means every
8//! element from 5 upwards gets a new number, and every one of those is a row
9//! rewritten through the index. Addressing by sort key means the new element
10//! gets a key between its two neighbours and nothing else is touched at all.
11//!
12//! The whole difficulty is in the word between. Two neighbours have to have room
13//! between them, and they have to keep having room however many times the same
14//! spot is hammered, because a work queue that always inserts at one priority is
15//! an ordinary thing to build and not a pathology.
16//!
17//! # Why the keys are variable length
18//!
19//! `03` section C, Y19, settles this and this module is the port. The obvious
20//! design is a fixed width key, a float or a fixed size integer, and a midpoint
21//! between two neighbours. It is compact, it compares in one instruction, and it
22//! runs out. A `f64` midpoint between two adjacent representable values has
23//! nowhere left to go after about 52 halvings, and at that point the structure
24//! has to stop in the middle of a command and renumber. That is a correctness
25//! cliff sitting inside a common access pattern, which is the one thing a
26//! storage engine is not allowed to have.
27//!
28//! A variable length key subdivides forever. It costs bytes, and the number that
29//! matters is how many: this allocator buys eight inserts at one spot for each
30//! byte a key grows by, so twenty thousand inserts between the same two
31//! neighbours leaves a key 2,508 bytes long and still strictly ordered. That is
32//! K14, and `a_hammer_at_one_spot_grows_by_a_byte_every_eight_inserts` measures
33//! it rather than trusting it.
34//!
35//! Growth is real and it is bounded by [`ORDER_KEY_MAX`] rather than by nothing.
36//! Past that [`between`] answers `None` and the caller has to renumber the run,
37//! which is a bounded, local, offline job because the growth is local to the
38//! spot being hammered. Nothing in the tree needs it yet at four kilobytes, so
39//! the renumberer is a follow up and not part of this.
40//!
41//! # The invariant
42//!
43//! **No key this module produces ends in a `0x00` byte.**
44//!
45//! It carries the whole thing. If a key could end in `0x00` then a pair like
46//! `[0x41]` and `[0x41, 0x00]` could turn up as neighbours, and there is no key
47//! at all that sorts strictly between those two: anything above `[0x41]` starts
48//! with `[0x41]` and a further byte, and every further byte is at or above
49//! `0x00`. The pair is a dead end and the list wedges, which is exactly the
50//! failure the variable length key was chosen to avoid.
51//!
52//! aki held the invariant for the keys its midpoint descent produced and argued
53//! the end keys were safe because they were all the same width. That argument
54//! has a hole in it, because an eight byte end key for sequence zero really does
55//! end in `0x00`, and a one byte interior key sorting just under it really is
56//! reachable. So [`end`] holds the invariant too, by spending its low byte on a
57//! fixed `0x80` and encoding the sequence in the seven bytes above it. Fifty six
58//! bits of sequence is a hundred years of pushing at ten million a second, and
59//! the invariant becomes something the module guarantees rather than something
60//! the caller has to keep true.
61//!
62//! # The two ways a list grows
63//!
64//! Pushing, which is the hot path, and inserting, which is not.
65//!
66//! A push takes the next sequence number from the end it is pushing to and
67//! encodes it, which is [`Ends`] and [`end`]. No descent, no comparison, no
68//! allocation. The one rule worth stating is that a pop **retires** its sequence
69//! rather than handing it back: the cursors only ever move outward. That is what
70//! makes it safe for a pop to drop its row outside the lock later on, because no
71//! future push can ever aim at the key a pop is in the middle of removing.
72//!
73//! An insert calls [`between`], which is a base 256 midpoint descent: at each
74//! byte it tries to fit a value strictly between the two keys' bytes, and where
75//! they are equal or next to each other it copies and goes one byte deeper.
76//!
77//! # Nothing here allocates
78//!
79//! [`between`] writes into a buffer the caller owns and answers how much of it
80//! it used, so an insert on a command path costs no more than the stack slot the
81//! caller already had. That is Y7 and it is why the signature is shaped the way
82//! it is rather than returning a `Vec<u8>`.
83
84/// The longest key [`between`] will build before it gives up and asks to be
85/// renumbered.
86///
87/// Four kilobytes is thirty two thousand inserts at one spot at the measured
88/// eight per byte, which is well past the twenty thousand K14 asks for and well
89/// under anything a key is compared against in bulk.
90pub const ORDER_KEY_MAX: usize = 4096;
91
92/// How wide the key for a pushed element is, always.
93pub const END_LEN: usize = 8;
94
95/// The lowest sequence [`end`] will encode.
96pub const SEQ_MIN: i64 = -(1 << 55);
97
98/// The highest sequence [`end`] will encode.
99pub const SEQ_MAX: i64 = (1 << 55) - 1;
100
101/// The byte a key gets when it has to be made longer without being made bigger
102/// by much. Halfway up the range, so that the next insert on either side of it
103/// still has somewhere to go.
104const MID: u8 = 0x80;
105
106/// The fifty six bits of an end key that carry the sequence.
107const SEQ_MASK: u64 = (1u64 << 56) - 1;
108
109/// The key for the element at sequence `seq`.
110///
111/// Big endian with the sign bit flipped, which is the encoding that makes a
112/// signed comparison and a bytewise comparison agree, in the seven bytes above a
113/// fixed `0x80`. Panics outside [`SEQ_MIN`]`..=`[`SEQ_MAX`], which is a
114/// programming error rather than a state a list can reach: it is fifty six bits
115/// of pushes to one end.
116#[must_use]
117pub fn end(seq: i64) -> [u8; END_LEN] {
118    assert!(
119        (SEQ_MIN..=SEQ_MAX).contains(&seq),
120        "list order sequence out of range"
121    );
122    // The flip turns the two's complement order into the unsigned one and the
123    // mask drops the sign extension above the fifty six bits that are being
124    // encoded, which would otherwise be shifted straight back into the key. The
125    // shift makes room for the terminal byte.
126    let u = (((seq as u64) ^ (1u64 << 55)) & SEQ_MASK) << 8;
127    let mut key = u.to_be_bytes();
128    key[END_LEN - 1] = MID;
129    key
130}
131
132/// The sequence [`end`] encoded, for a key that came from it.
133///
134/// Answers `None` for a key that did not, which is any key that is not eight
135/// bytes long or does not end in the terminal byte. An interior key can be eight
136/// bytes long, so the terminal check is not decoration.
137#[must_use]
138pub fn seq_of(key: &[u8]) -> Option<i64> {
139    if key.len() != END_LEN || key[END_LEN - 1] != MID {
140        return None;
141    }
142    let mut bytes = [0u8; END_LEN];
143    bytes.copy_from_slice(key);
144    bytes[END_LEN - 1] = 0;
145    let v = (u64::from_be_bytes(bytes) >> 8) ^ (1u64 << 55);
146    // Back up into the sign bit and down again, which is how a fifty six bit
147    // two's complement number is widened to sixty four.
148    Some(((v << 8) as i64) >> 8)
149}
150
151/// The two sequences a list's header keeps, one per end.
152///
153/// A pop does not appear here on purpose. The cursors move outward on a push and
154/// never come back, so a sequence is used once for the life of the list.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct Ends {
157    /// The next sequence a front push will take, counting down.
158    front: i64,
159    /// The next sequence a back push will take, counting up.
160    back: i64,
161}
162
163impl Default for Ends {
164    fn default() -> Ends {
165        Ends::new()
166    }
167}
168
169impl Ends {
170    /// An empty list, with both ends starting from the middle of the range.
171    #[must_use]
172    pub fn new() -> Ends {
173        Ends { front: -1, back: 0 }
174    }
175
176    /// The key for an element pushed onto the front, and the cursor moved down.
177    ///
178    /// `None` once the front has been pushed to fifty six bits of times, which
179    /// is a list that cannot exist rather than a case with a recovery.
180    pub fn push_front(&mut self) -> Option<[u8; END_LEN]> {
181        if self.front < SEQ_MIN {
182            return None;
183        }
184        let key = end(self.front);
185        self.front -= 1;
186        Some(key)
187    }
188
189    /// The key for an element pushed onto the back, and the cursor moved up.
190    pub fn push_back(&mut self) -> Option<[u8; END_LEN]> {
191        if self.back > SEQ_MAX {
192            return None;
193        }
194        let key = end(self.back);
195        self.back += 1;
196        Some(key)
197    }
198}
199
200/// Where the descent has got to.
201///
202/// It starts out pinned to both bounds and comes loose from one of them as soon
203/// as a byte is written that is strictly inside. Once it is loose from a bound
204/// that bound stops mattering, which is what lets the rest of the descent be a
205/// walk down one key rather than two.
206#[derive(Clone, Copy, PartialEq, Eq)]
207enum Descent {
208    /// Equal to both bounds so far.
209    Both,
210    /// Already above `lo`, so only `hi` is still in the way.
211    Upper,
212    /// Already below `hi`, so only `lo` is still in the way.
213    Lower,
214}
215
216/// A key that sorts strictly after `lo` and strictly before `hi`, written into
217/// `out`, with how many bytes of it were used.
218///
219/// `None` when there is no such key, which happens three ways: `lo` is not
220/// strictly below `hi`, the answer would be longer than `out` or longer than
221/// [`ORDER_KEY_MAX`], or the two bounds are a dead end of the shape the
222/// invariant exists to prevent. The last one is unreachable for keys this module
223/// produced and is answered rather than asserted, because `between` is also the
224/// thing an importer runs over keys it did not produce.
225///
226/// The answer never ends in `0x00`, so it can be a bound for the next call.
227pub fn between(lo: &[u8], hi: &[u8], out: &mut [u8]) -> Option<usize> {
228    if lo >= hi {
229        return None;
230    }
231    let cap = out.len().min(ORDER_KEY_MAX);
232    let mut mode = Descent::Both;
233    let mut i = 0;
234    let len = loop {
235        if i >= cap {
236            return None;
237        }
238        match mode {
239            Descent::Both => {
240                // `hi` cannot have run out here. It agrees with `lo` on every
241                // byte so far, so a `hi` that ended here would be a prefix of
242                // `lo` and would sort at or below it.
243                let h = *hi.get(i)?;
244                match lo.get(i) {
245                    // `lo` ran out, so anything written from here on is already
246                    // above it and only `hi` is left to stay under.
247                    None => mode = Descent::Upper,
248                    Some(&l) if h - l >= 2 => {
249                        // Room for a byte strictly between the two, which ends
250                        // the descent. It is at least `l + 1` and so never zero.
251                        out[i] = l + (h - l) / 2;
252                        break i + 1;
253                    }
254                    Some(&l) if h - l == 1 => {
255                        // Copying the lower byte puts us under `hi` for good.
256                        out[i] = l;
257                        i += 1;
258                        mode = Descent::Lower;
259                    }
260                    Some(&l) => {
261                        out[i] = l;
262                        i += 1;
263                    }
264                }
265            }
266            Descent::Upper => {
267                // Under `hi` is all that is left. A byte that ended here would
268                // be the dead end the invariant rules out, so answer rather than
269                // loop.
270                let h = *hi.get(i)?;
271                if h == 0 {
272                    out[i] = 0;
273                    i += 1;
274                } else {
275                    out[i] = h / 2;
276                    break i + 1;
277                }
278            }
279            Descent::Lower => {
280                // Above `lo` is all that is left, and `lo` running out is the
281                // easy case rather than the hard one: one byte past its end is
282                // already past it.
283                match lo.get(i) {
284                    Some(&0xff) => {
285                        out[i] = 0xff;
286                        i += 1;
287                    }
288                    Some(&l) => {
289                        out[i] = l + 1 + (0xff - l) / 2;
290                        break i + 1;
291                    }
292                    None => {
293                        out[i] = MID;
294                        break i + 1;
295                    }
296                }
297            }
298        }
299    };
300    // The one place a zero terminal byte can come out of the descent is halving
301    // a `hi` byte of one. Another byte fixes it, and it stays under `hi` because
302    // the key already differs from `hi` at an earlier byte rather than being a
303    // prefix of it.
304    if out[len - 1] == 0 {
305        if len >= cap {
306            return None;
307        }
308        out[len] = MID;
309        return Some(len + 1);
310    }
311    Some(len)
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    /// A key long enough for anything these tests ask for.
319    fn buf() -> [u8; ORDER_KEY_MAX] {
320        [0u8; ORDER_KEY_MAX]
321    }
322
323    #[test]
324    fn an_end_key_sorts_the_way_the_sequence_does() {
325        let seqs = [
326            SEQ_MIN,
327            SEQ_MIN + 1,
328            -1_000_000,
329            -256,
330            -1,
331            0,
332            1,
333            255,
334            256,
335            1_000_000,
336            SEQ_MAX - 1,
337            SEQ_MAX,
338        ];
339        for pair in seqs.windows(2) {
340            let (a, b) = (end(pair[0]), end(pair[1]));
341            assert!(a < b, "{:?} should sort under {:?}", pair[0], pair[1]);
342        }
343        for seq in seqs {
344            let key = end(seq);
345            assert_eq!(seq_of(&key), Some(seq));
346            assert_ne!(key[END_LEN - 1], 0, "an end key must not end in a zero");
347        }
348    }
349
350    #[test]
351    fn a_key_that_did_not_come_from_end_is_not_read_as_one() {
352        assert_eq!(seq_of(b""), None);
353        assert_eq!(seq_of(b"1234567"), None);
354        assert_eq!(seq_of(b"123456789"), None);
355        // Eight bytes and the wrong terminal, which an interior key can be.
356        assert_eq!(seq_of(&[0x80, 0, 0, 0, 0, 0, 0, 0x40]), None);
357    }
358
359    #[test]
360    fn the_ends_only_ever_move_outward() {
361        let mut ends = Ends::new();
362        let a = ends.push_back().unwrap();
363        let b = ends.push_back().unwrap();
364        let x = ends.push_front().unwrap();
365        let y = ends.push_front().unwrap();
366        assert!(y < x && x < a && a < b);
367        // A pop is not a method here, so the next push after one lands past
368        // everything that has ever been pushed rather than on top of it.
369        let c = ends.push_back().unwrap();
370        assert!(b < c);
371    }
372
373    #[test]
374    fn a_key_between_two_lands_between_them() {
375        let mut out = buf();
376        let cases: &[(&[u8], &[u8])] = &[
377            (b"a", b"c"),
378            (b"a", b"b"),
379            (b"aa", b"ab"),
380            (b"a", b"aa"),
381            (&[0x00], &[0xff]),
382            (&[0x41], &[0x41, 0x01, 0x80]),
383            (&[0xff, 0xff, 0x80], &[0xff, 0xff, 0x81]),
384            (&end(0), &end(1)),
385            (&end(-1), &end(0)),
386            (&end(SEQ_MIN), &end(SEQ_MAX)),
387        ];
388        for &(lo, hi) in cases {
389            let n = between(lo, hi, &mut out).expect("there is room between these");
390            let key = &out[..n];
391            assert!(lo < key, "{key:?} should sort above {lo:?}");
392            assert!(key < hi, "{key:?} should sort under {hi:?}");
393            assert_ne!(key[n - 1], 0, "{key:?} must not end in a zero");
394        }
395    }
396
397    #[test]
398    fn there_is_nothing_between_a_key_and_itself_or_a_pair_the_wrong_way_round() {
399        let mut out = buf();
400        assert_eq!(between(b"a", b"a", &mut out), None);
401        assert_eq!(between(b"b", b"a", &mut out), None);
402        assert_eq!(between(b"aa", b"a", &mut out), None);
403        // The dead end the invariant exists to keep out of a live list. It is
404        // answered rather than looped on.
405        assert_eq!(between(b"a", b"a\x00", &mut out), None);
406        assert_eq!(between(b"a", b"a\x00\x00", &mut out), None);
407    }
408
409    #[test]
410    fn a_key_that_will_not_fit_is_refused_rather_than_truncated() {
411        let mut small = [0u8; 2];
412        assert_eq!(between(&end(0), &end(1), &mut small), None);
413        let mut one = [0u8; 1];
414        assert_eq!(between(b"a", b"c", &mut one), Some(1));
415        assert_eq!(one[0], b'b');
416    }
417
418    /// Every ordered pair of short strings over a small alphabet, which is the
419    /// only way to be sure the three ways the descent can end are all right.
420    #[test]
421    fn every_pair_that_honours_the_invariant_has_a_key_between_it() {
422        let alphabet = [0x00u8, 0x01, 0x02, 0x7f, 0x80, 0xfe, 0xff];
423        let mut keys: Vec<Vec<u8>> = Vec::new();
424        for len in 1..=3 {
425            let mut key = vec![0u8; len];
426            let mut counter = 0usize;
427            let total = alphabet.len().pow(u32::try_from(len).unwrap());
428            while counter < total {
429                let mut n = counter;
430                for byte in key.iter_mut().take(len) {
431                    *byte = alphabet[n % alphabet.len()];
432                    n /= alphabet.len();
433                }
434                // The invariant is what the descent is allowed to assume, so a
435                // pair that breaks it is not a pair a list can present.
436                if key[len - 1] != 0 {
437                    keys.push(key.clone());
438                }
439                counter += 1;
440            }
441        }
442        keys.sort();
443        let mut out = buf();
444        let mut pairs = 0;
445        for (i, lo) in keys.iter().enumerate() {
446            for hi in &keys[i + 1..] {
447                let n = between(lo, hi, &mut out).expect("the invariant leaves room");
448                let key = &out[..n];
449                assert!(lo.as_slice() < key, "{key:?} above {lo:?}");
450                assert!(key < hi.as_slice(), "{key:?} under {hi:?}");
451                assert_ne!(key[n - 1], 0);
452                pairs += 1;
453            }
454        }
455        assert_eq!(pairs, 58_311, "every ordered pair of the 342 legal keys");
456    }
457
458    /// K14, measured. Twenty thousand inserts at one spot, every one of them
459    /// between the fixed left neighbour and the key the last insert produced,
460    /// which is the adversary that a fixed precision key dies to at about 52.
461    #[test]
462    fn a_hammer_at_one_spot_grows_by_a_byte_every_eight_inserts() {
463        const N: usize = 20_000;
464        let lo = end(0);
465        let mut hi = end(1).to_vec();
466        let mut out = buf();
467        let mut deepest = 0;
468        for i in 0..N {
469            let n = between(&lo, &hi, &mut out)
470                .unwrap_or_else(|| panic!("wedged after {i} inserts, which is what scheme A does"));
471            let key = &out[..n];
472            assert!(lo.as_slice() < key && key < hi.as_slice());
473            assert_ne!(key[n - 1], 0);
474            deepest = deepest.max(n);
475            hi.clear();
476            hi.extend_from_slice(key);
477        }
478        // Eight halvings to a byte, on top of the eight the end key started at.
479        let grown = deepest - END_LEN;
480        let per_byte = N as f64 / grown as f64;
481        assert!(
482            per_byte >= 8.0,
483            "{per_byte} inserts per byte, K14 asks for 8.0"
484        );
485        assert_eq!(deepest, 2508, "aki's number, to the byte");
486    }
487
488    /// The same hammer from the other side, because the descent takes a
489    /// different branch going up than it does going down.
490    #[test]
491    fn a_hammer_under_the_upper_neighbour_grows_at_the_same_rate() {
492        const N: usize = 20_000;
493        let hi = end(1);
494        let mut lo = end(0).to_vec();
495        let mut out = buf();
496        let mut deepest = 0;
497        for _ in 0..N {
498            let n = between(&lo, &hi, &mut out).expect("a variable key does not wedge");
499            let key = &out[..n];
500            assert!(lo.as_slice() < key && key < hi.as_slice());
501            assert_ne!(key[n - 1], 0);
502            deepest = deepest.max(n);
503            lo.clear();
504            lo.extend_from_slice(key);
505        }
506        let per_byte = N as f64 / (deepest - END_LEN) as f64;
507        assert!(
508            per_byte >= 8.0,
509            "{per_byte} inserts per byte, K14 asks for 8.0"
510        );
511    }
512
513    /// The faithful walk: start from two real end keys and only ever insert
514    /// between real neighbours, so every bound is a key the allocator itself
515    /// produced. Nothing here is allowed to wedge and the order is checked whole
516    /// rather than pairwise.
517    #[test]
518    fn a_list_built_only_out_of_its_own_keys_stays_ordered() {
519        let mut keys: Vec<Vec<u8>> = vec![end(0).to_vec(), end(1).to_vec()];
520        let mut out = buf();
521        // A cheap deterministic spread, so the inserts land all over the list
522        // rather than at one spot.
523        let mut seed = 0x2545_f491_4f6c_dd1du64;
524        for _ in 0..5_000 {
525            seed ^= seed << 13;
526            seed ^= seed >> 7;
527            seed ^= seed << 17;
528            let at = (seed % (keys.len() as u64 - 1)) as usize;
529            let n = between(&keys[at], &keys[at + 1], &mut out).expect("no wedge");
530            keys.insert(at + 1, out[..n].to_vec());
531        }
532        assert!(keys.windows(2).all(|w| w[0] < w[1]), "the list is ordered");
533        assert!(keys.iter().all(|k| *k.last().unwrap() != 0), "invariant");
534        assert_eq!(keys.len(), 5_002);
535    }
536}