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}