documenting the LBA manager's internal structure and operations.
Assisted-by: Claude <claude.ai>
Signed-off-by: Ronen Friedman <rfriedma@redhat.com>
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab expandtab
+/**
+ * FixedKVBtree - a generic, persistent, copy-on-write B+tree for Seastore.
+ *
+ * This template is the shared implementation for both the LBA tree (logical ->
+ * physical address mapping) and the Backref tree (physical -> logical reverse
+ * mapping). It is parameterized on:
+ * - node_key_t : key type stored in nodes (laddr_t for LBA, paddr_t for backref)
+ * - node_val_t : value type in leaf nodes (lba_map_val_t / backref_map_val_t)
+ * - internal_node_t / leaf_node_t : concrete extent types for inner/leaf nodes
+ * - cursor_t : type-erased handle returned to callers (LBACursor / BackrefCursor)
+ * - node_size : on-disk node size (typically 4096)
+ *
+ * The tree is "wandering": writes never update nodes in place. Instead, nodes
+ * are duplicated (CoW) via cache.duplicate_for_write(), and parent pointers are
+ * updated up to the root. All operations are transaction-scoped - mutations
+ * are visible only within the transaction until commit.
+ *
+ * Key concepts:
+ * - iterator : a stack of (node, position) pairs from root to leaf that
+ * represents a position in the tree. Can be "full" (every
+ * level populated) or "partial" (only leaf + some parents).
+ * - cursor_t : a lightweight, type-erased reference to a leaf entry,
+ * created from an iterator via get_cursor().
+ * - op_context_t : bundles {Cache&, Transaction&} for passing through the tree.
+ */
+
#pragma once
#include <boost/container/static_vector.hpp>
// TRANS_SYNC is used.
};
+/**
+ * Forward declarations - specialized per tree type (LBA or Backref) in
+ * their respective .cc files. These extract the tree-specific root pointer,
+ * root node, and per-transaction stats from the global root_t / Transaction.
+ */
+
template <typename T>
phy_tree_root_t& get_phy_tree_root(root_t& r);
const RootBlockRef &root_block,
op_context_t c);
+/**
+ * Returns this tree type's stats accumulator within the transaction (e.g.
+ * Transaction::lba_tree_stats or backref_tree_stats).
+ */
template <typename T>
Transaction::tree_stats_t& get_tree_stats(Transaction &t);
+/**
+ * =============================================================================
+ * FixedKVBtree<...> - the B+tree implementation.
+ *
+ * Template parameters:
+ * node_key_t - key type (laddr_t or paddr_t)
+ * node_val_t - leaf value type (lba_map_val_t or backref_map_val_t)
+ * internal_node_t - CachedExtent subclass for internal nodes
+ * leaf_node_t - CachedExtent subclass for leaf nodes
+ * cursor_t - type-erased leaf position handle (LBACursor / BackrefCursor)
+ * node_size - on-disk extent size (4096)
+ * =============================================================================
+ */
template <
typename node_key_t,
typename node_val_t,
class iterator;
using iterator_fut = base_iertr::future<iterator>;
+ /**
+ * True when leaf nodes can have child extents (LBA tree leaves point to
+ * data extents; backref leaves do not).
+ */
static constexpr bool leaf_has_children =
std::is_base_of_v<ParentNode<leaf_node_t, node_key_t>, leaf_node_t>;
+ /**
+ * Callback invoked during tree traversal to visit every node/leaf encountered.
+ */
using mapped_space_visitor_t = std::function<
void(paddr_t, node_key_t, extent_len_t, depth_t, extent_types_t, iterator&)>;
+ // =========================================================================
+
+ /**
+ * iterator - a root-to-leaf path through the tree.
+ *
+ * Holds a node_position_t<leaf_node_t> for the leaf level and a stack of
+ * node_position_t<internal_node_t> for depths 2..N (depth 1 is the leaf).
+ *
+ * An iterator can be "full" (all levels populated) or "partial" (only the
+ * leaf is known; internal entries are lazily filled via ensure_internal()
+ * when needed - e.g. for prev(), handle_boundary(), or split/merge).
+ *
+ * "at_boundary()" means the leaf position is past the end of the current
+ * leaf node. This happens naturally during forward iteration (next());
+ * handle_boundary() advances to the next leaf via the internal stack.
+ */
class iterator {
public:
#ifndef NDEBUG
}
#endif
+ /**
+ * Advance to the next entry. Increments leaf.pos; if that moves past
+ * the end of the current leaf, handle_boundary() walks up the internal
+ * stack to find the next leaf (or reaches tree-end).
+ */
iterator_fut next(
op_context_t c,
mapped_space_visitor_t *visitor=nullptr) const
}
+ /**
+ * Move to the previous entry. If already at position 0 in the current
+ * leaf, walks up the internal stack (ensure_internal_bottom_up) to find
+ * an ancestor with room to move left, then descends to the rightmost
+ * entry of the preceding subtree.
+ */
iterator_fut prev(op_context_t c) const
{
#ifndef NDEBUG
auto ret = *this;
+ // Fast path: still within the same leaf node.
if (ret.leaf.pos > 0) {
ret.leaf.pos--;
return iterator_fut(
ret);
}
+ // Slow path: need to move to the previous leaf.
+ // Walk up from depth 2 until we find a level where pos > 0 (has a
+ // left sibling), decrement there, then descend to the last entry at
+ // each level below.
return seastar::do_with(
(depth_t)2,
std::move(ret),
return ret.get_internal(depth_with_space).pos > 0;
}).si_then([&ret, c, &li, &ll](auto depth_with_space) {
assert(depth_with_space <= ret.get_depth()); // must not be begin()
+ // Clear intermediate levels that will be re-populated by the descent.
for (depth_t depth = 2; depth < depth_with_space; ++depth) {
ret.get_internal(depth).reset();
}
ret.leaf.reset();
ret.get_internal(depth_with_space).pos--;
+ // Descend using "last entry" lambdas (li, ll) at each level.
// note, cannot result in at_boundary() by construction
return lookup_depth_range(
c, ret, depth_with_space - 1, 0, li, ll, nullptr
return internal[depth - 2];
}
+ /**
+ * Lazily populate a specific internal level of a partial iterator.
+ * A partial iterator knows its leaf but may not have references to
+ * all ancestor nodes. This method navigates from a known child up
+ * to its parent (via get_parent_node()), fills in the internal entry
+ * at 'depth', and sets the correct position within that parent.
+ */
using ensure_internal_iertr = get_child_iertr;
using ensure_internal_ret = ensure_internal_iertr::template future<>;
ensure_internal_ret ensure_internal(op_context_t c, depth_t depth) {
});
}
+ /**
+ * Return the key at the current leaf position.
+ */
node_key_t get_key() const {
assert(!is_end());
return leaf.node->iter_idx(leaf.pos).get_key();
}
+
+ /**
+ * Return the value at the current leaf position. For the LBA tree,
+ * relative physical addresses stored in the leaf are resolved against
+ * the leaf's own paddr (they may be stored as offsets within the same
+ * segment to save space).
+ */
node_val_t get_val() const {
assert(!is_end());
auto ret = leaf.node->iter_idx(leaf.pos).get_val();
friend class FixedKVBtree;
static constexpr uint16_t INVALID = std::numeric_limits<uint16_t>::max();
+ /**
+ * A (node-ref, position-within-node) pair. One of these exists for
+ * each level in the iterator's path: one leaf entry + up to MAX_DEPTH-1
+ * internal entries.
+ */
template <typename NodeType>
struct node_position_t {
typename NodeType::Ref node;
return node->iter_idx(pos);
}
};
+
+ /**
+ * Stack of internal node positions, indexed by (depth - 2). Depth 1 is
+ * the leaf; depth 2 is the lowest internal node, etc. For a partial
+ * iterator, entries may be null (node == nullptr) for levels not yet
+ * resolved.
+ */
boost::container::static_vector<
node_position_t<internal_node_t>, MAX_DEPTH> internal;
node_position_t<leaf_node_t> leaf;
return leaf.pos == leaf.node->get_size();
}
+ /**
+ * Walk up from 'start_from' toward the root, calling ensure_internal()
+ * at each level, until stop_f(depth) returns true. Returns the depth
+ * at which stop_f was satisfied. Used by prev() and handle_boundary()
+ * to find the nearest ancestor that has room to move laterally.
+ */
using ensure_internal_bottom_up_ret =
ensure_internal_iertr::template future<depth_t>;
template <typename Func>
});
}
+ /**
+ * Called when leaf.pos has advanced past the end of the current leaf
+ * (at_boundary() == true). Walks up the internal stack to find the
+ * first ancestor whose position can be incremented (i.e. has a right
+ * sibling), then descends back down to the leftmost entry of the next
+ * subtree, populating internal and leaf positions along the way.
+ * If no ancestor has a right sibling, the iterator becomes end().
+ */
using handle_boundary_ertr = base_iertr;
using handle_boundary_ret = handle_boundary_ertr::future<>;
handle_boundary_ret handle_boundary(
});
}
+ /**
+ * Pre-insertion check: scan upward from the leaf to find the first
+ * level that is not at max capacity. Returns the depth from which
+ * splitting must begin (0 = no split needed, get_depth() = need a
+ * new root). Used by handle_split() to know how far up the split
+ * cascade must go.
+ */
using check_split_iertr = ensure_internal_iertr;
using check_split_ret = check_split_iertr::template future<depth_t>;
check_split_ret check_split(op_context_t c) {
}
};
+ /**
+ * Construct a tree handle rooted at the given RootBlock. The RootBlock
+ * stores the physical address and depth of this tree's root node.
+ */
FixedKVBtree(RootBlockRef &root_block) : root_block(root_block) {}
+ /**
+ * Access the tree-specific root descriptor (paddr + depth) from the
+ * global root_t stored in the RootBlock.
+ */
auto& get_root() {
return get_phy_tree_root<self_type>(root_block->get_root());
}
return get_phy_tree_root<self_type>(root_block->get_root());
}
+ /**
+ * Link a new root node into the RootBlock's parent-tracking system.
+ */
template <typename T>
void set_root_node(const TCachedExtentRef<T> &root_node) {
static_assert(std::is_base_of_v<typename internal_node_t::base_t, T>);
TreeRootLinker<RootBlock, T>::link_root(root_block, root_node.get());
}
+ /**
+ * Retrieve the root node extent (may trigger async I/O if not cached).
+ */
auto get_root_node(op_context_t c) const {
return get_phy_tree_root_node<self_type>(root_block, c);
}
+ /**
+ * Synchronous variant - asserts the root node is already in cache.
+ */
auto get_root_node_sync(op_context_t c) const {
return get_phy_tree_root_node_sync<self_type>(root_block, c);
}
- /// mkfs
+ /**
+ * mkfs
+ * Create the initial (empty) tree during mkfs. Allocates a single empty
+ * leaf node as the root, links it to the RootBlock, and returns the
+ * tree's root descriptor.
+ */
using mkfs_ret = phy_tree_root_t;
static mkfs_ret mkfs(RootBlockRef &root_block, op_context_t c) {
assert(root_block->is_mutation_pending());
return phy_tree_root_t{root_leaf->get_paddr(), 1u};
}
+ /**
+ * Build a partial iterator from an existing cursor. The cursor already
+ * holds a leaf node reference and position; the internal levels are left
+ * unpopulated (they'll be lazily filled by ensure_internal() if needed).
+ */
iterator make_partial_iter(
op_context_t c,
cursor_t &cursor)
return new cursor_t(c, leaf, leaf->modifications, key, it.get_val(), pos);
}
+ /**
+ * Synchronous lower_bound - requires all nodes along the path to already
+ * be in cache. Used on hot paths where blocking is unacceptable (e.g.
+ * transaction commit). Walks from root to leaf using get_child_sync().
+ */
iterator lower_bound_sync(
op_context_t c,
node_key_t addr)
}
/**
- * lower_bound
+ * lower_bound (async) - the primary tree lookup.
+ *
+ * Returns the first iterator whose key >= addr. Internally, this calls
+ * lookup() which: (1) resolves the root node, (2) at each internal level
+ * calls upper_bound(addr) and backs up one to find the child that could
+ * contain addr, (3) at the leaf calls lower_bound(addr) to land on the
+ * exact position.
+ *
+ * min_depth > 1 is used by update_internal_mapping() to stop the descent
+ * early and land on an internal node rather than a leaf.
+ *
+ * The optional visitor callback is invoked at each node visited during
+ * the descent (used for space accounting / scanning).
*
* @param c [in] context
* @param addr [in] ddr
/**
* upper_bound
*
+ * Implemented as lower_bound + skip-if-exact-match.
+ *
* @param c [in] context
* @param addr [in] ddr
* @return least iterator > key
}
/**
- * upper_bound_right
+ * upper_bound_right - find the entry whose range *contains* addr.
+ *
+ * Returns the least iterator i such that i.key + i.val.len > addr.
+ * This is the key lookup for range-based queries: given an address that
+ * may fall in the middle of a mapping, find the mapping that contains it.
+ *
+ * Algorithm: lower_bound(addr), then check whether the *previous* entry's
+ * range (key..key+len) spans addr. If so, return that previous entry.
+ * Used by BtreeLBAManager::get_cursors() to find the first mapping that
+ * overlaps a query range.
*
* @param c [in] context
* @param addr [in] addr
return upper_bound(c, min_max_t<node_key_t>::max);
}
+ /**
+ * =========================================================================
+ * Unit-test-only validation helpers.
+ *
+ * check_node(): for every entry in 'node', verifies that the child pointer
+ * tracking (parent->child and child->parent) is consistent - the child
+ * extent references its parent correctly, and the parent's children[]
+ * array points back. Handles stable, pending, and mutation_pending states.
+ *
+ * check_child_trackers(): full-tree validation - iterates every node via
+ * lower_bound + iterate_repeat and calls check_node() at each level.
+ * =========================================================================
+ */
#ifdef UNIT_TESTS_BUILT
template <typename child_node_t, typename node_t, bool lhc = leaf_has_children,
typename std::enable_if<lhc, int>::type = 0>
}
#endif
+ /**
+ * Generic forward iteration helper. Calls f(iter) repeatedly; if 'f'
+ * returns stop_iteration::no, advances iter via next() and repeats.
+ * Continues until 'f' returns stop_iteration::yes (typically when iter
+ * reaches end()). Used by check_child_trackers and callers that need
+ * to scan a range of entries.
+ */
using iterate_repeat_ret_inner = base_iertr::future<
seastar::stop_iteration>;
template <typename F>
* Inserts val at laddr with iter as a hint. If element at laddr already
* exists returns iterator to that element unchanged and returns false.
*
- * Invalidates all outstanding iterators for this tree on this transaction.
+ * Implementation steps:
+ * 1. find_insertion() adjusts iter to the correct insertion point.
+ * 2. If the key already exists, return (iter, false) without modifying.
+ * 3. handle_split() splits any full nodes along the path (CoW).
+ * 4. duplicate_for_write() the leaf if not already mutable.
+ * 5. Insert the entry at the correct position within the leaf.
+ * 6. If the leaf tracks child pointers (LBA tree), insert the child ptr.
+ *
+ * Returns (iterator-to-entry, true) on success, or (iterator-to-existing, false)
+ * if the key was already present.
+ *
+ * IMPORTANT: invalidates all outstanding iterators for this tree within
+ * the transaction, because splits may reallocate nodes.
*
* @param c [in] op context
* @param iter [in] hint, insertion constant if immediately prior to iter
* copy
*
* Copy is pretty similar as Insert, the difference is that it's
- * inserting the val copied from src_iter into the position cor-
- * responding to laddr.
+ * inserting the val copied from src_iter into the position
+ * corresponding to laddr.
*
* The reason we are introducing this method is that, since rewrite
* transactions are not invalidating other ones, we can't allow
});
}
+ /**
+ * Convenience overload: perform a lower_bound lookup first, then insert.
+ */
insert_ret insert(
op_context_t c,
node_key_t laddr,
/**
* update
*
+ * CoWs the leaf node if needed (duplicate_for_write), then overwrites the
+ * value at iter's position. Does NOT change the key. For the LBA tree
+ * this is used to update the physical address or refcount of a mapping
+ * without changing the logical address.
+ *
* Invalidates all outstanding iterators for this tree on this transaction.
*
* @param c [in] op context
* Replace the entry pointed by iter with the key and val. key
* must be within the range iter.get_key()~iter.get_length()
*
+ * If the new key still belongs to the same leaf node, a simple in-place
+ * replace is done. If the new key falls beyond the current leaf's key
+ * range (rare edge case during extent splits/remaps), the entry is
+ * inserted into the next leaf and the original is removed.
+ *
* @param c [in] op context
* @param iter [in] iterator to element to update, must not be end
* @param key [in] key with which to replace
/**
* remove
*
+ * Steps:
+ * 1. CoW the leaf (duplicate_for_write) if not already mutable.
+ * 2. Remove the entry from the leaf.
+ * 3. handle_merge() rebalances or merges underflowing nodes up the tree.
+ * 4. If the iterator lands at a boundary after removal, handle_boundary()
+ * advances it to the next valid position (or end).
+ *
+ * Returns an iterator pointing to the entry that now occupies the removed
+ * entry's position (or end if it was the last entry).
+ *
* Invalidates all outstanding iterators for this tree on this transaction.
*
* @param c [in] op context
});
});
}
-
+
/**
* init_cached_extent
*
* Checks whether e is live (reachable from fixed kv tree) and drops or initializes
- * accordingly.
+ * accordingly.
*
- * Returns if e is live.
+ * Called during cache warm-up or replay to determine if a cached extent
+ * is still part of the tree (hasn't been replaced by a CoW copy).
+ * Performs a lower_bound on the extent's begin key and checks whether the
+ * node found at the appropriate depth is the same object as e.
+ *
+ * Returns true if live (still reachable from the root), false otherwise.
*/
using init_cached_extent_iertr = base_iertr;
using init_cached_extent_ret = init_cached_extent_iertr::future<bool>;
}
}
- /// get_leaf_if_live: get leaf node at laddr/addr if still live
+ /**
+ * get_leaf_if_live: get leaf node at laddr/addr if still live
+ * Used by GC/cleaner to check if an on-disk leaf extent is still
+ * reachable before deciding to rewrite it.
+ */
using get_leaf_if_live_iertr = base_iertr;
using get_leaf_if_live_ret = get_leaf_if_live_iertr::future<CachedExtentRef>;
get_leaf_if_live_ret get_leaf_if_live(
}
- /// get_internal_if_live: get internal node at laddr/addr if still live
+ /**
+ * get_internal_if_live: get internal node at laddr/addr if still live
+ * Walks the iterator's internal stack to find a node at the matching paddr.
+ */
using get_internal_if_live_iertr = base_iertr;
using get_internal_if_live_ret = get_internal_if_live_iertr::future<CachedExtentRef>;
get_internal_if_live_ret get_internal_if_live(
/**
- * rewrite_extent
+ * rewrite_extent - GC/cleaner entry point for relocating a tree node.
*
* Rewrites a fresh copy of extent into transaction and updates internal
* references.
+ *
+ * Process:
+ * Allocates a fresh copy of the extent (at a new physical address with
+ * the target rewrite generation), copies the content via rewrite(),
+ * then calls update_internal_mapping() to patch the parent's pointer
+ * from old_paddr -> new_paddr. Finally retires the old extent.
+ *
+ * This is how the "wandering tree" handles segment cleaning: stale nodes
+ * are rewritten to new segments without changing the logical tree structure.
*/
using rewrite_extent_iertr = base_iertr;
using rewrite_extent_ret = rewrite_extent_iertr::future<>;
CachedExtentRef e) {
LOG_PREFIX(FixedKVBtree::rewrite_extent);
assert(is_lba_backref_node(e->get_type()));
-
+
auto do_rewrite = [&](auto &fixed_kv_extent) {
auto n_fixed_kv_extent = c.cache.template alloc_new_non_data_extent<
std::remove_reference_t<decltype(fixed_kv_extent)>
// get target rewrite generation
fixed_kv_extent.get_rewrite_generation());
n_fixed_kv_extent->rewrite(c.trans, fixed_kv_extent, 0);
-
+
SUBTRACET(
seastore_fixedkv_tree,
"rewriting {} into {}",
c.trans,
fixed_kv_extent,
*n_fixed_kv_extent);
-
+
return update_internal_mapping(
c,
n_fixed_kv_extent->get_node_meta().depth,
c.cache.retire_extent(c.trans, e);
});
};
-
+
if (e->get_type() == internal_node_t::TYPE) {
auto lint = e->cast<internal_node_t>();
return do_rewrite(*lint);
}
}
+ /**
+ * update_internal_mapping - patch a parent pointer after a child is rewritten.
+ *
+ * After rewrite_extent() creates a new copy of a node at a different
+ * physical address, this method finds the parent that points to the old
+ * address and updates it to point to the new address.
+ *
+ * Uses lower_bound() with min_depth = depth+1 to land on the parent node,
+ * then validates that the parent's entry matches old_addr before patching.
+ *
+ * If the rewritten node is the root, updates the RootBlock directly.
+ * Otherwise, CoWs the parent and updates the child pointer in place.
+ */
using update_internal_mapping_iertr = base_iertr;
using update_internal_mapping_ret = update_internal_mapping_iertr::future<>;
template <typename T>
private:
- RootBlockRef root_block;
+ RootBlockRef root_block; // handle to the global root; stores this tree's root paddr + depth
+ /**
+ * Build a partial iterator from a known leaf, key, and position.
+ * Internal levels are left empty (state = PARTIAL if depth > 1).
+ */
iterator make_partial_iter(
op_context_t c,
TCachedExtentRef<leaf_node_t> leaf,
template <typename T>
using node_position_t = typename iterator::template node_position_t<T>;
+ /**
+ * get_internal_node - read or retrieve an internal node extent from cache.
+ *
+ * Calls cache.maybe_get_absent_extent() which either returns the node
+ * from the transaction/cache (cache hit) or reads it from disk (cache
+ * miss). On first load, init_internal links the node into the parent
+ * tracking system (either as a child of parent_pos, or as the tree root).
+ *
+ * After loading, validates the in-extent checksum against the committed
+ * CRC and asserts that the node metadata (depth, begin, end) matches
+ * expectations.
+ */
using get_internal_node_iertr = base_iertr;
using get_internal_node_ret = get_internal_node_iertr::future<InternalNodeRef>;
static get_internal_node_ret get_internal_node(
}
+ /**
+ * Analogous to get_internal_node, but for leaf extents. Same cache-or-disk
+ * fetch, parent linking, checksum validation, and metadata assertion logic.
+ */
using get_leaf_node_iertr = base_iertr;
using get_leaf_node_ret = get_leaf_node_iertr::future<LeafNodeRef>;
static get_leaf_node_ret get_leaf_node(
});
}
+ /**
+ * lookup_root - resolve the root node and populate the top of the iterator.
+ *
+ * First tries get_root_node() which checks if the root is already cached
+ * in the transaction. If not, falls back to get_internal_node() or
+ * get_leaf_node() to fetch it from disk. Sets the root's position in
+ * the iterator and optionally invokes the visitor callback.
+ */
using lookup_root_iertr = base_iertr;
using lookup_root_ret = lookup_root_iertr::future<>;
lookup_root_ret lookup_root(
}
}
+ /**
+ * lookup_internal_level - descend one internal level during a lookup.
+ *
+ * Given an iterator with the parent level (depth+1) already populated,
+ * fetches the child internal node at the parent's current position.
+ * First checks if the child is already tracked via get_child() (in-memory
+ * parent->child link); if so, uses it directly. Otherwise, reads from
+ * disk via get_internal_node().
+ *
+ * After fetching, calls the lookup function f() on the child to determine
+ * which entry to descend into next, and stores the result in iter.
+ */
using lookup_internal_level_iertr = base_iertr;
using lookup_internal_level_ret = lookup_internal_level_iertr::future<>;
template <typename F>
});
}
+ /**
+ * Analogous to lookup_internal_level but for the final descent to a
+ * leaf node. Reads the leaf from the depth-2 parent's child pointer,
+ * calls the leaf lookup function f() to set the leaf position in iter.
+ */
using lookup_leaf_iertr = base_iertr;
using lookup_leaf_ret = lookup_leaf_iertr::future<>;
template <typename F>
}
/**
- * lookup_depth_range
+ * lookup_depth_range - descend from depth 'from' down to depth 'to'
+ * (exclusive), calling lookup_internal_level at each internal level and
+ * lookup_leaf at depth 1.
+ *
+ * li: function(internal_node) -> iterator - selects position at internal levels
+ * ll: function(leaf_node) -> iterator - selects position at leaf level
*
- * Performs node lookups on depths [from, to) using li and ll to
- * specific target at each level. Note, may leave the iterator
- * at_boundary(), call handle_boundary() prior to returning out
- * lf FixedKVBtree.
+ * This is the inner descent loop used by both lookup() and
+ * handle_boundary(). NOTE: may leave the iterator at_boundary()
+ * (past the end of a leaf); callers must call handle_boundary() before
+ * exposing the iterator externally.
*/
using lookup_depth_range_iertr = base_iertr;
using lookup_depth_range_ret = lookup_depth_range_iertr::future<>;
});
}
+ /**
+ * lookup - the core top-to-bottom tree traversal.
+ *
+ * This is the backbone of lower_bound() and all read operations.
+ * Steps:
+ * 1. lookup_root() - fetch and position at the root node.
+ * 2. Apply the internal lookup function (li) at the root if internal,
+ * or the leaf lookup function (ll) if root is a leaf.
+ * 3. lookup_depth_range() - descend through remaining levels.
+ * 4. If the descent lands at_boundary(), call handle_boundary() to
+ * advance to the next leaf (unless this is a min_depth > 1 lookup
+ * for update_internal_mapping, which stops at an internal level).
+ *
+ * Returns a fully-populated iterator pointing at the result.
+ */
using lookup_iertr = base_iertr;
using lookup_ret = lookup_iertr::future<iterator>;
template <typename LI, typename LL>
}
/**
- * find_insertion
+ * find_insertion - adjust an iterator (from lower_bound) to the exact
+ * insertion point for a new key.
+ *
*
* Prepare iter for insertion. iter should begin pointing at
* the valid insertion point (lower_bound(laddr)).
* position at which laddr should be inserted. iter may, upon completion,
* point at the end of a leaf other than the end leaf if that's the correct
* insertion point.
+ *
+ * Three cases:
+ * 1. Exact match (key exists): return immediately (insert will detect dup).
+ * 2. Key is within the current leaf's range: already at the right spot.
+ * 3. Key is before the current leaf's begin: the insertion point is at
+ * the end of the *previous* leaf. Call prev() and advance one past
+ * it. This is the only case where the iterator intentionally points
+ * at the boundary (end) of a non-end leaf.
*/
using find_insertion_iertr = base_iertr;
using find_insertion_ret = find_insertion_iertr::future<>;
* Upon completion, iter will point at the newly split insertion point. As
* with find_insertion, iter's leaf pointer may be end without iter being
* end.
+ *
+ * All splits are CoW: new nodes are allocated, the old ones are retired.
+ * The tree_stats extents_num_delta is incremented for each split (net +1
+ * node per split, since one node becomes two).
*/
using handle_split_iertr = base_iertr;
using handle_split_ret = handle_split_iertr::future<>;
}
+ /**
+ * handle_merge - rebalance or merge underflowing nodes after a remove.
+ *
+ * Starting from the leaf, checks if below_min_capacity(). If so:
+ * 1. ensure_internal() populates the parent level.
+ * 2. merge_level() either merges with a sibling (if sibling is also at
+ * minimum) or rebalances entries between the two nodes.
+ * 3. Walks upward: if the parent also became under-capacity after losing
+ * a child entry, repeat at the next level.
+ * 4. At the root: if the root has only one child after a merge, collapse
+ * the root (reduce tree depth by 1).
+ *
+ * All merges/rebalances are CoW: new nodes replace old ones.
+ */
using handle_merge_iertr = base_iertr;
using handle_merge_ret = handle_merge_iertr::future<>;
handle_merge_ret handle_merge(
return get_internal_node(c, depth, addr, begin, end, std::move(parent_pos));
}
+ /**
+ * merge_level - merge or rebalance a single under-capacity node with a
+ * sibling at the given depth.
+ *
+ * Picks the adjacent sibling (preferring left if pos is the rightmost
+ * child). Then:
+ * - If sibling is also at minimum capacity: make_full_merge() combines
+ * both into one node; the parent loses an entry (extents_num_delta--).
+ * - Otherwise: make_balanced() redistributes entries between the two
+ * nodes around a new pivot; the parent's key is updated.
+ *
+ * In both cases, old nodes are retired and new CoW copies replace them.
+ * The iterator (parent_pos, pos) is updated to reflect the new structure.
+ */
template <typename NodeType>
handle_merge_ret merge_level(
op_context_t c,
}
};
+
+// =============================================================================
+// Type trait and free-function helpers for working with FixedKVBtree instances.
+// =============================================================================
+
+/**
+ * is_fixed_kv_tree<T>::value is true for FixedKVBtree instantiations.
+ */
template <typename T>
struct is_fixed_kv_tree : std::false_type {};
+/**
+ * Synchronous btree construction - asserts root is already cached.
+ */
template <typename tree_type_t>
tree_type_t get_btree_sync(op_context_t c) {
assert(!c.trans.peek_root()->is_pending_io());
return tree_type_t(root);
}
+/**
+ * Async btree construction - fetches the root block from cache (may do I/O).
+ */
template <typename tree_type_t>
Cache::get_root_iertr::future<tree_type_t>
get_btree(op_context_t c) {
co_return tree_type_t{croot};
}
+/**
+ * Convenience: fetch the btree and invoke f(btree) with the tree held in a
+ * do_with scope (so it stays alive across continuations).
+ */
template <
typename tree_type_t,
typename F,
});
}
+/**
+ * Like with_btree, but also holds a State object across the operation.
+ * f(btree, state) can accumulate results into state; the final state is
+ * returned as the future's value. Two overloads: one takes an initial
+ * state, the other default-constructs it.
+ */
template <
typename tree_type_t,
typename State,
* - TRACE: read operations, DEBUG details
*/
+/**
+ * \file
+ * This file implements BtreeLBAManager - the LBA (logical → physical) address
+ * translation layer.
+ */
+
template <> struct fmt::formatter<
crimson::os::seastore::lba::LBABtree::iterator>
: public fmt::formatter<std::string_view>
}
};
+// -------------------------------------------------------------------------
+// Template specializations for the LBA tree.
+// These wire the generic FixedKVBtree infrastructure to the LBA-specific
+// root, stats, and node types.
+// -------------------------------------------------------------------------
+
namespace crimson::os::seastore {
+/** get_tree_stats<LBABtree> → Transaction::lba_tree_stats */
template <typename T>
Transaction::tree_stats_t& get_tree_stats(Transaction &t)
{
crimson::os::seastore::lba::LBABtree>(
Transaction &t);
+/** get_phy_tree_root<LBABtree> → root_t::lba_root (paddr + depth of tree root) */
template <typename T>
phy_tree_root_t& get_phy_tree_root(root_t &r)
{
get_phy_tree_root<
crimson::os::seastore::lba::LBABtree>(root_t &r);
+/**
+ * Synchronous root-node fetch: returns the LBA root node extent from cache.
+ * If the root_block is pending (being mutated), the root node is fetched
+ * from the prior (stable) instance. Asserts the node is in cache.
+ */
template <>
CachedExtentRef get_phy_tree_root_node_sync<
crimson::os::seastore::lba::LBABtree>(
return ret;
}
+/**
+ * Async root-node fetch: returns (found, future<node>). If the root node
+ * pointer is known (lba_root_node != null), returns {true, future} that
+ * resolves via get_extent_viewable_by_trans (may block if not yet readable).
+ * Otherwise returns {false, ready-future} signaling the caller should fall
+ * back to reading from disk via get_internal_node/get_leaf_node.
+ */
template <>
const get_phy_tree_root_node_ret get_phy_tree_root_node<
crimson::os::seastore::lba::LBABtree>(
}
}
+/**
+ * TreeRootLinker specialization for LBA tree. Bidirectionally links the
+ * RootBlock and the LBA root node (internal or leaf) so that the tree can
+ * be traversed from the root, and the root node can find its way back to
+ * the RootBlock.
+ */
template <typename RootT>
class TreeRootLinker<RootBlock, RootT> {
public:
namespace crimson::os::seastore::lba {
+// ---------------------------------------------------------------------------
+// Public API implementations
+// ---------------------------------------------------------------------------
+
+/**
+ * mkfs: create the initial empty LBA tree (single empty leaf root).
+ */
BtreeLBAManager::mkfs_ret
BtreeLBAManager::mkfs(
Transaction &t)
croot->get_root().lba_root = LBABtree::mkfs(croot, get_context(t));
}
+/**
+ * get_cursors (public): fetch the btree, then delegate to the internal
+ * overload that takes an op_context + btree reference.
+ */
BtreeLBAManager::get_cursors_ret
BtreeLBAManager::get_cursors(
Transaction &t,
co_return co_await get_cursors(c, btree, laddr, length);
}
+/**
+ * get_cursor (by laddr): exact match or containing-range match.
+ */
BtreeLBAManager::get_cursor_ret
BtreeLBAManager::get_cursor(
Transaction &t,
}
}
+/**
+ * get_cursor (by extent): navigates from the data extent up to its parent
+ * leaf node via get_parent_node(), then constructs a cursor at the extent's
+ * laddr. Avoids a full root-to-leaf tree traversal.
+ */
BtreeLBAManager::get_cursor_ret
BtreeLBAManager::get_cursor(
Transaction &t,
co_return btree.get_cursor(c, leaf, extent.get_laddr());
}
+/**
+ * get_cursors (internal): range query. Starts with upper_bound_right(laddr)
+ * to find the first entry whose range overlaps the query, then iterates
+ * forward collecting cursors until key >= laddr + length.
+ */
BtreeLBAManager::get_cursors_ret
BtreeLBAManager::get_cursors(
op_context_t c,
co_return ret;
}
+/**
+ * resolve_indirect_cursor: given an indirect mapping (laddr → local_clone_id),
+ * reconstruct the intermediate key and look up the direct mapping that owns
+ * the physical data. Asserts exactly one direct cursor is found.
+ */
BtreeLBAManager::resolve_indirect_cursor_ret
BtreeLBAManager::resolve_indirect_cursor(
op_context_t c,
});
}
+/** lower_bound: simple btree lower_bound, returns cursor at first entry >= laddr. */
BtreeLBAManager::lower_bound_ret
BtreeLBAManager::lower_bound(
Transaction &t,
co_return iter.get_cursor(c);
}
+/**
+ * reserve_region: insert a zero-mapping (P_ADDR_ZERO) at the specified laddr.
+ * Uses the cursor as a btree insertion hint. The reserved_ptr child pointer
+ * marks this leaf entry as a placeholder (no real data extent yet).
+ */
BtreeLBAManager::alloc_extent_ret
BtreeLBAManager::reserve_region(
Transaction &t,
co_return iter.get_cursor(c);
}
+/**
+ * alloc_extents (with cursor hint): insert mappings for extents that already
+ * have assigned laddrs, using 'cursor' as a btree hint. Processes extents
+ * in reverse order so each insertion stays near the hint position (since the
+ * hint is at the end of the target range).
+ */
BtreeLBAManager::alloc_extents_ret
BtreeLBAManager::alloc_extents(
Transaction &t,
co_return ret;
}
+/**
+ * clone_mapping: create an indirect mapping at 'laddr' that references the
+ * direct mapping 'mapping' via inter_key. The indirect entry stores
+ * inter_key.get_local_clone_id() as its pladdr. If updateref is true,
+ * the target direct mapping's refcount is incremented first.
+ */
BtreeLBAManager::clone_mapping_ret
BtreeLBAManager::clone_mapping(
Transaction &t,
mapping};
}
+// ---------------------------------------------------------------------------
+// Internal lookup helpers
+// ---------------------------------------------------------------------------
+
+/**
+ * get_cursor (internal, exact): lower_bound + check for exact key match.
+ * Returns enoent if laddr is not found.
+ */
BtreeLBAManager::get_cursor_ret
BtreeLBAManager::get_cursor(
op_context_t c,
});
}
+// ---------------------------------------------------------------------------
+// Address allocation internals
+// ---------------------------------------------------------------------------
+
+/**
+ * search_insert_position: find a free laddr near 'hint' that can accommodate
+ * 'length' bytes without overlapping existing mappings.
+ *
+ * Algorithm:
+ * 1. Start at upper_bound_right(hint.lower_boundary()) - first entry that
+ * could conflict with the hint range.
+ * 2. While there is a conflict (overlap or hint-policy violation):
+ * a. gen_random policy: pick a new random hint and re-search.
+ * b. linear policy: advance hint past the conflicting entry and try
+ * the next position. May loop back to the beginning of the
+ * address space if the hint wraps past the object boundary.
+ * 3. Return the chosen laddr and the btree iterator at the insertion point.
+ *
+ * Warns if > 32 attempts (possible fragmentation or misconfigured hints).
+ */
BtreeLBAManager::search_insert_position_ret
BtreeLBAManager::search_insert_position(
op_context_t c,
co_return insert_position_t{hint.addr, iter};
}
+/**
+ * alloc_contiguous_mappings: allocate a contiguous block of laddrs for
+ * multiple mappings. search_insert_position finds a single starting laddr
+ * for the total length; each info's key is then set sequentially from that
+ * base. All entries are inserted via insert_mappings.
+ */
BtreeLBAManager::alloc_mappings_ret
BtreeLBAManager::alloc_contiguous_mappings(
Transaction &t,
});
}
+/**
+ * alloc_sparse_mappings: allocate mappings at pre-assigned, non-contiguous
+ * laddrs. Each info already has a key; the base offset is adjusted by the
+ * difference between hint.addr and the allocated starting laddr. The
+ * entries must be sorted and non-overlapping.
+ */
BtreeLBAManager::alloc_mappings_ret
BtreeLBAManager::alloc_sparse_mappings(
Transaction &t,
});
}
+/**
+ * insert_mappings: the inner loop that inserts all alloc_infos into the btree.
+ *
+ * Phase 1 (forward): for each info, call btree.insert() at 'iter', advance
+ * iter to next. For direct mappings, sets the extent's laddr if not yet
+ * assigned. Uses reserved_ptr for indirect/zero mappings (no real child).
+ *
+ * Phase 2 (backward): walk iter backward alloc_infos.size() times to collect
+ * cursors for all inserted entries. This is necessary because forward
+ * insertions can invalidate previously-created cursors (splits reallocate
+ * leaf nodes), so cursors are only safe to create after all inserts complete.
+ */
BtreeLBAManager::alloc_mappings_ret
BtreeLBAManager::insert_mappings(
op_context_t c,
});
}
+// ---------------------------------------------------------------------------
+// Extent lifecycle - cache warm-up, GC, rewrite
+// ---------------------------------------------------------------------------
+
static bool is_lba_node(const CachedExtent &e)
{
return is_lba_node(e.get_type());
}
+/**
+ * _init_cached_extent: determine if extent 'e' is live in the LBA tree.
+ * For logical (data) extents: lower_bound(laddr), check paddr match, and
+ * if live, link the extent into the leaf's children[] array.
+ * For tree nodes (internal/leaf): delegate to btree.init_cached_extent.
+ */
base_iertr::template future<>
_init_cached_extent(
op_context_t c,
}
#endif
+/**
+ * scan_mappings: iterate all direct mappings in [begin, end), calling f
+ * for each. Indirect mappings (pladdr.is_laddr()) are skipped.
+ */
BtreeLBAManager::scan_mappings_ret
BtreeLBAManager::scan_mappings(
Transaction &t,
});
}
+/**
+ * rewrite_extent: GC entry point - relocate an LBA tree node to a new
+ * segment. Only processes LBA internal/leaf nodes; skips non-LBA extents.
+ * Delegates to LBABtree::rewrite_extent which allocates a fresh copy and
+ * patches the parent pointer.
+ */
BtreeLBAManager::rewrite_extent_ret
BtreeLBAManager::rewrite_extent(
Transaction &t,
}
}
+// ---------------------------------------------------------------------------
+// Update operations
+// ---------------------------------------------------------------------------
+
+/**
+ * update_mapping: update a single mapping's paddr, length, and checksum.
+ * Called during commit when a data extent has been relocated. Validates
+ * old paddr/length match before patching. Returns the new refcount.
+ */
BtreeLBAManager::update_mapping_ret
BtreeLBAManager::update_mapping(
Transaction& t,
co_return res->get_refcount();
}
+/**
+ * update_mappings: batch version - for each extent, navigate from the data
+ * extent up to its parent leaf (via get_parent_node), construct a cursor,
+ * then call _update_mapping to patch paddr + checksum. The nullptr child
+ * argument means the child pointer is already correct in the leaf.
+ */
BtreeLBAManager::update_mappings_ret
BtreeLBAManager::update_mappings(
Transaction& t,
});
}
+/**
+ * get_physical_extent_if_live: check if an LBA tree node at (type, paddr,
+ * laddr) is still reachable from the tree root. Used by the cleaner/GC
+ * to decide whether an on-disk node needs to be rewritten or can be
+ * reclaimed. Delegates to btree.get_internal_if_live or get_leaf_if_live.
+ */
BtreeLBAManager::get_physical_extent_if_live_ret
BtreeLBAManager::get_physical_extent_if_live(
Transaction &t,
});
}
+/**
+ * Register Seastar metrics under the "LBA" group: alloc_extents (bytes)
+ * and alloc_extents_iter_nexts (search iterations).
+ */
void BtreeLBAManager::register_metrics(store_index_t store_index)
{
LOG_PREFIX(BtreeLBAManager::register_metrics);
);
}
+/**
+ * _update_mapping: core update primitive.
+ * Creates a partial iterator from the cursor, applies f(old_val) to compute
+ * the new value. If refcount drops to 0 → btree.remove (entry is deleted).
+ * Otherwise → btree.update (in-place value change with CoW).
+ * The LogicalChildNode* is linked as the leaf's child pointer when non-null
+ * and not already tracked.
+ */
BtreeLBAManager::_update_mapping_ret
BtreeLBAManager::_update_mapping(
Transaction &t,
}
}
+/**
+ * scan_mapped_space: two-pass full tree scan for space accounting.
+ *
+ * Pass 1 (data): iterate all leaf entries from L_ADDR_MIN; for each direct
+ * mapping (non-indirect, non-zero paddr), invoke scan_visitor with the
+ * physical address, length, type, and laddr.
+ *
+ * Pass 2 (tree nodes): re-traverse from L_ADDR_MIN with a tree_visitor
+ * callback that fires for every internal and leaf node visited during
+ * the descent. This captures the tree's own metadata space usage.
+ */
BtreeLBAManager::scan_mapped_space_ret
BtreeLBAManager::scan_mapped_space(
Transaction &t,
}
}
+/**
+ * get_containing_cursor: find the mapping whose range [key, key+len)
+ * contains laddr. Uses upper_bound_right(laddr) and checks bounds.
+ * Returns enoent if no mapping spans laddr.
+ */
BtreeLBAManager::get_cursor_ret
BtreeLBAManager::get_containing_cursor(
op_context_t c,
}
#endif
+/**
+ * remap_mappings: split/shrink an existing mapping into multiple pieces
+ * according to the remap entries. Each remap specifies an (offset, length)
+ * sub-range of the original mapping.
+ *
+ * The first remap replaces the original entry (via btree.replace); subsequent
+ * remaps are inserted as new entries (via btree.insert). For indirect
+ * mappings, the local_clone_id is preserved; for direct mappings, the paddr
+ * is adjusted by the sub-range offset. After all modifications, cursors are
+ * refreshed in parallel since inserts may have invalidated earlier ones.
+ */
BtreeLBAManager::remap_ret
BtreeLBAManager::remap_mappings(
Transaction &t,
co_return ret;
}
+/**
+ * update_paddr_sync: synchronous paddr update for a mapping already in cache.
+ * Used when a background rewrite transaction has relocated an extent --
+ * the current transaction's pending leaf already has the entry, and we
+ * just need to patch its paddr. Uses lower_bound_sync (no I/O).
+ */
void BtreeLBAManager::update_paddr_sync(
Transaction &t,
laddr_t laddr,
modification_t::TRANS_SYNC);
}
+// ---------------------------------------------------------------------------
+// Move / clone operations
+// ---------------------------------------------------------------------------
+
+/**
+ * _copy_mapping: copy the mapping at 'src' to 'dest_laddr' without removing
+ * src. Steps:
+ * 1. Build partial iterators for both src and dest cursors.
+ * 2. Determine the pladdr: for indirect → local_clone_id, for direct → paddr.
+ * 3. Register the key copy with the transaction (new_lba_key_copied) so
+ * that if a background rewrite changes src's paddr before commit, the
+ * dest copy gets patched too (via update_paddr_sync callback).
+ * 4. btree.copy() inserts the new entry using src's value.
+ * 5. Refresh src (may have been invalidated by the insert's splits).
+ */
BtreeLBAManager::move_mapping_ret
BtreeLBAManager::_copy_mapping(
op_context_t c,
co_return ret;
}
+/**
+ * _move_mapping: copy src to dest_laddr, then remove src by decrementing
+ * its refcount to 0 (which triggers _update_mapping → btree.remove).
+ * After removal, refreshes dest and advances dest's cursor to the next
+ * entry (so the caller gets the position after the moved mapping).
+ */
BtreeLBAManager::move_mapping_ret
BtreeLBAManager::_move_mapping(
Transaction &t,
co_return ret;
}
+/**
+ * move_and_clone_direct_mapping: copy src to dest_laddr (transferring the
+ * data extent), then convert the original src mapping into an indirect one
+ * that points to the new dest mapping. This is used during snapshot
+ * operations: the data extent lives at the new location, and the old laddr
+ * becomes a clone reference to it.
+ *
+ * After converting src to indirect, its child pointer is reset (the data
+ * extent is now owned by dest, not src).
+ */
BtreeLBAManager::move_mapping_ret
BtreeLBAManager::move_and_clone_direct_mapping(
Transaction &t,
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab
+/**
+ * =============================================================================
+ * BtreeLBAManager - the concrete LBA (Logical Block Address) manager backed
+ * by a wandering B+tree (FixedKVBtree).
+ *
+ * This is the primary address-translation layer in Crimson Seastore. It maps
+ * logical addresses (laddr_t) to physical addresses (paddr_t) and manages the
+ * lifecycle of those mappings: allocation, lookup, update, cloning (indirect
+ * mappings for snapshots), remapping, and removal.
+ *
+ * The underlying tree is an LBABtree = FixedKVBtree<laddr_t, lba_map_val_t,
+ * LBAInternalNode, LBALeafNode, LBACursor, 4096>.
+ *
+ * All operations are transaction-scoped - they read/modify a Transaction and
+ * become visible only after commit. The tree is copy-on-write ("wandering"):
+ * modified nodes are duplicated, never updated in place.
+ *
+ * Key abstractions:
+ * LBACursor - a lightweight handle to a single leaf entry (see lba_btree_node.h)
+ * LBAMapping - a higher-level wrapper combining direct + optional indirect cursors
+ * (see lba_mapping.h)
+ * alloc_mapping_info_t - describes one mapping to be inserted (key, value, extent)
+ * =============================================================================
+ */
+
#pragma once
#include <iostream>
namespace crimson::os::seastore::lba {
class BtreeLBAManager;
+/**
+ * The concrete btree type: keys are laddr_t, values are lba_map_val_t
+ * (length, pladdr, refcount, checksum, extent-type), nodes are 4096 bytes.
+ */
using LBABtree = FixedKVBtree<
laddr_t, lba_map_val_t, LBAInternalNode,
LBALeafNode, LBACursor, LBA_BLOCK_SIZE>;
*
* get_mappings, alloc_extent_*, etc populate a Transaction
* which then gets submitted
+ *
+ * Some additional implementation notes:
+ *
+ * Transaction flow:
+ * 1. Read operations (get_cursor, get_cursors, scan_mappings) traverse the
+ * tree within the transaction's view and return LBACursorRef handles.
+ * 2. Write operations (alloc_extent, reserve_region, clone_mapping, etc.)
+ * produce deltas against tree nodes (CoW) and/or allocate new nodes.
+ * 3. On commit, new/modified tree nodes are written out; the root pointer
+ * is atomically updated.
+ *
+ * Mapping types:
+ * - Direct: laddr → paddr (normal data extent)
+ * - Indirect: laddr → intermediate_laddr (clone/snapshot, points to another
+ * LBA entry that holds the actual paddr)
+ * - Zero: laddr → P_ADDR_ZERO (reserved region, no data yet)
*/
class BtreeLBAManager : public LBAManager {
public:
register_metrics(store_index);
}
+ // ---------------------------------------------------------------------------
+ // Lookup operations - read the LBA tree to find mappings.
+ // All return LBACursorRef handles into the tree within the transaction's view.
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Create the initial empty LBA tree (single empty leaf as root).
+ */
mkfs_ret mkfs(
Transaction &t) final;
+ /**
+ * Find all mappings that overlap the range [offset, offset+length).
+ * Uses upper_bound_right to find the first overlapping entry, then
+ * iterates forward. Returns a list of cursors.
+ */
get_cursors_ret get_cursors(
Transaction &t,
laddr_t offset, extent_len_t length) final;
+ /**
+ * Find the mapping at exactly 'offset', or (if search_containing) the
+ * mapping whose range contains 'offset'. Returns enoent if not found.
+ */
get_cursor_ret get_cursor(
Transaction &t,
laddr_t offset,
bool search_containing = false) final;
+ /**
+ * Find the mapping for a known LogicalChildNode by navigating up from
+ * the extent to its parent leaf node (avoids a full tree traversal).
+ */
get_cursor_ret get_cursor(
Transaction &t,
LogicalChildNode &extent) final;
+ /**
+ * Standard btree lower_bound - returns cursor to first entry >= laddr.
+ */
lower_bound_ret lower_bound(
Transaction &t,
laddr_t laddr) final;
+ // ---------------------------------------------------------------------------
+ // Allocation operations - insert new mappings into the LBA tree.
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Reserve a region at a specific laddr (inserts a zero-mapping with
+ * P_ADDR_ZERO). 'pos' is a cursor used as an insertion hint.
+ */
alloc_extent_ret reserve_region(
Transaction &t,
LBACursorRef pos,
extent_len_t len,
extent_types_t type) final;
+ /**
+ * Reserve a region using a hint-based address search. Delegates to
+ * alloc_contiguous_mappings with a single zero-mapping info.
+ */
alloc_extent_ret reserve_region(
Transaction &t,
laddr_hint_t hint,
co_return std::move(cursors.front());
}
+ // ---------------------------------------------------------------------------
+ // Clone / move operations - support for snapshots and defragmentation.
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Create an indirect mapping (clone) at 'laddr' that points to 'inter_key'
+ * within the direct mapping 'mapping'. Optionally increments the refcount
+ * of the target mapping (updateref=true).
+ * 'pos' is used as an insertion hint for the new indirect entry.
+ */
clone_mapping_ret clone_mapping(
Transaction &t,
LBACursorRef pos,
extent_len_t len,
bool updateref) final;
+ /**
+ * Move an indirect mapping: copy src to dest_laddr, then remove src.
+ */
move_mapping_ret move_indirect_mapping(
Transaction &t,
LBACursorRef src,
t, std::move(src), dest_laddr, std::move(dest), nullptr);
}
+ /**
+ * Move a direct mapping: copy src to dest_laddr (re-linking the data
+ * extent), then remove src.
+ */
move_mapping_ret move_direct_mapping(
Transaction &t,
LBACursorRef src,
t, std::move(src), dest_laddr, std::move(dest), &extent);
}
+ /**
+ * Move a direct mapping and set up a clone: copies src to dest, then
+ * converts the original src mapping into an indirect mapping pointing
+ * at the new location. Used during snapshot operations.
+ */
move_mapping_ret move_and_clone_direct_mapping(
Transaction &t,
LBACursorRef src,
get_end_mapping_ret get_end_mapping(Transaction &t) final;
#endif
+ /**
+ * Insert mappings for a vector of extents at their pre-assigned laddrs,
+ * using 'pos' as a btree insertion hint. Inserts in reverse order for
+ * efficiency (each insertion stays near the hint).
+ */
alloc_extents_ret alloc_extents(
Transaction &t,
LBACursorRef pos,
std::vector<LogicalChildNodeRef> ext) final;
+ /**
+ * Allocate a single extent: finds a free laddr near 'hint', inserts the
+ * mapping (laddr -> ext.paddr), and assigns the laddr to the extent.
+ * Delegates to alloc_contiguous_mappings with a single direct mapping info.
+ */
alloc_extent_ret alloc_extent(
Transaction &t,
laddr_hint_t hint,
co_return std::move(cursors.front());
}
+ /**
+ * Allocate multiple extents. If extents already have laddrs assigned
+ * (has_laddr), uses alloc_sparse_mappings (each at its own address).
+ * Otherwise, uses alloc_contiguous_mappings (packed sequentially from
+ * a single hint-based starting point).
+ */
alloc_extents_ret alloc_extents(
Transaction &t,
laddr_hint_t hint,
co_return std::vector<LBACursorRef>(cursors.begin(), cursors.end());
}
+ // ---------------------------------------------------------------------------
+ // Update operations - modify existing mappings in the tree.
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Adjust a mapping's refcount by 'delta'. If refcount reaches 0,
+ * _update_mapping removes the entry entirely.
+ */
base_iertr::future<LBACursorRef> update_mapping_refcount(
Transaction &t,
LBACursorRef cursor,
);
}
+ /**
+ * Split or shrink an existing mapping according to the remap entries.
+ * Used by the remap_pin flow to adjust mapping boundaries (e.g. when
+ * an extent is partially overwritten).
+ */
remap_ret remap_mappings(
Transaction &t,
LBACursorRef mapping,
std::vector<remap_entry_t> remaps) final;
+ // ---------------------------------------------------------------------------
+ // Extent lifecycle - cache warm-up, GC, and commit-time updates.
+ // ---------------------------------------------------------------------------
+
/**
* init_cached_extent
*
*
* Returns if e is live.
*/
+ /**
+ * For logical extents: looks up e's laddr and checks if the tree entry
+ * points to e's paddr. If live, links the extent into the parent leaf's
+ * child-tracking array.
+ * For tree nodes (internal/leaf): delegates to LBABtree::init_cached_extent.
+ */
init_cached_extent_ret init_cached_extent(
Transaction &t,
CachedExtentRef e) final;
check_child_trackers_ret check_child_trackers(Transaction &t) final;
#endif
+ /**
+ * Iterate all direct mappings in [begin, end) and invoke f for each.
+ * Skips indirect mappings.
+ */
scan_mappings_ret scan_mappings(
Transaction &t,
laddr_t begin,
laddr_t end,
scan_mappings_func_t &&f) final;
+ /**
+ * GC/cleaner entry point: relocate an LBA tree node (internal or leaf)
+ * to a new segment. Delegates to LBABtree::rewrite_extent which
+ * allocates a new copy and patches the parent pointer. Skips non-LBA
+ * extents.
+ */
rewrite_extent_ret rewrite_extent(
Transaction &t,
CachedExtentRef extent) final;
+ /**
+ * Update a mapping's paddr, length, and checksum after a data extent
+ * has been rewritten (e.g. during commit when extents move from
+ * initial-write segment to their final location). Validates that the
+ * old paddr/length match before updating.
+ */
update_mapping_ret update_mapping(
Transaction& t,
LBACursorRef cursor,
paddr_t prev_addr,
LogicalChildNode&) final;
+ /**
+ * Batch version of update_mapping for multiple extents. For each
+ * extent, navigates to its parent leaf (via get_parent_node) and
+ * updates paddr + checksum.
+ */
update_mappings_ret update_mappings(
Transaction& t,
const std::list<LogicalChildNodeRef>& extents);
+ /**
+ * GC helper: check if a tree node at (type, paddr, laddr) is still
+ * live in the tree. Returns the extent if live, null otherwise.
+ */
get_physical_extent_if_live_ret get_physical_extent_if_live(
Transaction &t,
extent_types_t type,
laddr_t laddr,
extent_len_t len) final;
+ /**
+ * Full tree scan: iterate every mapping from L_ADDR_MIN to end,
+ * invoking f with (laddr, paddr, len, type) for each entry plus
+ * (paddr, len) for each internal tree node. Used for space accounting.
+ */
scan_mapped_space_ret scan_mapped_space(
Transaction &t,
scan_mapped_space_func_t &&f) final;
private:
Cache &cache;
+ /**
+ * Performance counters registered as Seastar metrics under the "LBA" group.
+ * num_alloc_extents: total bytes allocated via alloc_extent paths
+ * num_alloc_extents_iter_nexts: total btree iterator steps during
+ * search_insert_position (measures conflict
+ * resolution cost when hints collide)
+ */
struct {
uint64_t num_alloc_extents = 0;
uint64_t num_alloc_extents_iter_nexts = 0;
} stats;
+ /**
+ * Describes one mapping to be inserted into the tree. Used by
+ * alloc_contiguous_mappings, alloc_sparse_mappings, and insert_mappings.
+ *
+ * Three factory methods produce the three mapping flavors:
+ * create_zero - reserved region, paddr = P_ADDR_ZERO, no data extent
+ * create_indirect - clone entry, pladdr holds a local_clone_id pointing to
+ * the direct mapping that owns the physical data
+ * create_direct - normal mapping, pladdr holds the paddr, 'extent'
+ * points to the in-memory data extent for child-tracking
+ */
struct alloc_mapping_info_t {
laddr_t key = L_ADDR_NULL; // once assigned, the allocation to
// key must be exact and successful
}
};
+ /**
+ * Bundle cache + transaction into the op_context_t passed throughout
+ * the btree operations.
+ */
op_context_t get_context(Transaction &t) {
return op_context_t{cache, t};
}
seastar::metrics::metric_group metrics;
void register_metrics(store_index_t store_index);
+ // -------------------------------------------------------------------------
+ // Internal helpers for move/copy operations.
+ // -------------------------------------------------------------------------
+
/*
* _move_mapping
*
* copy the mapping "src" to "dest" and remove the "src" mapping.
+ * If extent != null (direct mapping), re-links the data extent to the
+ * new mapping.
*
* Return: the mappings next to "src" and the "dest" mapping
*/
/*
* _copy_mapping
+ * The data extent (if any) is linked to the new destination entry.
*
* copy the mapping "src" to "dest", the extent attached to
* "src" will also be attached to the dest. This is the building
* This is basically for updating the paddr of the mapping
* that has been copied by the transaction t and modified
* by some background rewrite transaction.
+ *
+ * Synchronous - requires the leaf to already be cached.
*/
void update_paddr_sync(
Transaction &t,
* _update_mapping
*
* Updates mapping, removes if f returns nullopt
+ *
+ * Core update primitive. Applies f(old_val) -> new_val on the mapping
+ * at cursor's position. If the resulting refcount is 0, removes the
+ * entry (btree.remove). Otherwise, updates in place (btree.update).
+ *
+ * Used by update_mapping_refcount, update_mapping (paddr change), and
+ * remap_mappings. The LogicalChildNode* parameter, when non-null and
+ * not yet tracked, is linked as the leaf's child pointer for the entry.
*/
using _update_mapping_ret = ref_iertr::future<LBACursorRef>;
using update_func_t = std::function<
update_func_t f,
LogicalChildNode*);
+ // -------------------------------------------------------------------------
+ // Address allocation - finding free space in the logical address range.
+ // -------------------------------------------------------------------------
+
+ /**
+ * Result of search_insert_position: the chosen laddr and a btree iterator
+ * positioned just past it (suitable as an insertion hint).
+ */
struct insert_position_t {
laddr_t laddr;
LBABtree::iterator insert_iter;
};
+
+ /**
+ * Find a free laddr near 'hint' that doesn't conflict with existing
+ * mappings. Uses upper_bound_right + forward scan (linear or random
+ * retry depending on hint.policy) to skip past occupied regions.
+ * Tracks iteration cost in stats.num_alloc_extents_iter_nexts.
+ */
using search_insert_position_iertr = base_iertr;
using search_insert_position_ret =
search_insert_position_iertr::future<insert_position_t>;
using alloc_mappings_iertr = base_iertr;
using alloc_mappings_ret =
alloc_mappings_iertr::future<std::list<LBACursorRef>>;
+
/**
* alloc_contiguous_mappings
*
LBABtree::iterator iter,
std::vector<alloc_mapping_info_t> &alloc_infos);
+ // -------------------------------------------------------------------------
+ // Internal lookup helpers (take op_context + btree, avoid redundant root fetch).
+ // -------------------------------------------------------------------------
+
+ /**
+ * Exact-match lookup: lower_bound(offset), return enoent if no match.
+ */
get_cursor_ret get_cursor(
op_context_t c,
LBABtree& btree,
laddr_t offset);
+ /**
+ * Containing-match: upper_bound_right(laddr) to find the mapping whose
+ * range [key, key+len) contains laddr.
+ */
get_cursor_ret get_containing_cursor(
op_context_t c,
LBABtree &btree,
laddr_t laddr);
+ /**
+ * Range lookup: upper_bound_right + iterate while key < offset+length.
+ */
get_cursors_ret get_cursors(
op_context_t c,
LBABtree& btree,
laddr_t offset,
extent_len_t length);
+ /**
+ * Resolve an indirect cursor to its target direct cursor. An indirect
+ * mapping stores a local_clone_id; get_intermediate_key() reconstructs
+ * the full laddr of the direct mapping. get_cursors() on that key
+ * returns the single direct cursor that owns the physical data.
+ */
using resolve_indirect_cursor_ret = base_iertr::future<LBACursorRef>;
resolve_indirect_cursor_ret resolve_indirect_cursor(
op_context_t c,
LBABtree& btree,
const LBACursor& indirect_cursor);
+ /**
+ * Convenience overload that fetches the btree internally.
+ */
resolve_indirect_cursor_ret resolve_indirect_cursor(
op_context_t c,
const LBACursor& indirect_cursor) {