masstree
A high-performance concurrent ordered map for Rust. It stores keys as &[u8] and supports variable-length keys by building a trie of B+trees, based on the Masstree paper
Disclaimer: This is an independent implementation. It is not endorsed by, affiliated with, or connected to the original Masstree authors or their institutions.
Status
Current Version: v0.9.1
Significantly reduced duplicate code through trait based abstractions, while improving performance further. All standard data structure features are implemented and streamlined, on top the specialized features like range scans and prefix queries. From latency analysis, it seems to have a surprisingly strong tail latency profile for a concurrent ordered map. The cache-line aligned layout design and aggressive prefetching seems to have paid off, on top of masstree's original excellent architecture.
The next issues to work on are the memory profile and memcomparable mappings for general key types, before 1.0.
vs Rust Concurrent Maps (12T SMT)
Source:
runs/run199_read_write.txt(masstree),runs/run184_read_write_reworked.txt(competitors) Config: 12 threads on 6 physical cores (SMT/hyperthreading), 200 samples.
| Benchmark | masstree15 | tree_index | skipmap | indexset | MT vs Best |
|---|---|---|---|---|---|
| 01_uniform | 44.18 | 14.49 | 10.80 | 14.94 | 2.96x |
| 02_zipfian | 40.48 | 18.86 | 12.92 | 3.28 | 2.15x |
| 03_shared_prefix | 30.12 | 14.61 | 11.08 | 14.36 | 2.06x |
| 04_high_contention | 70.85 | 21.64 | 16.90 | 1.97 | 3.27x |
| 05_large_dataset | 17.94 | 11.35 | 7.50 | 9.17 | 1.58x |
| 06_single_hot_key | 15.67 | 6.82 | 7.46 | 2.52 | 2.10x |
| 07_mixed_50_50 | 36.76 | 8.73 | 6.46 | 14.86 | 2.47x |
| 08_8byte_keys | 58.26 | 24.97 | 14.06 | 19.69 | 2.33x |
| 09_pure_read | 57.41 | 24.12 | 14.40 | 17.40 | 2.38x |
| 10_remove_heavy | 22.00 | 11.18 | 7.18 | 4.26 | 1.97x |
| 13_insert_only_fair | 39.71 | 24.29 | 15.13 | 6.30 | 1.63x |
| 14_pure_insert | 15.20 | 11.96 | 9.86 | 2.30 | 1.27x |
High-Impact Workloads (12T SMT)
Source:
runs/run200_high_impact.txt(masstree),runs/run188_high_impact.txt(competitors) Config: 12 threads on 6 physical cores (SMT), 200 samples
Benchmarks targeting Masstree's architectural advantages: long keys, variable-length keys, hot key patterns, mixed operations, prefix queries, deep trie traversal, and mixed prefix workloads.
| Benchmark | masstree15 | indexset | tree_index | skipmap | MT vs Best |
|---|---|---|---|---|---|
| 01_long_keys_128b | 34.85 | 14.60 | 12.83 | 10.84 | 2.39x |
| 02_multiple_hot_keys | 44.76 | 13.99 | 13.31 | 13.36 | 3.20x |
| 03_mixed_get_insert_remove | 26.79 | 5.923 | 10.02 | 8.488 | 2.67x |
| 04_variable_long_keys | 33.07 | 9.427 | 8.393 | 7.651 | 3.51x |
| 05_prefix_queries (Kitem/s) | 1075 | n/a | 495.9 | 144.9 | 2.17x |
| 06_deep_trie_traversal | 27.51 | 13.49 | 8.102 | 9.308 | 2.04x |
| 07_deep_trie_read_only | 31.86 | 14.81 | 14.54 | 13.37 | 2.15x |
| 08_variable_keys_arc | 30.54 | 11.67 | 10.45 | 8.875 | 2.62x |
| 09_prefix_realistic_mixed | 5.730 | n/a | 2.968 | 1.045 | 1.93x |
Range Scans (6T Physical)
Source:
runs/run191_range_scan.txtConfig: Physical cores only, 200 samples, performance governor
| Benchmark | masstree15_inline | tree_index | MT vs TI |
|---|---|---|---|
| 01_sequential_full_scan | 26.73 | 13.07 | 2.04x |
| 02_reverse_scan | 27.87 | 13.27 | 2.10x |
| 03_clustered_scan | 25.16 | 13.08 | 1.92x |
| 04_sparse_scan | 22.28 | 12.78 | 1.74x |
| 05_shared_prefix_scan | 22.93 | 15.33 | 1.50x |
| 06_suffix_differ_scan | 21.28 | 14.55 | 1.46x |
| 07_hierarchical_scan | 24.47 | 14.60 | 1.68x |
| 08_adversarial_splits | 26.83 | 8.807 | 3.05x |
| 09_interleaved_scan | 22.41 | 12.86 | 1.74x |
| 10_blink_stress_scan | 24.48 | 12.81 | 1.91x |
| 11_random_keys_scan | 26.02 | 12.36 | 2.10x |
| 12_long_keys_64b_scan | 26.06 | 14.24 | 1.83x |
| 15_full_scan_aggregate | 183.1 | 84.59 | 2.16x |
| 16_insert_heavy | 23.18 | 17.38 | 1.33x |
| 17_hot_spot | 11.24 | 5.318 | 2.11x |
Range Scans (12T SMT)
Source:
runs/run191_range_scan.txtConfig: 12 threads on 6 physical cores (SMT), 200 samples
| Benchmark | masstree15_inline | tree_index | MT vs TI |
|---|---|---|---|
| 01_sequential_full_scan | 31.77 | 14.49 | 2.19x |
| 02_reverse_scan | 29.96 | 14.66 | 2.04x |
| 03_clustered_scan | 31.85 | 14.54 | 2.19x |
| 04_sparse_scan | 30.95 | 14.58 | 2.12x |
| 05_shared_prefix_scan | 24.88 | 18.62 | 1.34x |
| 06_suffix_differ_scan | 23.41 | 18.16 | 1.29x |
| 07_hierarchical_scan | 26.99 | 18.46 | 1.46x |
| 08_adversarial_splits | 29.10 | 11.32 | 2.57x |
| 09_interleaved_scan | 24.82 | 15.34 | 1.62x |
| 10_blink_stress_scan | 30.05 | 15.12 | 1.99x |
| 11_random_keys_scan | 25.62 | 14.70 | 1.74x |
| 12_long_keys_64b_scan | 28.29 | 17.77 | 1.59x |
| 15_full_scan_aggregate | 230.8 | 111.9 | 2.06x |
| 16_insert_heavy | 25.09 | 21.88 | 1.15x |
| 17_hot_spot | 10.61 | 6.458 | 1.64x |
Tail Latency (pbench, Per-Operation)
Source:
runs/run202_tail_latency.txtConfig: 200k samples per benchmark,sample_size=1(unbatched), TSC timer (10 ns precision)
Point Lookups
| Benchmark | masstree | treeindex | skipmap | indexset |
|---|---|---|---|---|
| get 1T p50 | 140 ns | 250 ns | 371 ns | 270 ns |
| get 1T p99.9 | 541 ns | 641 ns | 871 ns | 701 ns |
| get 8T p50 | 531 ns | 561 ns | 741 ns | 701 ns |
| get 8T p99.9 | 1.29 us | 1.43 us | 2.53 us | 1.89 us |
| get 1M 1T p50 | 330 ns | 461 ns | 681 ns | 541 ns |
| get deep 8T p50 | 752 ns | 871 ns | 1.02 us | - |
| get long 8T p50 | 621 ns | 651 ns | 862 ns | - |
Inserts
| Benchmark | masstree | treeindex | skipmap | indexset |
|---|---|---|---|---|
| insert 1T p50 | 120 ns | 230 ns | 200 ns | 581 ns |
| insert 1T p99.9 | 511 ns | 1.29 us | 731 ns | 2.89 us |
| insert 8T p50 | 631 ns | 781 ns | 992 ns | 4.66 us |
| insert 8T p99.9 | 99.3 us | 21.2 us | 6.96 us | 59.3 us |
Mixed Read/Write (8T)
| Benchmark | masstree | treeindex | skipmap | indexset |
|---|---|---|---|---|
| 90/10 p50 | 511 ns | 621 ns | 851 ns | 731 ns |
| 90/10 p99.9 | 1.29 us | 4.46 us | 6.37 us | 1.91 us |
| 50/50 p50 | 591 ns | 1.06 us | 1.28 us | 781 ns |
| 50/50 p99.9 | 1.45 us | 7.40 us | 12.6 us | 3.88 us |
Range Scans (8T, 50-key scan)
| Benchmark | masstree | treeindex | skipmap |
|---|---|---|---|
| scan p50 | 1.08 us | 1.00 us | 3.83 us |
| scan+write p50 | 1.14 us | 1.16 us | 3.83 us |
| scan+write p99.9 | 5.34 us | 7.94 us | 11.1 us |
Install
[]
= { = "0.9.1", = ["mimalloc"] }
MSRV is Rust 1.92+ (Edition 2024).
The mimalloc feature sets the global allocator. If your project already uses a custom allocator, omit this feature.
Quick Start
use ;
let tree: = new;
let guard = tree.guard;
// Insert (returns None for new keys, Some(old) for existing)
tree.insert_with_guard;
tree.insert_with_guard;
// Point lookup (returns copy for inline storage)
assert_eq!;
// Remove
tree.remove_with_guard.unwrap;
assert_eq!;
// Range scan
tree.scan;
// Prefix scan
tree.scan_prefix;
Ergonomic APIs
For simpler use cases, auto-guard versions create guards internally:
use MassTree;
let tree: = new;
// Auto-guard versions (simpler but slightly more overhead per call)
tree.insert;
tree.insert;
assert_eq!;
assert_eq!;
assert!;
tree.remove.unwrap;
Range Iteration
use ;
let tree: = new;
let guard = tree.guard;
// Populate
for i in 0..100u64
// Iterator-based range scan
for entry in tree.range
// Full iteration
for entry in tree.iter
When to Use
May work well for:
- Long keys with shared prefixes (URLs, file paths, UUIDs)
- Range scans over ordered data
- Mixed read/write workloads
- High-contention scenarios (the trie structure helps here)
Consider alternatives for:
- Unordered point lookups →
dashmap - Integer keys only →
congee(ART-based) - Read-heavy with rare writes →
RwLock<BTreeMap>
Which Tree Type Should I Use?
| Type | Value Requirement | get_ref() |
Best For |
|---|---|---|---|
MassTree<V> |
V: InlineBits (Copy + ≤64 bits) |
Nope | u64, i32, f64, small tuples |
MassTree15<V> |
V: Send + Sync + 'static |
Yes | String, Vec, custom structs |
MassTree15Inline<V> |
V: InlineBits |
Nope | Same as MassTree<V> (explicit alias) |
MassTree<V> (Default, True-Inline)
- Values stored directly in leaf nodes (zero allocation per insert)
- Returns
Vby copy:get_with_guard() → Option<V> - Use
scan()for range iteration
MassTree15<V> (Box-Based)
- Values stored as
Box<V>raw pointers (heap allocation per insert) - Returns
ValuePtr<V>: a zero-costCopypointer withDerefto&V - Supports
get_ref() → Option<&V>for zero-copy access - Use
scan_ref()for zero-copy range iteration
MassTree<V> is the recommended default for Copy types like u64, i32, f64, pointers, etc.
Use MassTree15<V> explicitly when you need to store non-Copy types like String.
use ;
// Default: inline storage for Copy types (recommended)
let tree: = new;
// Box-based storage for non-Copy types
let tree_box: = new;
// Explicit inline storage (same as MassTree)
let inline: = new;
Examples
The examples/ directory contains comprehensive usage examples:
Rayon Integration
MassTree works seamlessly with Rayon for parallel bulk operations:
use MassTree15Inline;
use *;
use Arc;
let tree: = new;
// Parallel bulk insert (~10M ops/sec)
.into_par_iter.for_each;
// Parallel lookups (~45M ops/sec)
let sum: u64 = .into_par_iter
.map
.sum;
Tokio Integration
MassTree is thread-safe but guards cannot be held across .await points:
use MassTree15;
use Arc;
let tree: = new;
// Spawn async tasks that share the tree
let handle = spawn;
// For CPU-intensive operations, use spawn_blocking
let tree_clone = clone;
spawn_blocking.await;
Crate Features
mimalloc— Use mimalloc as global allocator (recommended)tracing— Enable structured logging tologs/masstree.jsonl
License
MIT. See LICENSE.
References
CHANGELOG
0.9.1: Route based re-traversal for sublayer GC. Fixes UAF from duplicate queue entries and stale pointers in deeply nested chains. The correctness fix and proper GC path leads to substantial performance improvements.