bplus-index
Arena-backed B+Tree for in-memory sorted indexes in Rust.
Designed for data-oriented architectures where you need secondary indexes on
tables backed by slotmaps or ECS-style storage. Supports non-unique keys
natively -- the (K, V) pair is the unique identity of each entry.
Features
- Arena storage -- all nodes live in contiguous
Vecs indexed byu32. NoBox, no heap allocation per node, no pointer chasing between siblings. - Cache-friendly -- each leaf holds up to 64 entries in inline
ArrayVecs. Range scans follow a doubly-linked leaf chain with sequential memory access. - Non-unique keys -- first-class support for one-to-many and many-to-many
secondary indexes. Inner node separators store
(K, V)for correct routing. - Zero
unsafe-- built onarrayvec. No raw pointer manipulation. - Minimal API --
insert,delete,update,lookup_unique,lookup_range,range,iter. One struct covers unique, 1:N, and N:N indexes depending on the choice of K and V.
Complexity
| Operation | Time |
|---|---|
insert |
O(log n) |
delete |
O(log n) |
update |
O(log n) |
lookup_unique |
O(log n) |
lookup_range |
O(log n + k) |
range |
O(log n + k) |
iter |
O(n) |
Where n is the total number of entries and k is the number of results.
Usage
[]
= "0.1"
Unique index
use BPlusIndex;
let mut idx = new;
idx.insert; // key=42, row_id=1001
assert_eq!;
Non-unique 1:N index
use BPlusIndex;
// One score -> many player ids
let mut idx = new;
idx.insert;
idx.insert;
idx.insert;
let players: = idx.lookup_range.copied.collect;
assert_eq!;
Composite key for N:N index
use BPlusIndex;
// (game_mode, mmr) -> ticket_id
let mut idx = new;
idx.insert;
idx.insert;
idx.insert;
idx.insert;
// Range scan: mode 1, mmr 1200..1400
let tickets: = idx.range
.map
.collect;
assert_eq!;
Update (re-index)
use BPlusIndex;
let mut idx = new;
idx.insert;
// Player 42's score changed from 1500 to 1600
idx.update;
assert_eq!;
assert_eq!;
Benchmarks
Compared against std::collections::BTreeMap with i64 keys and u64 values,
random insertion order.
Measured on Intel Core i7-10870H (8C/16T, 2.20 GHz)
running Fedora Linux 43. Run with cargo bench.
| Operation | Size | BPlusIndex | BTreeMap | Speedup |
|---|---|---|---|---|
| insert | 1K | 240 us | 338 us | 1.4x |
| insert | 10K | 3.3 ms | 4.8 ms | 1.4x |
| insert | 100K | 42 ms | 68 ms | 1.6x |
| lookup | 1K | 59 us | 91 us | 1.5x |
| lookup | 10K | 969 us | 1.98 ms | 2.0x |
| lookup | 100K | 16.4 ms | 31.9 ms | 1.9x |
| range scan | 100K | 402 us | 1.32 ms | 3.3x |
| delete | 1K | 228 us | 232 us | ~1x |
| delete | 10K | 3.1 ms | 3.2 ms | ~1x |
| delete | 100K | 43 ms | 49 ms | 1.1x |
| iter | 100K | 338 us | 1.01 ms | 3.0x |
Range scans and full iteration show the largest gains (3x) thanks to the linked leaf chain and sequential memory layout.
Lookups benefit from the flat arena reducing cache misses during descent (2x at scale).
Deletes are roughly on par since rebalancing logic dominates over memory access patterns
Design notes
Why not BTreeMap?
BTreeMap is a B-Tree (not B+Tree) -- values are stored in both leaf and
internal nodes, and there is no leaf chain. Range scans require climbing back
up the tree to visit the next subtree. Each node is a separate heap allocation,
so sibling traversal involves pointer chasing.
BPlusIndex stores values only in leaves, which are linked for O(1) sibling
traversal. Nodes live in flat Vecs for better memory locality.
Why (K, V) separators in inner nodes?
Standard B+Trees use K-only separators, which breaks down when the same key
appears in multiple leaves. A query for key K would descend into one subtree
and miss entries in adjacent subtrees. By using (K, V) as the separator, the
routing is always unambiguous regardless of key cardinality.
Node recycling
Deleted nodes are not deallocated. Their indices are pushed onto a free list and reused by subsequent allocations. This avoids Vec fragmentation and keeps arena indices stable.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.