The B-tree is the workhorse of persistent indexing, underpinning nearly every relational database, filesystem, and key-value store of consequence. Its logarithmic access properties are well understood in the single-threaded case. The moment concurrency enters the picture, however, the analysis shifts from balanced-tree invariants to a more subtle question: how do we permit many threads to traverse and modify a shared structure without corrupting it, and without serializing them into irrelevance?
Naive solutions collapse quickly. A single tree-wide latch reduces throughput to that of one CPU. Per-node latches acquired in root-to-leaf order without discipline invite deadlock, priority inversion, and pathological contention at the root—precisely the node every operation must touch. The design space demands protocols that reason formally about which invariants each latch protects and when those protections can safely be relinquished.
This article examines three cornerstones of concurrent B-tree design: the latch crabbing protocol that governs safe traversal, the B-link tree refinement that decouples readers from writers, and the treatment of structure modification operations—splits and merges—that must remain atomic with respect to concurrent observers. Each represents a distinct point on the trade-off surface between latch scope, contention, and code complexity, and each has direct analogs in production systems from PostgreSQL to modern LSM-augmented engines.
The Crabbing Protocol
Latch crabbing—so named for the sideways motion of releasing one latch as another is acquired—is the foundational protocol for concurrent B-tree traversal. The invariant it enforces is straightforward: a thread must hold a latch on a node whose contents it depends on, and it must release that latch only when it can prove the parent's structure will not change in a way that affects the descent.
For a read operation, the protocol descends root-to-leaf acquiring shared (S) latches. At each step, once the child has been latched, the parent latch is released. The proof of correctness rests on the observation that a reader never modifies the tree; having safely reached the child, the parent's subsequent evolution is irrelevant to this traversal.
Write operations require exclusive (X) latches and a more nuanced release condition. A node is termed safe if the pending operation cannot propagate structural change upward through it—concretely, a node is safe for insertion if it has room for another key, and safe for deletion if it has more than the minimum occupancy. When the descent encounters a safe child, all ancestor latches held above may be released, since no split or merge can reach them.
The complexity analysis is instructive. In the expected case, B-tree nodes are well-populated and most inserts terminate at a safe leaf, meaning ancestor latches are released almost immediately after acquisition. The amortized cost approaches that of latching a single node per level of descent, with contention concentrated only during the rare structural events.
The pessimistic variant—acquiring X latches on the entire descent path—guarantees correctness but destroys concurrency near the root. Modern implementations therefore employ optimistic crabbing: descend with S latches, and on reaching the leaf, upgrade to X. If the leaf proves unsafe, restart the descent with pessimistic X latches. This exploits the statistical rarity of splits to keep the common path lock-friendly.
TakeawayA latch protects an invariant, not a node. The moment you can prove the invariant no longer constrains you, holding the latch is pure contention with no correctness dividend.
B-Link Trees
Lehman and Yao's 1981 B-link tree refinement addresses a persistent weakness of crabbing: readers, though holding only shared latches, still participate in the latching protocol and thus contend with writers on interior nodes. The B-link modification eliminates this contention almost entirely by adding a single structural element—a right-sibling pointer at every level—and a corresponding high-key field indicating the largest key reachable through this subtree.
The critical insight is that a split, viewed atomically, can be decomposed into two independent steps: first, install the new right sibling and update the original node's high key and sibling pointer; second, insert the separator key into the parent. Between these two steps, the tree is in a consistent but temporarily incomplete state. A reader arriving at the original node and finding its target key exceeds the local high key simply follows the sibling pointer rightward until the correct node is located.
This transforms the concurrency model. Readers acquire no latches on interior nodes at all; they read pointers optimistically and rely on the right-link fallback to recover from any split that occurred mid-descent. Writers latch only the nodes they modify, in a well-defined order that avoids deadlock: bottom-up, with each level's latch acquired before the parent's.
The performance implications are substantial. Root-level contention, the classical bottleneck of concurrent B-trees, is effectively eliminated for read workloads. Analytical models and empirical measurements alike show throughput scaling near-linearly with core count on read-heavy workloads, whereas crabbing implementations plateau as root latch contention dominates.
The cost is complexity in the write path and in recovery. Every operation must be prepared to handle a partial split, and formal verification of B-link tree implementations has proven notoriously subtle—Lamport-style invariant proofs are essentially mandatory. The trade-off, however, is clearly favorable for systems where read throughput is the binding constraint.
TakeawayConcurrency is often unlocked not by finer locks but by structural redundancy that lets observers recover from staleness without coordination.
Structure Modification Operations
Splits and merges—collectively, structure modification operations (SMOs)—are the hard cases of concurrent B-tree design. Unlike point inserts or lookups, an SMO alters the shape of the tree, touching multiple nodes and potentially propagating through several levels. Any protocol must ensure that concurrent traversals observe either the pre-SMO or post-SMO state, never a partial one.
The pessimistic approach couples SMOs to the crabbing protocol: a writer that detects an unsafe leaf restarts its descent holding X latches on the entire path from the highest unsafe ancestor down. This guarantees atomicity at the cost of latching an O(log n) prefix of the tree, blocking all other operations that transit those nodes for the duration of the split.
Modern implementations favor optimistic SMO protocols that localize the modification. In the B-link approach, a split is a two-phase local operation: allocate and populate the sibling, then atomically swing the original node's high key and right-link. Only two nodes are latched, and the parent update is deferred and can be performed lazily by any subsequent traversal that notices the missing separator.
The complexity analysis reveals why this matters. Under uniform workloads, the probability that a given insert triggers a split is approximately 1/B where B is the branching factor—typically several hundred. Splits that propagate to the second level are rarer by another factor of B, and multi-level cascades are vanishingly rare. Optimizing for the O(1/B) case at the expense of the common path is precisely the wrong trade-off.
Merges present symmetric but harder problems. Because deletion policies vary and underfull nodes can often defer merging without semantic consequence, many production systems—PostgreSQL notably—omit merges entirely, accepting some space waste in exchange for eliminating an entire class of concurrent SMO. This is a defensible engineering choice: the theoretical complexity is preserved amortized, while the concurrency protocol is dramatically simplified.
TakeawayOptimize the common path aggressively and let the rare path pay the coordination cost. Amortized analysis rewards this asymmetry more often than intuition suggests.
Concurrent B-tree design illustrates a broader principle in systems engineering: correctness protocols and performance protocols are the same protocol, viewed from different sides. Crabbing, B-link pointers, and optimistic SMOs are not distinct techniques but points on a continuum defined by which invariants are enforced synchronously and which are permitted to lag.
The progression from tree-wide latches to fine-grained crabbing to B-link's lock-free readers mirrors a recurring pattern in concurrent data structure design: replace coordination with recovery. Each generation of the protocol tolerates more transient inconsistency in exchange for less synchronization, relying on structural redundancy and careful ordering to converge to correctness.
For the practitioner, the takeaway is methodological. Before adding a latch, articulate precisely which invariant it protects and for how long that protection is required. Most contention in concurrent systems is not fundamental but incidental—latches held past their semantic necessity, waiting to be reclaimed by a more careful analysis.