Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5f565e1
Evict superseded leaf cache pages after write completion
codex Oct 8, 2026
710e510
clean up same thing
citizen-stig Oct 13, 2026
46fdaec
perf: advance B-tree leaf cursors in place
codex Oct 8, 2026
6b14ce4
perf: count page leaves with a compact index stack
codex Oct 8, 2026
a445203
Compare trie key prefixes by bytes
codex Oct 8, 2026
8104051
Move overlay changes into the commit instead of cloning them
citizen-stig Oct 13, 2026
6ef4f50
Overlap rollback persistence and post-meta cleanup with tree work
citizen-stig Oct 13, 2026
2179e77
rename and interface change
citizen-stig Oct 20, 2026
4263c8e
explicit clone
citizen-stig Oct 20, 2026
2d970a6
Add Options::prepopulate_leaf_cache to fill the leaf cache on startup
citizen-stig Oct 12, 2026
76a99d9
Hash session actuals for the merkle update on multiple threads
citizen-stig Oct 21, 2026
fc91b25
Build the rollback delta while the merkle update runs
citizen-stig Oct 21, 2026
acd216e
Resolve a seek's own leaf from its read value hash without a beatree …
citizen-stig Oct 12, 2026
5788a55
Fix formatting and tidy up leaf cache prepopulation
citizen-stig Oct 21, 2026
866b754
Parametrize concurrency
citizen-stig Oct 21, 2026
fb5daaf
smarter parallelism
citizen-stig Oct 21, 2026
2284064
Merge remote-tracking branch 'origin/autoresearchresult-02-leaf-cache…
citizen-stig Oct 21, 2026
e2c1558
Merge remote-tracking branch 'origin/autoresearchresult-03-reconstruc…
citizen-stig Oct 21, 2026
b250274
Merge remote-tracking branch 'origin/nikolai/overlay-commit-move' int…
citizen-stig Oct 21, 2026
ea8935c
Merge remote-tracking branch 'origin/nikolai/rollback-in-store-sync' …
citizen-stig Oct 21, 2026
d3fc807
Merge remote-tracking branch 'origin/nikolai/known-leaf-seek' into ni…
citizen-stig Oct 21, 2026
a30eaef
Merge remote-tracking branch 'origin/nikolai/parallel-compact-actuals…
citizen-stig Oct 21, 2026
cd0da4b
Merge remote-tracking branch 'origin/nikolai/rollback-delta-during-me…
citizen-stig Oct 21, 2026
d0c5287
Merge remote-tracking branch 'origin/nikolai/prepopulate-leaf-cache' …
citizen-stig Oct 21, 2026
6e2eb4b
Update compact_actuals test for read value hashes in merkle KeyReadWrite
citizen-stig Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions core/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,16 @@ pub fn build_trie<H: NodeHasher>(
}

let common_after_prefix = |k1: &KeyPath, k2: &KeyPath| {
let x = &k1.view_bits::<Msb0>()[skip..];
let y = &k2.view_bits::<Msb0>()[skip..];
shared_bits(x, y)
let first_byte = skip / 8;
let shared = k1[first_byte..]
.iter()
.zip(&k2[first_byte..])
.position(|(a, b)| a != b)
.map_or(256, |i| {
(first_byte + i) * 8
+ (k1[first_byte + i] ^ k2[first_byte + i]).leading_zeros() as usize
});
shared - skip
};

