TLA Line data Source code
1 : //!
2 : //! Simple on-disk B-tree implementation
3 : //!
4 : //! This is used as the index structure within image and delta layers
5 : //!
6 : //! Features:
7 : //! - Fixed-width keys
8 : //! - Fixed-width values (VALUE_SZ)
9 : //! - The tree is created in a bulk operation. Insert/deletion after creation
10 : //! is not supported
11 : //! - page-oriented
12 : //!
13 : //! TODO:
14 : //! - maybe something like an Adaptive Radix Tree would be more efficient?
15 : //! - the values stored by image and delta layers are offsets into the file,
16 : //! and they are in monotonically increasing order. Prefix compression would
17 : //! be very useful for them, too.
18 : //! - An Iterator interface would be more convenient for the callers than the
19 : //! 'visit' function
20 : //!
21 : use byteorder::{ReadBytesExt, BE};
22 : use bytes::{BufMut, Bytes, BytesMut};
23 : use either::Either;
24 : use hex;
25 : use std::{cmp::Ordering, io, result};
26 : use thiserror::Error;
27 : use tracing::error;
28 :
29 : use crate::{
30 : context::{DownloadBehavior, RequestContext},
31 : task_mgr::TaskKind,
32 : tenant::block_io::{BlockReader, BlockWriter},
33 : };
34 :
35 : // The maximum size of a value stored in the B-tree. 5 bytes is enough currently.
36 : pub const VALUE_SZ: usize = 5;
37 : pub const MAX_VALUE: u64 = 0x007f_ffff_ffff;
38 :
39 : #[allow(dead_code)]
40 : pub const PAGE_SZ: usize = 8192;
41 :
42 UBC 0 : #[derive(Clone, Copy, Debug)]
43 : struct Value([u8; VALUE_SZ]);
44 :
45 : impl Value {
46 CBC 73339725 : fn from_slice(slice: &[u8]) -> Value {
47 73339725 : let mut b = [0u8; VALUE_SZ];
48 73339725 : b.copy_from_slice(slice);
49 73339725 : Value(b)
50 73339725 : }
51 :
52 46827128 : fn from_u64(x: u64) -> Value {
53 46827128 : assert!(x <= 0x007f_ffff_ffff);
54 46827128 : Value([
55 46827128 : (x >> 32) as u8,
56 46827128 : (x >> 24) as u8,
57 46827128 : (x >> 16) as u8,
58 46827128 : (x >> 8) as u8,
59 46827128 : x as u8,
60 46827128 : ])
61 46827128 : }
62 :
63 90524 : fn from_blknum(x: u32) -> Value {
64 90524 : Value([
65 90524 : 0x80,
66 90524 : (x >> 24) as u8,
67 90524 : (x >> 16) as u8,
68 90524 : (x >> 8) as u8,
69 90524 : x as u8,
70 90524 : ])
71 90524 : }
72 :
73 : #[allow(dead_code)]
74 UBC 0 : fn is_offset(self) -> bool {
75 0 : self.0[0] & 0x80 != 0
76 0 : }
77 :
78 CBC 67129457 : fn to_u64(self) -> u64 {
79 67129457 : let b = &self.0;
80 67129457 : (b[0] as u64) << 32
81 67129457 : | (b[1] as u64) << 24
82 67129457 : | (b[2] as u64) << 16
83 67129457 : | (b[3] as u64) << 8
84 67129457 : | b[4] as u64
85 67129457 : }
86 :
87 6207256 : fn to_blknum(self) -> u32 {
88 6207256 : let b = &self.0;
89 6207256 : assert!(b[0] == 0x80);
90 6207256 : (b[1] as u32) << 24 | (b[2] as u32) << 16 | (b[3] as u32) << 8 | b[4] as u32
91 6207256 : }
92 : }
93 :
94 UBC 0 : #[derive(Error, Debug)]
95 : pub enum DiskBtreeError {
96 : #[error("Attempt to append a value that is too large {0} > {}", MAX_VALUE)]
97 : AppendOverflow(u64),
98 :
99 : #[error("Unsorted input: key {key:?} is <= last_key {last_key:?}")]
100 : UnsortedInput { key: Box<[u8]>, last_key: Box<[u8]> },
101 :
102 : #[error("Could not push to new leaf node")]
103 : FailedToPushToNewLeafNode,
104 :
105 : #[error("IoError: {0}")]
106 : Io(#[from] io::Error),
107 : }
108 :
109 : pub type Result<T> = result::Result<T, DiskBtreeError>;
110 :
111 : /// This is the on-disk representation.
112 : struct OnDiskNode<'a, const L: usize> {
113 : // Fixed-width fields
114 : num_children: u16,
115 : level: u8,
116 : prefix_len: u8,
117 : suffix_len: u8,
118 :
119 : // Variable-length fields. These are stored on-disk after the fixed-width
120 : // fields, in this order. In the in-memory representation, these point to
121 : // the right parts in the page buffer.
122 : prefix: &'a [u8],
123 : keys: &'a [u8],
124 : values: &'a [u8],
125 : }
126 :
127 : impl<'a, const L: usize> OnDiskNode<'a, L> {
128 : ///
129 : /// Interpret a PAGE_SZ page as a node.
130 : ///
131 CBC 22074562 : fn deparse(buf: &[u8]) -> Result<OnDiskNode<L>> {
132 22074562 : let mut cursor = std::io::Cursor::new(buf);
133 22074562 : let num_children = cursor.read_u16::<BE>()?;
134 22074562 : let level = cursor.read_u8()?;
135 22074562 : let prefix_len = cursor.read_u8()?;
136 22074562 : let suffix_len = cursor.read_u8()?;
137 :
138 22074562 : let mut off = cursor.position();
139 22074562 : let prefix_off = off as usize;
140 22074562 : off += prefix_len as u64;
141 22074562 :
142 22074562 : let keys_off = off as usize;
143 22074562 : let keys_len = num_children as usize * suffix_len as usize;
144 22074562 : off += keys_len as u64;
145 22074562 :
146 22074562 : let values_off = off as usize;
147 22074562 : let values_len = num_children as usize * VALUE_SZ;
148 22074562 : //off += values_len as u64;
149 22074562 :
150 22074562 : let prefix = &buf[prefix_off..prefix_off + prefix_len as usize];
151 22074562 : let keys = &buf[keys_off..keys_off + keys_len];
152 22074562 : let values = &buf[values_off..values_off + values_len];
153 22074562 :
154 22074562 : Ok(OnDiskNode {
155 22074562 : num_children,
156 22074562 : level,
157 22074562 : prefix_len,
158 22074562 : suffix_len,
159 22074562 : prefix,
160 22074562 : keys,
161 22074562 : values,
162 22074562 : })
163 22074562 : }
164 :
165 : ///
166 : /// Read a value at 'idx'
167 : ///
168 73339743 : fn value(&self, idx: usize) -> Value {
169 73339743 : let value_off = idx * VALUE_SZ;
170 73339743 : let value_slice = &self.values[value_off..value_off + VALUE_SZ];
171 73339743 : Value::from_slice(value_slice)
172 73339743 : }
173 :
174 21871490 : fn binary_search(
175 21871490 : &self,
176 21871490 : search_key: &[u8; L],
177 21871490 : keybuf: &mut [u8],
178 21871490 : ) -> result::Result<usize, usize> {
179 21871490 : let mut size = self.num_children as usize;
180 21871490 : let mut low = 0;
181 21871490 : let mut high = size;
182 138745839 : while low < high {
183 117458128 : let mid = low + size / 2;
184 117458128 :
185 117458128 : let key_off = mid * self.suffix_len as usize;
186 117458128 : let suffix = &self.keys[key_off..key_off + self.suffix_len as usize];
187 117458128 : // Does this match?
188 117458128 : keybuf[self.prefix_len as usize..].copy_from_slice(suffix);
189 117458128 :
190 117458128 : let cmp = keybuf[..].cmp(search_key);
191 117458128 :
192 117458128 : if cmp == Ordering::Less {
193 36959295 : low = mid + 1;
194 80498833 : } else if cmp == Ordering::Greater {
195 79915054 : high = mid;
196 79915054 : } else {
197 583779 : return Ok(mid);
198 : }
199 116874349 : size = high - low;
200 : }
201 21287711 : Err(low)
202 21871490 : }
203 : }
204 :
205 : ///
206 : /// Public reader object, to search the tree.
207 : ///
208 : pub struct DiskBtreeReader<R, const L: usize>
209 : where
210 : R: BlockReader,
211 : {
212 : start_blk: u32,
213 : root_blk: u32,
214 : reader: R,
215 : }
216 :
217 21871458 : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
218 : pub enum VisitDirection {
219 : Forwards,
220 : Backwards,
221 : }
222 :
223 : impl<R, const L: usize> DiskBtreeReader<R, L>
224 : where
225 : R: BlockReader,
226 : {
227 15458536 : pub fn new(start_blk: u32, root_blk: u32, reader: R) -> Self {
228 15458536 : DiskBtreeReader {
229 15458536 : start_blk,
230 15458536 : root_blk,
231 15458536 : reader,
232 15458536 : }
233 15458536 : }
234 :
235 : ///
236 : /// Read the value for given key. Returns the value, or None if it doesn't exist.
237 : ///
238 653772 : pub async fn get(&self, search_key: &[u8; L], ctx: &RequestContext) -> Result<Option<u64>> {
239 653772 : let mut result: Option<u64> = None;
240 653772 : self.visit(
241 653772 : search_key,
242 653772 : VisitDirection::Forwards,
243 653772 : |key, value| {
244 553760 : if key == search_key {
245 552755 : result = Some(value);
246 552755 : }
247 553760 : false
248 653772 : },
249 653772 : ctx,
250 653772 : )
251 6110 : .await?;
252 653772 : Ok(result)
253 653772 : }
254 :
255 : ///
256 : /// Scan the tree, starting from 'search_key', in the given direction. 'visitor'
257 : /// will be called for every key >= 'search_key' (or <= 'search_key', if scanning
258 : /// backwards)
259 : ///
260 15664229 : pub async fn visit<V>(
261 15664229 : &self,
262 15664229 : search_key: &[u8; L],
263 15664229 : dir: VisitDirection,
264 15664229 : mut visitor: V,
265 15664229 : ctx: &RequestContext,
266 15664229 : ) -> Result<bool>
267 15664229 : where
268 15664229 : V: FnMut(&[u8], u64) -> bool,
269 15664229 : {
270 15664229 : let mut stack = Vec::new();
271 15664229 : stack.push((self.root_blk, None));
272 15664229 : let block_cursor = self.reader.block_cursor();
273 22178410 : while let Some((node_blknum, opt_iter)) = stack.pop() {
274 : // Locate the node.
275 22071546 : let node_buf = block_cursor
276 22071546 : .read_blk(self.start_blk + node_blknum, ctx)
277 190423 : .await?;
278 :
279 22071546 : let node = OnDiskNode::deparse(node_buf.as_ref())?;
280 22071546 : let prefix_len = node.prefix_len as usize;
281 22071546 : let suffix_len = node.suffix_len as usize;
282 22071546 :
283 22071546 : assert!(node.num_children > 0);
284 :
285 22071546 : let mut keybuf = Vec::new();
286 22071546 : keybuf.extend(node.prefix);
287 22071546 : keybuf.resize(prefix_len + suffix_len, 0);
288 :
289 22071546 : let mut iter = if let Some(iter) = opt_iter {
290 200056 : iter
291 21871490 : } else if dir == VisitDirection::Forwards {
292 : // Locate the first match
293 1008749 : let idx = match node.binary_search(search_key, keybuf.as_mut_slice()) {
294 554113 : Ok(idx) => idx,
295 454636 : Err(idx) => {
296 454636 : if node.level == 0 {
297 : // Imagine that the node contains the following keys:
298 : //
299 : // 1
300 : // 3 <-- idx
301 : // 5
302 : //
303 : // If the search key is '2' and there is exact match,
304 : // the binary search would return the index of key
305 : // '3'. That's cool, '3' is the first key to return.
306 135766 : idx
307 : } else {
308 : // This is an internal page, so each key represents a lower
309 : // bound for what's in the child page. If there is no exact
310 : // match, we have to return the *previous* entry.
311 : //
312 : // 1 <-- return this
313 : // 3 <-- idx
314 : // 5
315 318870 : idx.saturating_sub(1)
316 : }
317 : }
318 : };
319 1008749 : Either::Left(idx..node.num_children.into())
320 : } else {
321 20862741 : let idx = match node.binary_search(search_key, keybuf.as_mut_slice()) {
322 29666 : Ok(idx) => {
323 29666 : // Exact match. That's the first entry to return, and walk
324 29666 : // backwards from there.
325 29666 : idx
326 : }
327 20833075 : Err(idx) => {
328 : // No exact match. The binary search returned the index of the
329 : // first key that's > search_key. Back off by one, and walk
330 : // backwards from there.
331 20833075 : if let Some(idx) = idx.checked_sub(1) {
332 12055482 : idx
333 : } else {
334 8777593 : return Ok(false);
335 : }
336 : }
337 : };
338 12085148 : Either::Right((0..=idx).rev())
339 : };
340 :
341 : // idx points to the first match now. Keep going from there
342 73643647 : while let Some(idx) = iter.next() {
343 73336727 : let key_off = idx * suffix_len;
344 73336727 : let suffix = &node.keys[key_off..key_off + suffix_len];
345 73336727 : keybuf[prefix_len..].copy_from_slice(suffix);
346 73336727 : let value = node.value(idx);
347 73336727 : #[allow(clippy::collapsible_if)]
348 73336727 : if node.level == 0 {
349 : // leaf
350 67129466 : if !visitor(&keybuf, value.to_u64()) {
351 6779771 : return Ok(false);
352 60349694 : }
353 : } else {
354 6207261 : stack.push((node_blknum, Some(iter)));
355 6207261 : stack.push((value.to_blknum(), None));
356 6207261 : break;
357 : }
358 : }
359 : }
360 106864 : Ok(true)
361 15664228 : }
362 :
363 : #[allow(dead_code)]
364 5 : pub async fn dump(&self) -> Result<()> {
365 5 : let mut stack = Vec::new();
366 5 : let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
367 5 :
368 5 : stack.push((self.root_blk, String::new(), 0, 0, 0));
369 5 :
370 5 : let block_cursor = self.reader.block_cursor();
371 :
372 3021 : while let Some((blknum, path, depth, child_idx, key_off)) = stack.pop() {
373 3016 : let blk = block_cursor.read_blk(self.start_blk + blknum, &ctx).await?;
374 3016 : let buf: &[u8] = blk.as_ref();
375 3016 : let node = OnDiskNode::<L>::deparse(buf)?;
376 :
377 3016 : if child_idx == 0 {
378 9 : print!("{:indent$}", "", indent = depth * 2);
379 9 : let path_prefix = stack
380 9 : .iter()
381 9 : .map(|(_blknum, path, ..)| path.as_str())
382 9 : .collect::<String>();
383 9 : println!(
384 9 : "blk #{blknum}: path {path_prefix}{path}: prefix {}, suffix_len {}",
385 9 : hex::encode(node.prefix),
386 9 : node.suffix_len
387 9 : );
388 3007 : }
389 :
390 3016 : if child_idx + 1 < node.num_children {
391 3007 : let key_off = key_off + node.suffix_len as usize;
392 3007 : stack.push((blknum, path.clone(), depth, child_idx + 1, key_off));
393 3007 : }
394 3016 : let key = &node.keys[key_off..key_off + node.suffix_len as usize];
395 3016 : let val = node.value(child_idx as usize);
396 3016 :
397 3016 : print!("{:indent$}", "", indent = depth * 2 + 2);
398 3016 : println!("{}: {}", hex::encode(key), hex::encode(val.0));
399 3016 :
400 3016 : if node.level > 0 {
401 4 : stack.push((val.to_blknum(), hex::encode(node.prefix), depth + 1, 0, 0));
402 3012 : }
403 : }
404 5 : Ok(())
405 5 : }
406 : }
407 :
408 : ///
409 : /// Public builder object, for creating a new tree.
410 : ///
411 : /// Usage: Create a builder object by calling 'new', load all the data into the
412 : /// tree by calling 'append' for each key-value pair, and then call 'finish'
413 : ///
414 : /// 'L' is the key length in bytes
415 : pub struct DiskBtreeBuilder<W, const L: usize>
416 : where
417 : W: BlockWriter,
418 : {
419 : writer: W,
420 :
421 : ///
422 : /// `stack[0]` is the current root page, `stack.last()` is the leaf.
423 : ///
424 : /// We maintain the length of the stack to be always greater than zero.
425 : /// Two exceptions are:
426 : /// 1. `Self::flush_node`. The method will push the new node if it extracted the last one.
427 : /// So because other methods cannot see the intermediate state invariant still holds.
428 : /// 2. `Self::finish`. It consumes self and does not return it back,
429 : /// which means that this is where the structure is destroyed.
430 : /// Thus stack of zero length cannot be observed by other methods.
431 : stack: Vec<BuildNode<L>>,
432 :
433 : /// Last key that was appended to the tree. Used to sanity check that append
434 : /// is called in increasing key order.
435 : last_key: Option<[u8; L]>,
436 : }
437 :
438 : impl<W, const L: usize> DiskBtreeBuilder<W, L>
439 : where
440 : W: BlockWriter,
441 : {
442 20463 : pub fn new(writer: W) -> Self {
443 20463 : DiskBtreeBuilder {
444 20463 : writer,
445 20463 : last_key: None,
446 20463 : stack: vec![BuildNode::new(0)],
447 20463 : }
448 20463 : }
449 :
450 46827239 : pub fn append(&mut self, key: &[u8; L], value: u64) -> Result<()> {
451 46827239 : if value > MAX_VALUE {
452 UBC 0 : return Err(DiskBtreeError::AppendOverflow(value));
453 CBC 46827239 : }
454 46827239 : if let Some(last_key) = &self.last_key {
455 46806776 : if key <= last_key {
456 1 : return Err(DiskBtreeError::UnsortedInput {
457 1 : key: key.as_slice().into(),
458 1 : last_key: last_key.as_slice().into(),
459 1 : });
460 46806775 : }
461 20463 : }
462 46827238 : self.last_key = Some(*key);
463 46827238 :
464 46827238 : self.append_internal(key, Value::from_u64(value))
465 46827239 : }
466 :
467 46917761 : fn append_internal(&mut self, key: &[u8; L], value: Value) -> Result<()> {
468 46917761 : // Try to append to the current leaf buffer
469 46917761 : let last = self
470 46917761 : .stack
471 46917761 : .last_mut()
472 46917761 : .expect("should always have at least one item");
473 46917761 : let level = last.level;
474 46917761 : if last.push(key, value) {
475 46749322 : return Ok(());
476 168439 : }
477 168439 :
478 168439 : // It did not fit. Try to compress, and if it succeeds to make
479 168439 : // some room on the node, try appending to it again.
480 168439 : #[allow(clippy::collapsible_if)]
481 168439 : if last.compress() {
482 85342 : if last.push(key, value) {
483 85291 : return Ok(());
484 51 : }
485 83097 : }
486 :
487 : // Could not append to the current leaf. Flush it and create a new one.
488 83148 : self.flush_node()?;
489 :
490 : // Replace the node we flushed with an empty one and append the new
491 : // key to it.
492 83148 : let mut last = BuildNode::new(level);
493 83148 : if !last.push(key, value) {
494 UBC 0 : return Err(DiskBtreeError::FailedToPushToNewLeafNode);
495 CBC 83148 : }
496 83148 :
497 83148 : self.stack.push(last);
498 83148 :
499 83148 : Ok(())
500 46917761 : }
501 :
502 : /// Flush the bottommost node in the stack to disk. Appends a downlink to its parent,
503 : /// and recursively flushes the parent too, if it becomes full. If the root page becomes full,
504 : /// creates a new root page, increasing the height of the tree.
505 90524 : fn flush_node(&mut self) -> Result<()> {
506 90524 : // Get the current bottommost node in the stack and flush it to disk.
507 90524 : let last = self
508 90524 : .stack
509 90524 : .pop()
510 90524 : .expect("should always have at least one item");
511 90524 : let buf = last.pack();
512 90524 : let downlink_key = last.first_key();
513 90524 : let downlink_ptr = self.writer.write_blk(buf)?;
514 :
515 : // Append the downlink to the parent. If there is no parent, ie. this was the root page,
516 : // create a new root page, increasing the height of the tree.
517 90524 : if self.stack.is_empty() {
518 7387 : self.stack.push(BuildNode::new(last.level + 1));
519 83137 : }
520 90524 : self.append_internal(&downlink_key, Value::from_blknum(downlink_ptr))
521 90524 : }
522 :
523 : ///
524 : /// Flushes everything to disk, and returns the block number of the root page.
525 : /// The caller must store the root block number "out-of-band", and pass it
526 : /// to the DiskBtreeReader::new() when you want to read the tree again.
527 : /// (In the image and delta layers, it is stored in the beginning of the file,
528 : /// in the summary header)
529 : ///
530 20452 : pub fn finish(mut self) -> Result<(u32, W)> {
531 : // flush all levels, except the root.
532 27828 : while self.stack.len() > 1 {
533 7376 : self.flush_node()?;
534 : }
535 :
536 20452 : let root = self
537 20452 : .stack
538 20452 : .first()
539 20452 : .expect("by the check above we left one item there");
540 20452 : let buf = root.pack();
541 20452 : let root_blknum = self.writer.write_blk(buf)?;
542 :
543 20452 : Ok((root_blknum, self.writer))
544 20452 : }
545 :
546 1757220 : pub fn borrow_writer(&self) -> &W {
547 1757220 : &self.writer
548 1757220 : }
549 : }
550 :
551 : ///
552 : /// BuildNode represesnts an incomplete page that we are appending to.
553 : ///
554 UBC 0 : #[derive(Clone, Debug)]
555 : struct BuildNode<const L: usize> {
556 : num_children: u16,
557 : level: u8,
558 : prefix: Vec<u8>,
559 : suffix_len: usize,
560 :
561 : keys: Vec<u8>,
562 : values: Vec<u8>,
563 :
564 : size: usize, // physical size of this node, if it was written to disk like this
565 : }
566 :
567 : const NODE_SIZE: usize = PAGE_SZ;
568 :
569 : const NODE_HDR_SIZE: usize = 2 + 1 + 1 + 1;
570 :
571 : impl<const L: usize> BuildNode<L> {
572 CBC 110998 : fn new(level: u8) -> Self {
573 110998 : BuildNode {
574 110998 : num_children: 0,
575 110998 : level,
576 110998 : prefix: Vec::new(),
577 110998 : suffix_len: 0,
578 110998 : keys: Vec::new(),
579 110998 : values: Vec::new(),
580 110998 : size: NODE_HDR_SIZE,
581 110998 : }
582 110998 : }
583 :
584 : /// Try to append a key-value pair to this node. Returns 'true' on
585 : /// success, 'false' if the page was full or the key was
586 : /// incompatible with the prefix of the existing keys.
587 47086251 : fn push(&mut self, key: &[u8; L], value: Value) -> bool {
588 47086251 : // If we have already performed prefix-compression on the page,
589 47086251 : // check that the incoming key has the same prefix.
590 47086251 : if self.num_children > 0 {
591 : // does the prefix allow it?
592 46975253 : if !key.starts_with(&self.prefix) {
593 21178 : return false;
594 46954075 : }
595 110998 : } else {
596 110998 : self.suffix_len = key.len();
597 110998 : }
598 :
599 : // Is the node too full?
600 47065073 : if self.size + self.suffix_len + VALUE_SZ >= NODE_SIZE {
601 147312 : return false;
602 46917761 : }
603 46917761 :
604 46917761 : // All clear
605 46917761 : self.num_children += 1;
606 46917761 : self.keys.extend(&key[self.prefix.len()..]);
607 46917761 : self.values.extend(value.0);
608 :
609 46917761 : assert!(self.keys.len() == self.num_children as usize * self.suffix_len);
610 46917761 : assert!(self.values.len() == self.num_children as usize * VALUE_SZ);
611 :
612 46917761 : self.size += self.suffix_len + VALUE_SZ;
613 46917761 :
614 46917761 : true
615 47086251 : }
616 :
617 : ///
618 : /// Perform prefix-compression.
619 : ///
620 : /// Returns 'true' on success, 'false' if no compression was possible.
621 : ///
622 168439 : fn compress(&mut self) -> bool {
623 168439 : let first_suffix = self.first_suffix();
624 168439 : let last_suffix = self.last_suffix();
625 168439 :
626 168439 : // Find the common prefix among all keys
627 168439 : let mut prefix_len = 0;
628 1659575 : while prefix_len < self.suffix_len {
629 1659575 : if first_suffix[prefix_len] != last_suffix[prefix_len] {
630 168439 : break;
631 1491136 : }
632 1491136 : prefix_len += 1;
633 : }
634 168439 : if prefix_len == 0 {
635 83097 : return false;
636 85342 : }
637 85342 :
638 85342 : // Can compress. Rewrite the keys without the common prefix.
639 85342 : self.prefix.extend(&self.keys[..prefix_len]);
640 85342 :
641 85342 : let mut new_keys = Vec::new();
642 85342 : let mut key_off = 0;
643 22645080 : while key_off < self.keys.len() {
644 22559738 : let next_key_off = key_off + self.suffix_len;
645 22559738 : new_keys.extend(&self.keys[key_off + prefix_len..next_key_off]);
646 22559738 : key_off = next_key_off;
647 22559738 : }
648 85342 : self.keys = new_keys;
649 85342 : self.suffix_len -= prefix_len;
650 85342 :
651 85342 : self.size -= prefix_len * self.num_children as usize;
652 85342 : self.size += prefix_len;
653 :
654 85342 : assert!(self.keys.len() == self.num_children as usize * self.suffix_len);
655 85342 : assert!(self.values.len() == self.num_children as usize * VALUE_SZ);
656 :
657 85342 : true
658 168439 : }
659 :
660 : ///
661 : /// Serialize the node to on-disk format.
662 : ///
663 110976 : fn pack(&self) -> Bytes {
664 110976 : assert!(self.keys.len() == self.num_children as usize * self.suffix_len);
665 110976 : assert!(self.values.len() == self.num_children as usize * VALUE_SZ);
666 110976 : assert!(self.num_children > 0);
667 :
668 110976 : let mut buf = BytesMut::new();
669 110976 :
670 110976 : buf.put_u16(self.num_children);
671 110976 : buf.put_u8(self.level);
672 110976 : buf.put_u8(self.prefix.len() as u8);
673 110976 : buf.put_u8(self.suffix_len as u8);
674 110976 : buf.put(&self.prefix[..]);
675 110976 : buf.put(&self.keys[..]);
676 110976 : buf.put(&self.values[..]);
677 :
678 110976 : assert!(buf.len() == self.size);
679 :
680 110976 : assert!(buf.len() <= PAGE_SZ);
681 110976 : buf.resize(PAGE_SZ, 0);
682 110976 : buf.freeze()
683 110976 : }
684 :
685 258963 : fn first_suffix(&self) -> &[u8] {
686 258963 : &self.keys[..self.suffix_len]
687 258963 : }
688 168439 : fn last_suffix(&self) -> &[u8] {
689 168439 : &self.keys[self.keys.len() - self.suffix_len..]
690 168439 : }
691 :
692 : /// Return the full first key of the page, including the prefix
693 90524 : fn first_key(&self) -> [u8; L] {
694 90524 : let mut key = [0u8; L];
695 90524 : key[..self.prefix.len()].copy_from_slice(&self.prefix);
696 90524 : key[self.prefix.len()..].copy_from_slice(self.first_suffix());
697 90524 : key
698 90524 : }
699 : }
700 :
701 : #[cfg(test)]
702 : pub(crate) mod tests {
703 : use super::*;
704 : use crate::context::DownloadBehavior;
705 : use crate::task_mgr::TaskKind;
706 : use crate::tenant::block_io::{BlockCursor, BlockLease, BlockReaderRef};
707 : use rand::Rng;
708 : use std::collections::BTreeMap;
709 : use std::sync::atomic::{AtomicUsize, Ordering};
710 :
711 5 : #[derive(Clone, Default)]
712 : pub(crate) struct TestDisk {
713 : blocks: Vec<Bytes>,
714 : }
715 : impl TestDisk {
716 5 : fn new() -> Self {
717 5 : Self::default()
718 5 : }
719 508371 : pub(crate) fn read_blk(&self, blknum: u32) -> io::Result<BlockLease> {
720 508371 : let mut buf = [0u8; PAGE_SZ];
721 508371 : buf.copy_from_slice(&self.blocks[blknum as usize]);
722 508371 : Ok(std::sync::Arc::new(buf).into())
723 508371 : }
724 : }
725 : impl BlockReader for TestDisk {
726 205702 : fn block_cursor(&self) -> BlockCursor<'_> {
727 205702 : BlockCursor::new(BlockReaderRef::TestDisk(self))
728 205702 : }
729 : }
730 : impl BlockWriter for &mut TestDisk {
731 107 : fn write_blk(&mut self, buf: Bytes) -> io::Result<u32> {
732 107 : let blknum = self.blocks.len();
733 107 : self.blocks.push(buf);
734 107 : Ok(blknum as u32)
735 107 : }
736 : }
737 :
738 1 : #[tokio::test]
739 1 : async fn basic() -> Result<()> {
740 1 : let mut disk = TestDisk::new();
741 1 : let mut writer = DiskBtreeBuilder::<_, 6>::new(&mut disk);
742 1 :
743 1 : let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error);
744 1 :
745 1 : let all_keys: Vec<&[u8; 6]> = vec![
746 1 : b"xaaaaa", b"xaaaba", b"xaaaca", b"xabaaa", b"xababa", b"xabaca", b"xabada", b"xabadb",
747 1 : ];
748 1 : let all_data: Vec<(&[u8; 6], u64)> = all_keys
749 1 : .iter()
750 1 : .enumerate()
751 8 : .map(|(idx, key)| (*key, idx as u64))
752 1 : .collect();
753 8 : for (key, val) in all_data.iter() {
754 8 : writer.append(key, *val)?;
755 : }
756 :
757 1 : let (root_offset, _writer) = writer.finish()?;
758 :
759 1 : let reader = DiskBtreeReader::new(0, root_offset, disk);
760 1 :
761 1 : reader.dump().await?;
762 :
763 : // Test the `get` function on all the keys.
764 8 : for (key, val) in all_data.iter() {
765 8 : assert_eq!(reader.get(key, &ctx).await?, Some(*val));
766 : }
767 : // And on some keys that don't exist
768 1 : assert_eq!(reader.get(b"aaaaaa", &ctx).await?, None);
769 1 : assert_eq!(reader.get(b"zzzzzz", &ctx).await?, None);
770 1 : assert_eq!(reader.get(b"xaaabx", &ctx).await?, None);
771 :
772 : // Test search with `visit` function
773 1 : let search_key = b"xabaaa";
774 1 : let expected: Vec<(Vec<u8>, u64)> = all_data
775 1 : .iter()
776 8 : .filter(|(key, _value)| key[..] >= search_key[..])
777 5 : .map(|(key, value)| (key.to_vec(), *value))
778 1 : .collect();
779 1 :
780 1 : let mut data = Vec::new();
781 1 : reader
782 1 : .visit(
783 1 : search_key,
784 1 : VisitDirection::Forwards,
785 5 : |key, value| {
786 5 : data.push((key.to_vec(), value));
787 5 : true
788 5 : },
789 1 : &ctx,
790 1 : )
791 UBC 0 : .await?;
792 CBC 1 : assert_eq!(data, expected);
793 :
794 : // Test a backwards scan
795 1 : let mut expected: Vec<(Vec<u8>, u64)> = all_data
796 1 : .iter()
797 8 : .filter(|(key, _value)| key[..] <= search_key[..])
798 4 : .map(|(key, value)| (key.to_vec(), *value))
799 1 : .collect();
800 1 : expected.reverse();
801 1 : let mut data = Vec::new();
802 1 : reader
803 1 : .visit(
804 1 : search_key,
805 1 : VisitDirection::Backwards,
806 4 : |key, value| {
807 4 : data.push((key.to_vec(), value));
808 4 : true
809 4 : },
810 1 : &ctx,
811 1 : )
812 UBC 0 : .await?;
813 CBC 1 : assert_eq!(data, expected);
814 :
815 : // Backward scan where nothing matches
816 1 : reader
817 1 : .visit(
818 1 : b"aaaaaa",
819 1 : VisitDirection::Backwards,
820 1 : |key, value| {
821 UBC 0 : panic!("found unexpected key {}: {}", hex::encode(key), value);
822 CBC 1 : },
823 1 : &ctx,
824 1 : )
825 UBC 0 : .await?;
826 :
827 : // Full scan
828 CBC 1 : let expected: Vec<(Vec<u8>, u64)> = all_data
829 1 : .iter()
830 8 : .map(|(key, value)| (key.to_vec(), *value))
831 1 : .collect();
832 1 : let mut data = Vec::new();
833 1 : reader
834 1 : .visit(
835 1 : &[0u8; 6],
836 1 : VisitDirection::Forwards,
837 8 : |key, value| {
838 8 : data.push((key.to_vec(), value));
839 8 : true
840 8 : },
841 1 : &ctx,
842 1 : )
843 UBC 0 : .await?;
844 CBC 1 : assert_eq!(data, expected);
845 :
846 1 : Ok(())
847 : }
848 :
849 1 : #[tokio::test]
850 1 : async fn lots_of_keys() -> Result<()> {
851 1 : let mut disk = TestDisk::new();
852 1 : let mut writer = DiskBtreeBuilder::<_, 8>::new(&mut disk);
853 1 : let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error);
854 1 :
855 1 : const NUM_KEYS: u64 = 1000;
856 1 :
857 1 : let mut all_data: BTreeMap<u64, u64> = BTreeMap::new();
858 :
859 1001 : for idx in 0..NUM_KEYS {
860 1000 : let key_int: u64 = 1 + idx * 2;
861 1000 : let key = u64::to_be_bytes(key_int);
862 1000 : writer.append(&key, idx)?;
863 :
864 1000 : all_data.insert(key_int, idx);
865 : }
866 :
867 1 : let (root_offset, _writer) = writer.finish()?;
868 :
869 1 : let reader = DiskBtreeReader::new(0, root_offset, disk);
870 1 :
871 1 : reader.dump().await?;
872 :
873 : use std::sync::Mutex;
874 :
875 1 : let result = Mutex::new(Vec::new());
876 1 : let limit: AtomicUsize = AtomicUsize::new(10);
877 41910 : let take_ten = |key: &[u8], value: u64| {
878 41910 : let mut keybuf = [0u8; 8];
879 41910 : keybuf.copy_from_slice(key);
880 41910 : let key_int = u64::from_be_bytes(keybuf);
881 41910 :
882 41910 : let mut result = result.lock().unwrap();
883 41910 : result.push((key_int, value));
884 41910 :
885 41910 : // keep going until we have 10 matches
886 41910 : result.len() < limit.load(Ordering::Relaxed)
887 41910 : };
888 :
889 2010 : for search_key_int in 0..(NUM_KEYS * 2 + 10) {
890 2010 : let search_key = u64::to_be_bytes(search_key_int);
891 2010 : assert_eq!(
892 2010 : reader.get(&search_key, &ctx).await?,
893 2010 : all_data.get(&search_key_int).cloned()
894 : );
895 :
896 : // Test a forward scan starting with this key
897 2010 : result.lock().unwrap().clear();
898 2010 : reader
899 2010 : .visit(&search_key, VisitDirection::Forwards, take_ten, &ctx)
900 UBC 0 : .await?;
901 CBC 2010 : let expected = all_data
902 2010 : .range(search_key_int..)
903 2010 : .take(10)
904 19910 : .map(|(&key, &val)| (key, val))
905 2010 : .collect::<Vec<(u64, u64)>>();
906 2010 : assert_eq!(*result.lock().unwrap(), expected);
907 :
908 : // And a backwards scan
909 2010 : result.lock().unwrap().clear();
910 2010 : reader
911 2010 : .visit(&search_key, VisitDirection::Backwards, take_ten, &ctx)
912 UBC 0 : .await?;
913 CBC 2010 : let expected = all_data
914 2010 : .range(..=search_key_int)
915 2010 : .rev()
916 2010 : .take(10)
917 20000 : .map(|(&key, &val)| (key, val))
918 2010 : .collect::<Vec<(u64, u64)>>();
919 2010 : assert_eq!(*result.lock().unwrap(), expected);
920 : }
921 :
922 : // full scan
923 1 : let search_key = u64::to_be_bytes(0);
924 1 : limit.store(usize::MAX, Ordering::Relaxed);
925 1 : result.lock().unwrap().clear();
926 1 : reader
927 1 : .visit(&search_key, VisitDirection::Forwards, take_ten, &ctx)
928 UBC 0 : .await?;
929 CBC 1 : let expected = all_data
930 1 : .iter()
931 1000 : .map(|(&key, &val)| (key, val))
932 1 : .collect::<Vec<(u64, u64)>>();
933 1 : assert_eq!(*result.lock().unwrap(), expected);
934 :
935 : // full scan
936 1 : let search_key = u64::to_be_bytes(u64::MAX);
937 1 : limit.store(usize::MAX, Ordering::Relaxed);
938 1 : result.lock().unwrap().clear();
939 1 : reader
940 1 : .visit(&search_key, VisitDirection::Backwards, take_ten, &ctx)
941 UBC 0 : .await?;
942 CBC 1 : let expected = all_data
943 1 : .iter()
944 1 : .rev()
945 1000 : .map(|(&key, &val)| (key, val))
946 1 : .collect::<Vec<(u64, u64)>>();
947 1 : assert_eq!(*result.lock().unwrap(), expected);
948 :
949 1 : Ok(())
950 : }
951 :
952 1 : #[tokio::test]
953 1 : async fn random_data() -> Result<()> {
954 1 : let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error);
955 1 :
956 1 : // Generate random keys with exponential distribution, to
957 1 : // exercise the prefix compression
958 1 : const NUM_KEYS: usize = 100000;
959 1 : let mut all_data: BTreeMap<u128, u64> = BTreeMap::new();
960 100001 : for idx in 0..NUM_KEYS {
961 100000 : let u: f64 = rand::thread_rng().gen_range(0.0..1.0);
962 100000 : let t = -(f64::ln(u));
963 100000 : let key_int = (t * 1000000.0) as u128;
964 100000 :
965 100000 : all_data.insert(key_int, idx as u64);
966 100000 : }
967 :
968 : // Build a tree from it
969 1 : let mut disk = TestDisk::new();
970 1 : let mut writer = DiskBtreeBuilder::<_, 16>::new(&mut disk);
971 :
972 97649 : for (&key, &val) in all_data.iter() {
973 97649 : writer.append(&u128::to_be_bytes(key), val)?;
974 : }
975 1 : let (root_offset, _writer) = writer.finish()?;
976 :
977 1 : let reader = DiskBtreeReader::new(0, root_offset, disk);
978 :
979 : // Test get() operation on all the keys
980 97649 : for (&key, &val) in all_data.iter() {
981 97649 : let search_key = u128::to_be_bytes(key);
982 97649 : assert_eq!(reader.get(&search_key, &ctx).await?, Some(val));
983 : }
984 :
985 : // Test get() operations on random keys, most of which will not exist
986 100001 : for _ in 0..100000 {
987 100000 : let key_int = rand::thread_rng().gen::<u128>();
988 100000 : let search_key = u128::to_be_bytes(key_int);
989 100000 : assert!(reader.get(&search_key, &ctx).await? == all_data.get(&key_int).cloned());
990 : }
991 :
992 : // Test boundary cases
993 1 : assert!(
994 1 : reader.get(&u128::to_be_bytes(u128::MIN), &ctx).await?
995 1 : == all_data.get(&u128::MIN).cloned()
996 : );
997 1 : assert!(
998 1 : reader.get(&u128::to_be_bytes(u128::MAX), &ctx).await?
999 1 : == all_data.get(&u128::MAX).cloned()
1000 : );
1001 :
1002 1 : Ok(())
1003 : }
1004 :
1005 1 : #[test]
1006 1 : fn unsorted_input() {
1007 1 : let mut disk = TestDisk::new();
1008 1 : let mut writer = DiskBtreeBuilder::<_, 2>::new(&mut disk);
1009 1 :
1010 1 : let _ = writer.append(b"ba", 1);
1011 1 : let _ = writer.append(b"bb", 2);
1012 1 : let err = writer.append(b"aa", 3).expect_err("should've failed");
1013 1 : match err {
1014 1 : DiskBtreeError::UnsortedInput { key, last_key } => {
1015 1 : assert_eq!(key.as_ref(), b"aa".as_slice());
1016 1 : assert_eq!(last_key.as_ref(), b"bb".as_slice());
1017 : }
1018 UBC 0 : _ => panic!("unexpected error variant, expected DiskBtreeError::UnsortedInput"),
1019 : }
1020 CBC 1 : }
1021 :
1022 : ///
1023 : /// This test contains a particular data set, see disk_btree_test_data.rs
1024 : ///
1025 1 : #[tokio::test]
1026 1 : async fn particular_data() -> Result<()> {
1027 1 : // Build a tree from it
1028 1 : let mut disk = TestDisk::new();
1029 1 : let mut writer = DiskBtreeBuilder::<_, 26>::new(&mut disk);
1030 1 : let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error);
1031 :
1032 2001 : for (key, val) in disk_btree_test_data::TEST_DATA {
1033 2000 : writer.append(&key, val)?;
1034 : }
1035 1 : let (root_offset, writer) = writer.finish()?;
1036 :
1037 1 : println!("SIZE: {} blocks", writer.blocks.len());
1038 1 :
1039 1 : let reader = DiskBtreeReader::new(0, root_offset, disk);
1040 :
1041 : // Test get() operation on all the keys
1042 2001 : for (key, val) in disk_btree_test_data::TEST_DATA {
1043 2000 : assert_eq!(reader.get(&key, &ctx).await?, Some(val));
1044 : }
1045 :
1046 : // Test full scan
1047 1 : let mut count = 0;
1048 1 : reader
1049 1 : .visit(
1050 1 : &[0u8; 26],
1051 1 : VisitDirection::Forwards,
1052 2000 : |_key, _value| {
1053 2000 : count += 1;
1054 2000 : true
1055 2000 : },
1056 1 : &ctx,
1057 1 : )
1058 UBC 0 : .await?;
1059 CBC 1 : assert_eq!(count, disk_btree_test_data::TEST_DATA.len());
1060 :
1061 1 : reader.dump().await?;
1062 :
1063 1 : Ok(())
1064 : }
1065 : }
1066 :
1067 : #[cfg(test)]
1068 : #[path = "disk_btree_test_data.rs"]
1069 : mod disk_btree_test_data;
|