#[cfg(test)]
mod test;
mod insert;
mod iter;
mod nodes;
mod remove;
mod types;
pub use iter::BTreeIter;
use crate::common::error::Result;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
fmt,
ops::Bound,
result::Result as StdResult,
};
use vsdb_core::basic::mapx_raw::MapxRaw;
pub(crate) use nodes::{MAX_KEYS, MIN_KEYS, Node};
pub use types::{EMPTY_ROOT, NodeId};
pub(crate) use types::{InsertResult, LeafState, NodeRef, RemoveResult};
#[derive(Clone, Debug)]
pub struct PersistentBTree {
pub(crate) nodes: MapxRaw,
next_id: NodeId,
pub(crate) ref_counts: HashMap<NodeId, NodeRef>,
pub(crate) ref_counts_ready: bool,
pending: HashMap<NodeId, Vec<u8>>,
}
impl Serialize for PersistentBTree {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: serde::Serializer,
{
debug_assert!(
self.pending.is_empty(),
"PersistentBTree serialized with a non-empty write buffer"
);
use serde::ser::SerializeTuple;
let mut t = serializer.serialize_tuple(2)?;
t.serialize_element(&self.nodes)?;
t.serialize_element(&self.next_id)?;
t.end()
}
}
impl<'de> Deserialize<'de> for PersistentBTree {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Vis;
impl<'de> serde::de::Visitor<'de> for Vis {
type Value = PersistentBTree;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("PersistentBTree")
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(
self,
mut seq: A,
) -> StdResult<PersistentBTree, A::Error> {
let nodes: MapxRaw = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let stored_next_id: NodeId = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
let mut next_id = stored_next_id;
for (k, _) in nodes.iter() {
let id = NodeId::from_le_bytes(k[..8].try_into().unwrap());
next_id = next_id.max(id.saturating_add(1));
}
Ok(PersistentBTree {
nodes,
next_id,
ref_counts: Default::default(),
ref_counts_ready: false,
pending: Default::default(),
})
}
}
deserializer.deserialize_tuple(2, Vis)
}
}
impl PersistentBTree {
pub fn instance_id(&self) -> u64 {
self.nodes.instance_id()
}
pub fn save_meta(&self) -> Result<u64> {
let id = self.instance_id();
crate::common::save_instance_meta(id, self)?;
Ok(id)
}
pub fn from_meta(instance_id: u64) -> Result<Self> {
crate::common::load_instance_meta(instance_id)
}
pub fn new() -> Self {
Self {
nodes: MapxRaw::new(),
next_id: 1, ref_counts: HashMap::new(),
ref_counts_ready: true, pending: HashMap::new(),
}
}
const PENDING_FLUSH_THRESHOLD: usize = 1024;
fn alloc(&mut self, node: &Node) -> NodeId {
let id = self.next_id;
self.next_id = self
.next_id
.checked_add(1)
.expect("PersistentBTree: NodeId space exhausted");
debug_assert!(
!self.pending.contains_key(&id)
&& self.nodes.get(id.to_le_bytes()).is_none(),
"PersistentBTree: NodeId {id} already occupied — allocator regression"
);
self.pending.insert(id, node.encode());
if self.ref_counts_ready {
let children = match node {
Node::Internal { children, .. } => {
for &child in children {
if let Some(cr) = self.ref_counts.get_mut(&child) {
cr.ref_count += 1;
}
}
children.clone()
}
Node::Leaf { .. } => Vec::new(),
};
self.ref_counts.insert(
id,
NodeRef {
ref_count: 0,
children,
},
);
}
id
}
fn node(&self, id: NodeId) -> Node {
if !self.pending.is_empty()
&& let Some(raw) = self.pending.get(&id)
{
return Node::decode(raw);
}
let raw = self
.nodes
.get(id.to_le_bytes())
.unwrap_or_else(|| panic!("PersistentBTree: missing node {id}"));
Node::decode(&raw)
}
fn flush_pending(&mut self) {
if self.pending.is_empty() {
return;
}
let mut batch = self.nodes.batch_entry();
for (id, raw) in self.pending.drain() {
batch.insert(&id.to_le_bytes(), &raw);
}
batch
.commit()
.expect("vsdb: node flush failed during btree write");
}
fn child_index(keys: &[Vec<u8>], target: &[u8]) -> usize {
match keys.binary_search_by(|k| k.as_slice().cmp(target)) {
Ok(i) => i + 1,
Err(i) => i,
}
}
pub fn get(&self, root: NodeId, key: &[u8]) -> Option<Vec<u8>> {
if root == EMPTY_ROOT {
return None;
}
let mut cur = root;
loop {
match self.node(cur) {
Node::Leaf { keys, values } => {
return match keys.binary_search_by(|k| k.as_slice().cmp(key)) {
Ok(i) => Some(values[i].clone()),
Err(_) => None,
};
}
Node::Internal { keys, children } => {
cur = children[Self::child_index(&keys, key)];
}
}
}
}
#[inline]
pub fn contains_key(&self, root: NodeId, key: &[u8]) -> bool {
self.get(root, key).is_some()
}
pub fn iter(&self, root: NodeId) -> BTreeIter<'_> {
BTreeIter::new(self, root, Bound::Unbounded, Bound::Unbounded)
}
pub fn range(
&self,
root: NodeId,
lo: Bound<&[u8]>,
hi: Bound<&[u8]>,
) -> BTreeIter<'_> {
let lo = match lo {
Bound::Included(k) => Bound::Included(k.to_vec()),
Bound::Excluded(k) => Bound::Excluded(k.to_vec()),
Bound::Unbounded => Bound::Unbounded,
};
let hi = match hi {
Bound::Included(k) => Bound::Included(k.to_vec()),
Bound::Excluded(k) => Bound::Excluded(k.to_vec()),
Bound::Unbounded => Bound::Unbounded,
};
BTreeIter::new(self, root, lo, hi)
}
pub fn bulk_load(
&mut self,
entries: impl IntoIterator<Item = (Vec<u8>, Vec<u8>)>,
) -> NodeId {
let entries: Vec<_> = entries.into_iter().fold(
Vec::<(Vec<u8>, Vec<u8>)>::new(),
|mut acc, (k, v)| {
if let Some((last_k, last_v)) = acc.last_mut() {
if k == *last_k {
*last_v = v;
return acc;
}
assert!(
k > *last_k,
"PersistentBTree::bulk_load entries must be sorted by key"
);
}
acc.push((k, v));
acc
},
);
if entries.is_empty() {
return EMPTY_ROOT;
}
fn chunk_sizes(total: usize, cap: usize, min: usize) -> Vec<usize> {
debug_assert!(min <= cap.div_ceil(2));
let mut sizes = Vec::with_capacity(total.div_ceil(cap));
let mut remaining = total;
while remaining > cap {
sizes.push(cap);
remaining -= cap;
}
if remaining > 0 {
sizes.push(remaining);
}
let n = sizes.len();
if n >= 2 && sizes[n - 1] < min {
let merged = sizes[n - 2] + sizes[n - 1];
sizes[n - 2] = merged.div_ceil(2);
sizes[n - 1] = merged / 2;
}
sizes
}
let mut leaf_ids = Vec::new();
let mut off = 0;
for size in chunk_sizes(entries.len(), MAX_KEYS, MIN_KEYS) {
let chunk = &entries[off..off + size];
off += size;
let keys = chunk.iter().map(|(k, _)| k.clone()).collect();
let values = chunk.iter().map(|(_, v)| v.clone()).collect();
leaf_ids.push(self.alloc(&Node::Leaf { keys, values }));
if self.pending.len() >= Self::PENDING_FLUSH_THRESHOLD {
self.flush_pending();
}
}
let mut level = leaf_ids;
while level.len() > 1 {
let mut next = Vec::new();
let mut i = 0;
for take in chunk_sizes(level.len(), MAX_KEYS + 1, MIN_KEYS + 1) {
let chunk = &level[i..i + take];
let mut keys = Vec::with_capacity(chunk.len() - 1);
for &cid in &chunk[1..] {
keys.push(self.first_key(cid));
}
next.push(self.alloc(&Node::Internal {
keys,
children: chunk.to_vec(),
}));
if self.pending.len() >= Self::PENDING_FLUSH_THRESHOLD {
self.flush_pending();
}
i += take;
}
level = next;
}
self.flush_pending();
level[0]
}
fn first_key(&self, id: NodeId) -> Vec<u8> {
let mut cur = id;
loop {
match self.node(cur) {
Node::Leaf { keys, .. } => return keys[0].clone(),
Node::Internal { children, .. } => cur = children[0],
}
}
}
pub fn acquire_node(&mut self, id: NodeId) {
if id == EMPTY_ROOT || !self.ref_counts_ready {
return;
}
if let Some(nr) = self.ref_counts.get_mut(&id) {
nr.ref_count += 1;
}
}
pub fn release_node(&mut self, id: NodeId) {
if id == EMPTY_ROOT || !self.ref_counts_ready {
return;
}
debug_assert!(
self.pending.is_empty(),
"release_node called with a non-empty write buffer"
);
let mut dead_keys = Vec::new();
let mut work = vec![id];
while let Some(nid) = work.pop() {
if nid == EMPTY_ROOT {
continue;
}
let Some(nr) = self.ref_counts.get_mut(&nid) else {
continue;
};
debug_assert!(
nr.ref_count > 0,
"release_node called on node {nid} with ref_count=0"
);
if nr.ref_count == 0 {
continue;
}
nr.ref_count -= 1;
if nr.ref_count == 0 {
let children = std::mem::take(&mut nr.children);
self.ref_counts.remove(&nid);
dead_keys.push(nid.to_le_bytes().to_vec());
work.extend(children);
}
}
if !dead_keys.is_empty() {
self.nodes.lazy_delete_batch(dead_keys);
}
}
pub fn rebuild_ref_counts(&mut self, live_roots: &[NodeId]) {
let mut new_refs: HashMap<NodeId, NodeRef> = HashMap::new();
let mut visited = HashSet::new();
let mut queue: Vec<NodeId> = Vec::new();
for &root in live_roots {
if root != EMPTY_ROOT {
new_refs
.entry(root)
.or_insert_with(|| NodeRef {
ref_count: 0,
children: Vec::new(),
})
.ref_count += 1;
queue.push(root);
}
}
while let Some(id) = queue.pop() {
if !visited.insert(id) {
continue;
}
if let Some(raw) = self.nodes.get(id.to_le_bytes()) {
let node = Node::decode(&raw);
let children = match &node {
Node::Internal { children, .. } => {
for &child in children {
new_refs
.entry(child)
.or_insert_with(|| NodeRef {
ref_count: 0,
children: Vec::new(),
})
.ref_count += 1;
queue.push(child);
}
children.clone()
}
Node::Leaf { .. } => Vec::new(),
};
new_refs
.entry(id)
.or_insert_with(|| NodeRef {
ref_count: 0,
children: Vec::new(),
})
.children = children;
}
}
let mut max_id = 0;
let dead_keys: Vec<Vec<u8>> = self
.nodes
.iter()
.filter_map(|(k, _)| {
let id = u64::from_le_bytes(k[..8].try_into().unwrap());
max_id = max_id.max(id);
(!visited.contains(&id)).then_some(k)
})
.collect();
if !dead_keys.is_empty() {
self.nodes.lazy_delete_batch(dead_keys);
}
self.next_id = self.next_id.max(
max_id
.checked_add(1)
.expect("PersistentBTree: NodeId space exhausted"),
);
self.ref_counts = new_refs;
self.ref_counts_ready = true;
}
pub fn gc(&mut self, live_roots: &[NodeId]) {
self.rebuild_ref_counts(live_roots);
}
}
impl Default for PersistentBTree {
fn default() -> Self {
Self::new()
}
}