From: Ronen Friedman Date: Wed, 1 Jul 2026 03:55:19 +0000 (+0000) Subject: crimson/os/seastore: documenting the LBA manager X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6f3fa9a289f7b97ffc92dcbc624ee49e786e9d29;p=ceph.git crimson/os/seastore: documenting the LBA manager documenting the LBA manager's internal structure and operations. Assisted-by: Claude Signed-off-by: Ronen Friedman --- diff --git a/src/crimson/os/seastore/btree/fixed_kv_btree.h b/src/crimson/os/seastore/btree/fixed_kv_btree.h index fdfa31230ab..3c6149ba4b6 100644 --- a/src/crimson/os/seastore/btree/fixed_kv_btree.h +++ b/src/crimson/os/seastore/btree/fixed_kv_btree.h @@ -1,6 +1,32 @@ // -*- 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 @@ -30,6 +56,12 @@ enum modification_t { // 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 phy_tree_root_t& get_phy_tree_root(root_t& r); @@ -46,9 +78,26 @@ const get_phy_tree_root_node_ret get_phy_tree_root_node( 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 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, @@ -71,12 +120,35 @@ public: class iterator; using iterator_fut = base_iertr::future; + /** + * 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, 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 for the leaf level and a stack of + * node_position_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 @@ -110,6 +182,11 @@ public: } #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 @@ -139,6 +216,12 @@ public: } + /** + * 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 @@ -148,6 +231,7 @@ public: auto ret = *this; + // Fast path: still within the same leaf node. if (ret.leaf.pos > 0) { ret.leaf.pos--; return iterator_fut( @@ -155,6 +239,10 @@ public: 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), @@ -168,11 +256,13 @@ public: 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 @@ -219,6 +309,13 @@ public: 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) { @@ -271,10 +368,20 @@ public: }); } + /** + * 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(); @@ -328,6 +435,11 @@ public: friend class FixedKVBtree; static constexpr uint16_t INVALID = std::numeric_limits::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 struct node_position_t { typename NodeType::Ref node; @@ -349,6 +461,13 @@ public: 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, MAX_DEPTH> internal; node_position_t leaf; @@ -361,6 +480,12 @@ public: 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; template @@ -395,6 +520,14 @@ public: }); } + /** + * 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( @@ -435,6 +568,13 @@ public: }); } + /** + * 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; check_split_ret check_split(op_context_t c) { @@ -462,8 +602,16 @@ public: } }; + /** + * 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(root_block->get_root()); } @@ -472,21 +620,35 @@ public: return get_phy_tree_root(root_block->get_root()); } + /** + * Link a new root node into the RootBlock's parent-tracking system. + */ template void set_root_node(const TCachedExtentRef &root_node) { static_assert(std::is_base_of_v); TreeRootLinker::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(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(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()); @@ -505,6 +667,11 @@ public: 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) @@ -540,6 +707,11 @@ public: 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) @@ -601,7 +773,19 @@ public: } /** - * 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 @@ -657,6 +841,8 @@ public: /** * upper_bound * + * Implemented as lower_bound + skip-if-exact-match. + * * @param c [in] context * @param addr [in] ddr * @return least iterator > key @@ -679,7 +865,16 @@ public: } /** - * 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 @@ -721,6 +916,19 @@ public: return upper_bound(c, min_max_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 ::type = 0> @@ -897,6 +1105,13 @@ public: } #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 @@ -944,7 +1159,19 @@ public: * 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 @@ -1020,8 +1247,8 @@ public: * 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 @@ -1092,6 +1319,9 @@ public: }); } + /** + * Convenience overload: perform a lower_bound lookup first, then insert. + */ insert_ret insert( op_context_t c, node_key_t laddr, @@ -1107,6 +1337,11 @@ public: /** * 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 @@ -1156,6 +1391,11 @@ public: * 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 @@ -1237,6 +1477,16 @@ public: /** * 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 @@ -1287,14 +1537,19 @@ public: }); }); } - + /** * 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; @@ -1366,7 +1621,11 @@ public: } } - /// 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; get_leaf_if_live_ret get_leaf_if_live( @@ -1404,7 +1663,10 @@ public: } - /// 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; get_internal_if_live_ret get_internal_if_live( @@ -1446,10 +1708,19 @@ public: /** - * 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<>; @@ -1458,7 +1729,7 @@ public: 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 @@ -1469,14 +1740,14 @@ public: // 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, @@ -1488,7 +1759,7 @@ public: c.cache.retire_extent(c.trans, e); }); }; - + if (e->get_type() == internal_node_t::TYPE) { auto lint = e->cast(); return do_rewrite(*lint); @@ -1499,6 +1770,19 @@ public: } } + /** + * 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 @@ -1619,8 +1903,12 @@ public: 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, @@ -1651,6 +1939,18 @@ private: template using node_position_t = typename iterator::template node_position_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; static get_internal_node_ret get_internal_node( @@ -1738,6 +2038,10 @@ private: } + /** + * 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; static get_leaf_node_ret get_leaf_node( @@ -1820,6 +2124,14 @@ private: }); } + /** + * 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( @@ -1907,6 +2219,18 @@ private: } } + /** + * 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 @@ -1982,6 +2306,11 @@ private: }); } + /** + * 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 @@ -2055,12 +2384,17 @@ private: } /** - * 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<>; @@ -2113,6 +2447,21 @@ private: }); } + /** + * 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; template @@ -2172,7 +2521,9 @@ private: } /** - * 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)). @@ -2181,6 +2532,14 @@ private: * 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<>; @@ -2229,6 +2588,10 @@ private: * 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<>; @@ -2361,6 +2724,20 @@ private: } + /** + * 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( @@ -2479,6 +2856,20 @@ private: 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 handle_merge_ret merge_level( op_context_t c, @@ -2622,9 +3013,20 @@ private: } }; + +// ============================================================================= +// Type trait and free-function helpers for working with FixedKVBtree instances. +// ============================================================================= + +/** + * is_fixed_kv_tree::value is true for FixedKVBtree instantiations. + */ template struct is_fixed_kv_tree : std::false_type {}; +/** + * Synchronous btree construction - asserts root is already cached. + */ template tree_type_t get_btree_sync(op_context_t c) { assert(!c.trans.peek_root()->is_pending_io()); @@ -2632,6 +3034,9 @@ tree_type_t get_btree_sync(op_context_t c) { return tree_type_t(root); } +/** + * Async btree construction - fetches the root block from cache (may do I/O). + */ template Cache::get_root_iertr::future get_btree(op_context_t c) { @@ -2664,6 +3069,10 @@ get_btree(Cache &cache, 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, @@ -2683,6 +3092,12 @@ auto with_btree( }); } +/** + * 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, diff --git a/src/crimson/os/seastore/lba/btree_lba_manager.cc b/src/crimson/os/seastore/lba/btree_lba_manager.cc index 124aa1a4da2..480d40f36db 100644 --- a/src/crimson/os/seastore/lba/btree_lba_manager.cc +++ b/src/crimson/os/seastore/lba/btree_lba_manager.cc @@ -19,6 +19,12 @@ SET_SUBSYS(seastore_lba); * - 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 @@ -35,8 +41,15 @@ template <> struct fmt::formatter< } }; +// ------------------------------------------------------------------------- +// 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 → Transaction::lba_tree_stats */ template Transaction::tree_stats_t& get_tree_stats(Transaction &t) { @@ -48,6 +61,7 @@ get_tree_stats< crimson::os::seastore::lba::LBABtree>( Transaction &t); +/** get_phy_tree_root → root_t::lba_root (paddr + depth of tree root) */ template phy_tree_root_t& get_phy_tree_root(root_t &r) { @@ -58,6 +72,11 @@ template phy_tree_root_t& 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>( @@ -77,6 +96,13 @@ CachedExtentRef get_phy_tree_root_node_sync< return ret; } +/** + * Async root-node fetch: returns (found, future). 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>( @@ -104,6 +130,12 @@ const get_phy_tree_root_node_ret get_phy_tree_root_node< } } +/** + * 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 class TreeRootLinker { public: @@ -124,6 +156,13 @@ template class TreeRootLinker; 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) @@ -136,6 +175,10 @@ BtreeLBAManager::mkfs( 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, @@ -150,6 +193,9 @@ BtreeLBAManager::get_cursors( 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, @@ -172,6 +218,11 @@ BtreeLBAManager::get_cursor( } } +/** + * 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, @@ -204,6 +255,11 @@ BtreeLBAManager::get_cursor( 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, @@ -232,6 +288,11 @@ BtreeLBAManager::get_cursors( 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, @@ -256,6 +317,7 @@ BtreeLBAManager::resolve_indirect_cursor( }); } +/** lower_bound: simple btree lower_bound, returns cursor at first entry >= laddr. */ BtreeLBAManager::lower_bound_ret BtreeLBAManager::lower_bound( Transaction &t, @@ -267,6 +329,11 @@ BtreeLBAManager::lower_bound( 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, @@ -296,6 +363,12 @@ BtreeLBAManager::reserve_region( 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, @@ -340,6 +413,12 @@ BtreeLBAManager::alloc_extents( 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, @@ -383,6 +462,14 @@ BtreeLBAManager::clone_mapping( 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, @@ -405,6 +492,26 @@ BtreeLBAManager::get_cursor( }); } +// --------------------------------------------------------------------------- +// 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, @@ -500,6 +607,12 @@ BtreeLBAManager::search_insert_position( 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, @@ -532,6 +645,12 @@ BtreeLBAManager::alloc_contiguous_mappings( }); } +/** + * 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, @@ -570,6 +689,18 @@ BtreeLBAManager::alloc_sparse_mappings( }); } +/** + * 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, @@ -634,11 +765,21 @@ BtreeLBAManager::insert_mappings( }); } +// --------------------------------------------------------------------------- +// 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, @@ -709,6 +850,10 @@ BtreeLBAManager::check_child_trackers( } #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, @@ -744,6 +889,12 @@ BtreeLBAManager::scan_mappings( }); } +/** + * 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, @@ -771,6 +922,15 @@ BtreeLBAManager::rewrite_extent( } } +// --------------------------------------------------------------------------- +// 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, @@ -816,6 +976,12 @@ BtreeLBAManager::update_mapping( 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, @@ -882,6 +1048,12 @@ BtreeLBAManager::update_mappings( }); } +/** + * 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, @@ -909,6 +1081,10 @@ BtreeLBAManager::get_physical_extent_if_live( }); } +/** + * 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); @@ -934,6 +1110,14 @@ void BtreeLBAManager::register_metrics(store_index_t store_index) ); } +/** + * _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, @@ -972,6 +1156,17 @@ BtreeLBAManager::_update_mapping( } } +/** + * 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, @@ -1026,6 +1221,11 @@ BtreeLBAManager::scan_mapped_space( } } +/** + * 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, @@ -1064,6 +1264,17 @@ BtreeLBAManager::get_end_mapping( } #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, @@ -1143,6 +1354,12 @@ BtreeLBAManager::remap_mappings( 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, @@ -1177,6 +1394,21 @@ void BtreeLBAManager::update_paddr_sync( 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, @@ -1228,6 +1460,12 @@ BtreeLBAManager::_copy_mapping( 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, @@ -1261,6 +1499,16 @@ BtreeLBAManager::_move_mapping( 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, diff --git a/src/crimson/os/seastore/lba/btree_lba_manager.h b/src/crimson/os/seastore/lba/btree_lba_manager.h index 3d715d4b308..8b150d95e25 100644 --- a/src/crimson/os/seastore/lba/btree_lba_manager.h +++ b/src/crimson/os/seastore/lba/btree_lba_manager.h @@ -1,6 +1,31 @@ // -*- 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. + * + * 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 @@ -30,6 +55,10 @@ class LogicalCachedExtent; 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>; @@ -50,6 +79,22 @@ using LBABtree = FixedKVBtree< * * 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: @@ -59,26 +104,58 @@ 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, @@ -86,6 +163,10 @@ public: 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, @@ -100,6 +181,16 @@ public: 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, @@ -109,6 +200,9 @@ public: 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, @@ -119,6 +213,10 @@ public: 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, @@ -130,6 +228,11 @@ public: 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, @@ -141,11 +244,21 @@ public: 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 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, @@ -170,6 +283,12 @@ public: 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, @@ -211,6 +330,14 @@ public: co_return std::vector(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 update_mapping_refcount( Transaction &t, LBACursorRef cursor, @@ -230,11 +357,20 @@ public: ); } + /** + * 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 remaps) final; + // --------------------------------------------------------------------------- + // Extent lifecycle - cache warm-up, GC, and commit-time updates. + // --------------------------------------------------------------------------- + /** * init_cached_extent * @@ -243,6 +379,12 @@ public: * * 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; @@ -251,16 +393,32 @@ public: 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, @@ -268,10 +426,19 @@ public: 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& 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, @@ -279,6 +446,11 @@ public: 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; @@ -286,11 +458,29 @@ public: 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 @@ -349,6 +539,10 @@ private: } }; + /** + * 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}; } @@ -356,10 +550,16 @@ private: 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 */ @@ -372,6 +572,7 @@ private: /* * _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 @@ -393,6 +594,8 @@ private: * 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, @@ -404,6 +607,14 @@ private: * _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; using update_func_t = std::function< @@ -415,10 +626,25 @@ private: 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; @@ -431,6 +657,7 @@ private: using alloc_mappings_iertr = base_iertr; using alloc_mappings_ret = alloc_mappings_iertr::future>; + /** * alloc_contiguous_mappings * @@ -475,28 +702,51 @@ private: LBABtree::iterator iter, std::vector &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; 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) {