while let Some((this_key, this_val)) = b {
Expand Down
5 changes: 5 additions & 0 deletions nomt/src/beatree/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ impl Index {
self.first_key_map.insert(separator, branch)
}

/// Iterate all bottom-level branch nodes.
pub fn branches(&self) -> impl Iterator<Item = &Arc<BranchNode>> {
self.first_key_map.values()
}

#[cfg(test)]
pub fn into_iter(self) -> impl Iterator<Item = (Key, Arc<BranchNode>)> {
self.first_key_map.into_iter()
Expand Down
21 changes: 21 additions & 0 deletions nomt/src/beatree/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,27 @@ impl LeafIterator {
}

fn next<'a>(&'a mut self) -> Option<IterOutput<'a>> {
// Most steps remain within the same leaf. Advance its cursor in place instead
// of moving the entire state, including its branch and leaf handles, twice.
let within_leaf = match &self.state {
LeafIteratorState::Proceeding { current, .. } => {
let next = current.index + 1;
next < current.leaf.n()
&& self
.end
.as_ref()
.map_or(true, |end| current.leaf.key(next) < *end)
}
_ => false,
};
if within_leaf {
let LeafIteratorState::Proceeding { current, .. } = &mut self.state else {
unreachable!();
};
current.index += 1;
return Some(current.last_output());
}

// This is a so-called "Streaming Iterator", where the items borrow the lifetime
// of the callsite rather than consuming items from the object - these are somewhat awkward
// because we first have to advance the state of the iterator and then return a borrow
Expand Down
14 changes: 14 additions & 0 deletions nomt/src/beatree/leaf_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,27 @@ impl LeafCache {
shard.cache.get(&page_number).map(|x| x.clone())
}

/// The maximum number of leaves the cache holds.
pub fn capacity(&self) -> usize {
self.inner
.shards
.iter()
.map(|shard| shard.lock().max_items)
.sum()
}

/// Insert a cache entry. This does not evict anything.
pub fn insert(&self, page_number: PageNumber, node: Arc<LeafNode>) {
let mut shard = self.inner.shard_for(page_number);

shard.cache.put(page_number, node);
}

/// Remove a superseded page. Existing readers can still reload it from disk.
pub(super) fn remove(&self, page_number: PageNumber) {
self.inner.shard_for(page_number).cache.pop(&page_number);
}

/// Evict all excess items from the cache.
pub fn evict(&self) {
for shard in &self.inner.shards {
Expand Down
53 changes: 46 additions & 7 deletions nomt/src/beatree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,36 @@ impl Tree {
})
}

/// Fill the leaf cache with leaves, up to its capacity, reading them on `threads` threads.
pub fn prepopulate_leaf_cache(&self, threads: usize) {
let shared = self.shared.read();
let leaf_cache = shared.leaf_cache.clone();
let leaf_store = shared.leaf_store_rd.clone();
let leaves: Vec<PageNumber> = shared
.bbn_index
.branches()
.flat_map(|branch| (0..branch.n() as usize).map(|i| PageNumber(branch.node_pointer(i))))
.take(leaf_cache.capacity())
.collect();
drop(shared);

// `chunks` panics on 0, so keep the size at least 1 even if there are no leaves.
let chunk_size = leaves.len().div_ceil(threads.max(1)).max(1);
let (leaf_cache, leaf_store) = (&leaf_cache, &leaf_store);
std::thread::scope(|scope| {
for chunk in leaves.chunks(chunk_size) {
scope.spawn(move || {
for &pn in chunk {
let leaf = Arc::new(leaf::node::LeafNode {
inner: leaf_store.query(pn),
});
leaf_cache.insert(pn, leaf);
}
});
}
});
}

/// Lookup a key in the btree. This blocks the current thread.
pub fn lookup(&self, key: Key) -> Option<Vec<u8>> {
let shared = self.shared.read();
Expand Down Expand Up @@ -483,13 +513,22 @@ impl SyncController {

/// Finishes sync.
///
/// Has to be called after the manifest is updated. Must be invoked by the sync
/// thread. Blocking.
pub fn post_meta(&mut self) {
let pre_swap_rx = self.inner.pre_swap_rx.lock().take().unwrap();
join_task(&pre_swap_rx);
let bbn_index = self.inner.bbn_index.lock().take().unwrap();
Tree::finish_sync(&self.inner.shared, bbn_index);
/// Has to be called after the manifest is updated. Non-blocking: the returned task must be
/// joined before the commit completes.
pub fn post_meta(&mut self) -> Receiver<TaskResult<()>> {
let inner = self.inner.clone();
let (tx, rx) = crossbeam_channel::bounded(1);
spawn_task(
&self.inner.sync.tp,
move || {
let pre_swap_rx = inner.pre_swap_rx.lock().take().unwrap();
join_task(&pre_swap_rx);
let bbn_index = inner.bbn_index.lock().take().unwrap();
Tree::finish_sync(&inner.shared, bbn_index);
},
tx,
);
rx
}
}

Expand Down
3 changes: 3 additions & 0 deletions nomt/src/beatree/ops/update/leaf_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ impl PostIoWork {
pub(super) fn run(self, leaf_cache: &LeafCache) {
for worker_output in self.worker_changes.into_iter() {
for (_, leaf_entry) in worker_output.leaves_tracker.inner {
if let Some(pn) = leaf_entry.deleted {
leaf_cache.remove(pn);
}
if let Some((leaf, pn)) = leaf_entry.inserted {
leaf_cache.insert(pn, leaf);
}
Expand Down
Loading
Loading