From 86cc5a09a6315e73f9bc4835ce772266bc2dd08a Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Tue, 13 May 2025 10:01:38 +0900 Subject: [PATCH 1/5] Fix tests --- src/tree.rs | 2 +- tests/test_tree_failpoints.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index f39543532..01512590b 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1967,7 +1967,7 @@ impl Tree { /// # let db: sled::Db<1024> = config.open()?; /// db.insert(b"a", vec![0]); /// db.insert(b"b", vec![1]); - /// assert_eq!(db.len(), 2); + /// assert_eq!(db.len().unwrap(), 2); /// # Ok(()) } /// ``` pub fn len(&self) -> io::Result { diff --git a/tests/test_tree_failpoints.rs b/tests/test_tree_failpoints.rs index 2043a08d4..17cb0b0f2 100644 --- a/tests/test_tree_failpoints.rs +++ b/tests/test_tree_failpoints.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "for-internal-testing-only")] +#![cfg(feature = "failpoints")] mod common; use std::collections::BTreeMap; From 6390980da5df1462ff0f4095a5760b165d9ff371 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Fri, 16 May 2025 14:13:15 +0900 Subject: [PATCH 2/5] Implement prefix compression on btree nodes --- src/leaf.rs | 222 ++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 5 +- src/object_cache.rs | 6 +- src/tree.rs | 140 +++----------------------- tests/00_regression.rs | 50 +++++++--- tests/tree/mod.rs | 4 + 6 files changed, 267 insertions(+), 160 deletions(-) diff --git a/src/leaf.rs b/src/leaf.rs index 8822e86eb..17243fdf7 100644 --- a/src/leaf.rs +++ b/src/leaf.rs @@ -5,7 +5,7 @@ pub(crate) struct Leaf { pub lo: InlineArray, pub hi: Option, pub prefix_length: usize, - pub data: stack_map::StackMap, + data: stack_map::StackMap, pub in_memory_size: usize, pub mutation_count: u64, #[serde(skip)] @@ -58,13 +58,8 @@ impl Leaf { pub(crate) fn get(&self, key: &[u8]) -> Option<&InlineArray> { assert!(self.deleted.is_none()); - let prefixed_key = if self.prefix_length == 0 { - key - } else { - let prefix = self.prefix(); - assert!(key.starts_with(prefix)); - &key[self.prefix_length..] - }; + assert!(key.starts_with(self.prefix())); + let prefixed_key = &key[self.prefix_length..]; self.data.get(prefixed_key) } @@ -74,13 +69,8 @@ impl Leaf { value: InlineArray, ) -> Option { assert!(self.deleted.is_none()); - let prefixed_key = if self.prefix_length == 0 { - key - } else { - let prefix = self.prefix(); - assert!(key.starts_with(prefix)); - key[self.prefix_length..].into() - }; + assert!(key.starts_with(self.prefix())); + let prefixed_key = key[self.prefix_length..].into(); self.data.insert(prefixed_key, value) } @@ -91,4 +81,206 @@ impl Leaf { let partial_key = &key[self.prefix_length..]; self.data.remove(partial_key) } + + pub(crate) fn merge_from(&mut self, other: &mut Self) { + assert!(self.is_empty()); + + self.hi = other.hi.clone(); + + if self.prefix() == other.prefix() { + self.data = std::mem::take(&mut other.data); + return; + } + + let new_prefix_len = if let Some(hi) = &self.hi { + self.lo.iter().zip(hi.iter()).take_while(|(l, r)| l == r).count() + } else { + 0 + }; + + assert_eq!(self.lo[..new_prefix_len], other.lo[..new_prefix_len]); + + // self.prefix_length is not read because it's expected to be + // initialized here. + self.prefix_length = new_prefix_len; + + assert!(self.prefix_length < other.prefix_length); + + let unshifted_key_amount = other.prefix_length - self.prefix_length; + let unshifted_prefix = + &other.lo[other.prefix_length - unshifted_key_amount..]; + + for (k, v) in other.data.iter() { + let mut unshifted_key = + Vec::with_capacity(unshifted_prefix.len() + k.len()); + unshifted_key.extend_from_slice(unshifted_prefix); + unshifted_key.extend_from_slice(k); + self.data.insert(unshifted_key.into(), v.clone()); + } + } + + pub(crate) fn iter( + &self, + ) -> impl Iterator { + let prefix = self.prefix(); + self.data.iter().map(|(k, v)| { + let mut unshifted_key = Vec::with_capacity(prefix.len() + k.len()); + unshifted_key.extend_from_slice(prefix); + unshifted_key.extend_from_slice(k); + (unshifted_key.into(), v.clone()) + }) + } + + pub(crate) fn serialize(&self, zstd_compression_level: i32) -> Vec { + let mut ret = vec![]; + + let mut zstd_enc = + zstd::stream::Encoder::new(&mut ret, zstd_compression_level) + .unwrap(); + + bincode::serialize_into(&mut zstd_enc, self).unwrap(); + + zstd_enc.finish().unwrap(); + + ret + } + + pub(crate) fn deserialize( + buf: &[u8], + ) -> std::io::Result>> { + let zstd_decoded = zstd::stream::decode_all(buf).unwrap(); + let mut leaf: Box> = + bincode::deserialize(&zstd_decoded).unwrap(); + + // use decompressed buffer length as a cheap proxy for in-memory size for now + leaf.in_memory_size = zstd_decoded.len(); + + Ok(leaf) + } + + fn set_in_memory_size(&mut self) { + self.in_memory_size = std::mem::size_of::>() + + self.hi.as_ref().map(|h| h.len()).unwrap_or(0) + + self.lo.len() + + self.data.iter().map(|(k, v)| k.len() + v.len()).sum::(); + } + + pub(crate) fn split_if_full( + &mut self, + new_epoch: FlushEpoch, + allocator: &ObjectCache, + collection_id: CollectionId, + ) -> Option<(InlineArray, Object)> { + if self.data.is_full() { + let old_prefix_len = self.prefix_length; + // split + let split_offset = if self.lo.is_empty() { + // split left-most shard almost at the beginning for + // optimizing downward-growing workloads + 1 + } else if self.hi.is_none() { + // split right-most shard almost at the end for + // optimizing upward-growing workloads + self.data.len() - 2 + } else { + self.data.len() / 2 + }; + + let data = self.data.split_off(split_offset); + + let left_max = &self.data.last().unwrap().0; + let right_min = &data.first().unwrap().0; + + // suffix truncation attempts to shrink the split key + // so that shorter keys bubble up into the index + let splitpoint_length = right_min + .iter() + .zip(left_max.iter()) + .take_while(|(a, b)| a == b) + .count() + + 1; + + let split_key = InlineArray::from(&right_min[..splitpoint_length]); + + let rhs_id = allocator.allocate_object_id(new_epoch); + + log::trace!( + "split leaf {:?} at split key: {:?} into new {:?} at {:?}", + self.lo, + split_key, + rhs_id, + new_epoch, + ); + + let mut rhs = Leaf { + dirty_flush_epoch: Some(new_epoch), + hi: self.hi.clone(), + lo: split_key.clone(), + prefix_length: 0, + in_memory_size: 0, + data, + mutation_count: 0, + page_out_on_flush: None, + deleted: None, + max_unflushed_epoch: None, + }; + + rhs.shorten_keys_after_split(old_prefix_len); + + rhs.set_in_memory_size(); + + self.hi = Some(split_key.clone()); + + self.shorten_keys_after_split(old_prefix_len); + + self.set_in_memory_size(); + + assert_eq!(self.hi.as_ref().unwrap(), &split_key); + assert_eq!(rhs.lo, &split_key); + + let rhs_node = Object { + object_id: rhs_id, + collection_id, + low_key: split_key.clone(), + inner: Arc::new(RwLock::new(CacheBox { + leaf: Some(Box::new(rhs)), + logged_index: BTreeMap::default(), + })), + }; + + return Some((split_key, rhs_node)); + } + + None + } + + pub(crate) fn shorten_keys_after_split(&mut self, old_prefix_len: usize) { + let Some(hi) = self.hi.as_ref() else { return }; + + let new_prefix_len = + self.lo.iter().zip(hi.iter()).take_while(|(l, r)| l == r).count(); + + assert_eq!(self.lo[..new_prefix_len], hi[..new_prefix_len]); + + // self.prefix_length is not read because it's expected to be + // initialized here. + self.prefix_length = new_prefix_len; + + if new_prefix_len == old_prefix_len { + return; + } + + assert!( + new_prefix_len > old_prefix_len, + "expected new prefix length of {} to be greater than the pre-split prefix length of {}", + new_prefix_len, + old_prefix_len + ); + + let key_shift = new_prefix_len - old_prefix_len; + + for (k, v) in std::mem::take(&mut self.data).iter() { + self.data.insert(k[key_shift..].into(), v.clone()); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 1927c8888..038c064b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,6 @@ // 1.0 blockers // // bugs -// * tree predecessor holds lock on successor and tries to get it for predecessor. This will -// deadlock if used concurrently with write batches, which acquire locks lexicographically. -// * add merges to iterator test and assert it deadlocks -// * alternative is to merge right, not left // * page-out needs to be deferred until after any flush of the dirty epoch // * need to remove max_unflushed_epoch after flushing it // * can't send reliable page-out request backwards from 7->6 @@ -25,6 +21,7 @@ // * clean -> dirty -> {maybe coop} -> flushed // * for page-out, we only care if it's stable or if we need to add it to // a page-out priority queue +// * page-out doesn't seem to happen as expected // // reliability // TODO make all writes wrapped in a Tearable wrapper that splits writes diff --git a/src/object_cache.rs b/src/object_cache.rs index 15acd4d50..f02830afe 100644 --- a/src/object_cache.rs +++ b/src/object_cache.rs @@ -396,12 +396,12 @@ impl ObjectCache { // this being called in the destructor. pub fn mark_access_and_evict( &self, - object_id: ObjectId, + accessed_object_id: ObjectId, size: usize, #[allow(unused)] flush_epoch: FlushEpoch, ) -> io::Result<()> { let mut ca = self.cache_advisor.borrow_mut(); - let to_evict = ca.accessed_reuse_buffer(*object_id, size); + let to_evict = ca.accessed_reuse_buffer(*accessed_object_id, size); let mut not_found = 0; for (node_to_evict, _rough_size) in to_evict { let object_id = @@ -411,6 +411,8 @@ impl ObjectCache { unreachable!("object ID must never have been 0"); }; + assert_ne!(accessed_object_id, object_id); + let node = if let Some(n) = self.object_id_index.get(&object_id) { if *n.object_id != *node_to_evict { continue; diff --git a/src/tree.rs b/src/tree.rs index 01512590b..b428d813b 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, VecDeque}; use std::fmt; use std::hint; use std::io; -use std::mem::{self, ManuallyDrop}; +use std::mem::ManuallyDrop; use std::ops; use std::ops::Bound; use std::ops::RangeBounds; @@ -118,6 +118,9 @@ impl LeafWriteGuard<'_, LEAF_FANOUT> { self.flush_epoch_guard.epoch() } + // Handling cache access involves acquiring a mutex to anything + // that is being paged-out so that it can be dropped. We call + // this for things that we want to perform cache fn handle_cache_access_and_eviction_externally( mut self, ) -> (ObjectId, usize) { @@ -412,7 +415,7 @@ impl Tree { let successor_leaf = successor.leaf_write.leaf.as_mut().unwrap(); assert!(predecessor_leaf.deleted.is_none()); - assert!(predecessor_leaf.data.is_empty()); + assert!(predecessor_leaf.is_empty()); assert!(successor_leaf.deleted.is_none()); assert_eq!( predecessor_leaf.hi.as_deref(), @@ -441,9 +444,8 @@ impl Tree { ); } - predecessor_leaf.hi = successor_leaf.hi.clone(); predecessor_leaf.set_dirty_epoch(merge_epoch); - predecessor_leaf.data = std::mem::take(&mut successor_leaf.data); + predecessor_leaf.merge_from(successor_leaf.as_mut()); successor_leaf.deleted = Some(merge_epoch); @@ -966,7 +968,7 @@ impl Tree { } if cfg!(not(feature = "monotonic-behavior")) - && leaf.data.is_empty() + && leaf.is_empty() && leaf.hi.is_some() { self.merge_leaf_into_right_sibling(leaf_guard)?; @@ -1149,7 +1151,7 @@ impl Tree { } if cfg!(not(feature = "monotonic-behavior")) - && leaf.data.is_empty() + && leaf.is_empty() && leaf.hi.is_some() { assert!(!split_happened); @@ -2066,8 +2068,8 @@ impl Iterator for Iter { continue; } - for (k, v) in leaf.data.iter() { - if self.bounds.contains(k) && &search_key <= k { + for (k, v) in leaf.iter() { + if self.bounds.contains(&k) && search_key <= k { self.prefetched.push_back((k.clone(), v.clone())); } } @@ -2142,11 +2144,11 @@ impl DoubleEndedIterator for Iter { continue; } - for (k, v) in leaf.data.iter() { - if self.bounds.contains(k) { + for (k, v) in leaf.iter() { + if self.bounds.contains(&k) { let beneath_last_lo = if let Some(last_lo) = &self.next_back_last_lo { - k < last_lo + &k < last_lo } else { true }; @@ -2242,119 +2244,3 @@ impl Batch { Some(inner.as_ref()) } } - -impl Leaf { - pub fn serialize(&self, zstd_compression_level: i32) -> Vec { - let mut ret = vec![]; - - let mut zstd_enc = - zstd::stream::Encoder::new(&mut ret, zstd_compression_level) - .unwrap(); - - bincode::serialize_into(&mut zstd_enc, self).unwrap(); - - zstd_enc.finish().unwrap(); - - ret - } - - fn deserialize(buf: &[u8]) -> io::Result>> { - let zstd_decoded = zstd::stream::decode_all(buf).unwrap(); - let mut leaf: Box> = - bincode::deserialize(&zstd_decoded).unwrap(); - - // use decompressed buffer length as a cheap proxy for in-memory size for now - leaf.in_memory_size = zstd_decoded.len(); - - Ok(leaf) - } - - fn set_in_memory_size(&mut self) { - self.in_memory_size = mem::size_of::>() - + self.hi.as_ref().map(|h| h.len()).unwrap_or(0) - + self.lo.len() - + self.data.iter().map(|(k, v)| k.len() + v.len()).sum::(); - } - - fn split_if_full( - &mut self, - new_epoch: FlushEpoch, - allocator: &ObjectCache, - collection_id: CollectionId, - ) -> Option<(InlineArray, Object)> { - if self.data.is_full() { - // split - let split_offset = if self.lo.is_empty() { - // split left-most shard almost at the beginning for - // optimizing downward-growing workloads - 1 - } else if self.hi.is_none() { - // split right-most shard almost at the end for - // optimizing upward-growing workloads - self.data.len() - 2 - } else { - self.data.len() / 2 - }; - - let data = self.data.split_off(split_offset); - - let left_max = &self.data.last().unwrap().0; - let right_min = &data.first().unwrap().0; - - // suffix truncation attempts to shrink the split key - // so that shorter keys bubble up into the index - let splitpoint_length = right_min - .iter() - .zip(left_max.iter()) - .take_while(|(a, b)| a == b) - .count() - + 1; - - let split_key = InlineArray::from(&right_min[..splitpoint_length]); - - let rhs_id = allocator.allocate_object_id(new_epoch); - - log::trace!( - "split leaf {:?} at split key: {:?} into new {:?} at {:?}", - self.lo, - split_key, - rhs_id, - new_epoch, - ); - - let mut rhs = Leaf { - dirty_flush_epoch: Some(new_epoch), - hi: self.hi.clone(), - lo: split_key.clone(), - prefix_length: 0, - in_memory_size: 0, - data, - mutation_count: 0, - page_out_on_flush: None, - deleted: None, - max_unflushed_epoch: None, - }; - rhs.set_in_memory_size(); - - self.hi = Some(split_key.clone()); - self.set_in_memory_size(); - - assert_eq!(self.hi.as_ref().unwrap(), &split_key); - assert_eq!(rhs.lo, &split_key); - - let rhs_node = Object { - object_id: rhs_id, - collection_id, - low_key: split_key.clone(), - inner: Arc::new(RwLock::new(CacheBox { - leaf: Some(Box::new(rhs)), - logged_index: BTreeMap::default(), - })), - }; - - return Some((split_key, rhs_node)); - } - - None - } -} diff --git a/tests/00_regression.rs b/tests/00_regression.rs index e99d20e8c..8908a34e2 100644 --- a/tests/00_regression.rs +++ b/tests/00_regression.rs @@ -3,7 +3,7 @@ mod tree; use std::alloc::{Layout, System}; -use tree::{prop_tree_matches_btreemap, Key, Op::*}; +use tree::{Key, Op::*, prop_tree_matches_btreemap}; #[global_allocator] static ALLOCATOR: ShredAllocator = ShredAllocator; @@ -12,18 +12,22 @@ static ALLOCATOR: ShredAllocator = ShredAllocator; struct ShredAllocator; unsafe impl std::alloc::GlobalAlloc for ShredAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe { - assert!(layout.size() < 1_000_000_000); - let ret = System.alloc(layout); - assert_ne!(ret, std::ptr::null_mut()); - std::ptr::write_bytes(ret, 0xa1, layout.size()); - ret - }} + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + assert!(layout.size() < 1_000_000_000); + let ret = System.alloc(layout); + assert_ne!(ret, std::ptr::null_mut()); + std::ptr::write_bytes(ret, 0xa1, layout.size()); + ret + } + } - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { - std::ptr::write_bytes(ptr, 0xde, layout.size()); - System.dealloc(ptr, layout) - }} + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + std::ptr::write_bytes(ptr, 0xde, layout.size()); + System.dealloc(ptr, layout) + } + } } #[allow(dead_code)] @@ -1638,3 +1642,25 @@ fn tree_bug_51() { 0, ); } + +#[test] +#[cfg_attr(miri, ignore)] +fn tree_bug_52() { + // postmortem: + prop_tree_matches_btreemap( + vec![ + Set(Key(vec![57; 1]), 235), + Set(Key(vec![229; 1]), 136), + Set(Key(vec![]), 74), + Set(Key(vec![57; 2]), 0), + Get(Key(vec![57; 1])), + GetGt(Key(vec![57; 1])), + Get(Key(vec![57; 2])), + GetLt(Key(vec![57; 2])), + //Scan(Key(vec![]), 4), + ], + false, + 0, + 0, + ); +} diff --git a/tests/tree/mod.rs b/tests/tree/mod.rs index 801886ded..323535e3c 100644 --- a/tests/tree/mod.rs +++ b/tests/tree/mod.rs @@ -291,6 +291,8 @@ fn prop_tree_matches_btreemap_inner( tree ); } + + assert!(tree_iter.next().is_none()); } else { let mut tree_iter = tree .range(&*k.0..) @@ -317,6 +319,8 @@ fn prop_tree_matches_btreemap_inner( tree ); } + + assert!(tree_iter.next().is_none()); } } Restart => { From 03a48d0dd9ac1355e8ae99d7c11f8cdf672b2e19 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Fri, 16 May 2025 15:14:49 +0900 Subject: [PATCH 3/5] Fix bug with prefix compression --- src/leaf.rs | 11 ++++++++--- tests/00_regression.rs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/leaf.rs b/src/leaf.rs index 17243fdf7..f9bb7950f 100644 --- a/src/leaf.rs +++ b/src/leaf.rs @@ -200,7 +200,11 @@ impl Leaf { .count() + 1; - let split_key = InlineArray::from(&right_min[..splitpoint_length]); + let mut split_vec = + Vec::with_capacity(self.prefix_length + splitpoint_length); + split_vec.extend_from_slice(self.prefix()); + split_vec.extend_from_slice(&right_min[..splitpoint_length]); + let split_key = InlineArray::from(split_vec); let rhs_id = allocator.allocate_object_id(new_epoch); @@ -272,9 +276,10 @@ impl Leaf { assert!( new_prefix_len > old_prefix_len, - "expected new prefix length of {} to be greater than the pre-split prefix length of {}", + "expected new prefix length of {} to be greater than the pre-split prefix length of {} for node {:?}", new_prefix_len, - old_prefix_len + old_prefix_len, + self ); let key_shift = new_prefix_len - old_prefix_len; diff --git a/tests/00_regression.rs b/tests/00_regression.rs index 8908a34e2..b0a8ea89b 100644 --- a/tests/00_regression.rs +++ b/tests/00_regression.rs @@ -1657,7 +1657,7 @@ fn tree_bug_52() { GetGt(Key(vec![57; 1])), Get(Key(vec![57; 2])), GetLt(Key(vec![57; 2])), - //Scan(Key(vec![]), 4), + Scan(Key(vec![]), 4), ], false, 0, From 946fc3dce94aefa4aa40fcb3059c7aff98d95495 Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Fri, 16 May 2025 16:04:42 +0900 Subject: [PATCH 4/5] Keep debugging prefix compression --- src/leaf.rs | 22 ++++++++++++++++------ src/object_cache.rs | 6 +++++- src/tree.rs | 12 ++++++++---- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/leaf.rs b/src/leaf.rs index f9bb7950f..b55b44a4b 100644 --- a/src/leaf.rs +++ b/src/leaf.rs @@ -87,11 +87,6 @@ impl Leaf { self.hi = other.hi.clone(); - if self.prefix() == other.prefix() { - self.data = std::mem::take(&mut other.data); - return; - } - let new_prefix_len = if let Some(hi) = &self.hi { self.lo.iter().zip(hi.iter()).take_while(|(l, r)| l == r).count() } else { @@ -104,7 +99,17 @@ impl Leaf { // initialized here. self.prefix_length = new_prefix_len; - assert!(self.prefix_length < other.prefix_length); + if self.prefix() == other.prefix() { + self.data = std::mem::take(&mut other.data); + return; + } + + assert!( + self.prefix_length < other.prefix_length, + "self: {:?} other: {:?}", + self, + other + ); let unshifted_key_amount = other.prefix_length - self.prefix_length; let unshifted_prefix = @@ -117,6 +122,8 @@ impl Leaf { unshifted_key.extend_from_slice(k); self.data.insert(unshifted_key.into(), v.clone()); } + + assert_eq!(other.data.len(), self.data.len()); } pub(crate) fn iter( @@ -172,6 +179,8 @@ impl Leaf { collection_id: CollectionId, ) -> Option<(InlineArray, Object)> { if self.data.is_full() { + let original_len = self.data.len(); + let old_prefix_len = self.prefix_length; // split let split_offset = if self.lo.is_empty() { @@ -241,6 +250,7 @@ impl Leaf { assert_eq!(self.hi.as_ref().unwrap(), &split_key); assert_eq!(rhs.lo, &split_key); + assert_eq!(rhs.data.len() + self.data.len(), original_len); let rhs_node = Object { object_id: rhs_id, diff --git a/src/object_cache.rs b/src/object_cache.rs index f02830afe..ea6a31517 100644 --- a/src/object_cache.rs +++ b/src/object_cache.rs @@ -411,7 +411,11 @@ impl ObjectCache { unreachable!("object ID must never have been 0"); }; - assert_ne!(accessed_object_id, object_id); + if accessed_object_id == object_id { + // TODO our own object was evicted, so + // set page out after current epoch (or just page out if clean?) + continue; + } let node = if let Some(n) = self.object_id_index.get(&object_id) { if *n.object_id != *node_to_evict { diff --git a/src/tree.rs b/src/tree.rs index b428d813b..fd775ffdf 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -212,15 +212,19 @@ impl Tree { let mut loops: u64 = 0; let mut last_continue = "none"; + let mut warned = false; loop { loops += 1; if loops > 10_000_000 { - log::warn!( - "page_in spinning for a long time due to continue point {}", - last_continue - ); + if !warned { + log::warn!( + "page_in spinning for a long time due to continue point {}", + last_continue + ); + warned = true; + } #[cfg(feature = "for-internal-testing-only")] assert!( From f6870e089006e9b25d1839e404ec315b19c5c01f Mon Sep 17 00:00:00 2001 From: Tyler Neely Date: Fri, 16 May 2025 16:20:42 +0900 Subject: [PATCH 5/5] Fix bug with prefix encoding in merges --- src/leaf.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/leaf.rs b/src/leaf.rs index b55b44a4b..3a2334cc2 100644 --- a/src/leaf.rs +++ b/src/leaf.rs @@ -112,8 +112,8 @@ impl Leaf { ); let unshifted_key_amount = other.prefix_length - self.prefix_length; - let unshifted_prefix = - &other.lo[other.prefix_length - unshifted_key_amount..]; + let unshifted_prefix = &other.lo + [other.prefix_length - unshifted_key_amount..other.prefix_length]; for (k, v) in other.data.iter() { let mut unshifted_key = @@ -124,6 +124,15 @@ impl Leaf { } assert_eq!(other.data.len(), self.data.len()); + + #[cfg(feature = "for-internal-testing-only")] + assert_eq!( + self.iter().collect::>(), + other.iter().collect::>(), + "self: {:#?} \n other: {:#?}\n", + self, + other + ); } pub(crate) fn iter(