Masstree
Goals
- Fast key-value store for multicore servers
- All data fits in memory (in-memory)
- Persistent across crashes
- Support range queries
- Handle hard workloads
- Skewed key popularity
- Small key-value pairs
- Many puts (writes)
- Variable-length arbitrary keys
Problem: DRAM is the bottleneck
- Network/disk can be optimized away
- Binary tree: O(log N) serial DRAM latencies
- Cache-craftiness: careful use of cache and memory
Key Design: Trie of B+ Trees
- Each trie level = B+ tree indexed by 8-byte key slice
- Level 0: k[0:7], Level 1: k[8:15], …
- Combines benefits:
- B+ tree: balanced, wide fanout
- Trie: handles long common prefixes efficiently
- Example: keys with P-byte prefix
- Masstree: O(log N) comparisons and DRAM accesses
- Single B+ tree: O(P log N) for both
Cache-Crafty Optimizations
Integer Key Comparison (IntCmp)
- Store 8-byte key slices as 64-bit integers
- Single CPU integer comparison vs byte-by-byte loops
- 15-24% performance improvement
- Little-endian challenge:
- String “ABCDEFGH” in memory:
41 42 43 44 45 46 47 48
- Loaded as int:
0x4847464544434241 (reversed!)
- Solution: byte-swap to
0x4142434445464748
- Now integer
< gives same result as string comparison
Wide Fanout (4-tree → B+ tree)
- 4-tree: fanout 4, one cache line per node
- ½ levels vs binary tree → ½ DRAM latencies
- Problem: unbalanced for sequential inserts
- B+ tree: fanout 15, balanced
- 4 cache lines per node (256 bytes)
Why 256 Bytes (4 Cache Lines)?
- Hardware constraints:
- CPU loads in 64-byte cache lines
- Limited prefetch queue (4-16 outstanding requests)
- L1 cache only 32KB (512 lines total)
- Tradeoffs:
- 128 bytes → fanout 7 → deeper tree
- 256 bytes → fanout 15 → optimal balance
- 512 bytes → fanout 31 → wastes cache (nodes 75% full)
- Performance impact:
- 256 bytes = 0.78% of L1 cache
- Can prefetch all 4 lines in parallel
- Balances tree depth vs cache pollution
Prefetching
- Software prefetch all cache lines of a node
- Result: 1 DRAM latency per node (vs 2-4)
- 9-31% throughput improvement
Permuter
- Problem: B+ tree inserts rearrange keys → intermediate states
- Solution: store keys unsorted, use 64-bit permutation field
- Encodes sort order + number of keys
- Insert appears atomic to concurrent lookups
- No retry needed for concurrent reads
Concurrency Control
- Optimistic concurrency control (OCC) for reads
- No locks, no writing to shared data
- Version checking: retry if inconsistent
- Fine-grained locking for writes
- Lock only affected nodes (≤ 3 per operation)
- Allows parallel updates to different parts
RCU (Read-Copy-Update)
- Masstree inspired by RCU techniques
- What is RCU?
- Synchronization mechanism allowing reads without locks
- Readers access data structures without synchronization
- Writers create new versions instead of modifying in-place
- Old versions kept until all readers finish
- In Masstree:
- Readers never block or use locks
- Writers update by creating new versions
- Epoch-based reclamation for garbage collection
- Ensures readers always see consistent data
Version Management
- Version field per node includes:
- locked bit (for writers)
- inserting/splitting bits (dirty markers)
- vinsert/vsplit counters
- Writers mark dirty → make changes → clear & increment
- Readers snapshot version → read → check version
Handling Splits
- Problem: splits move keys between nodes
- Solution: hand-over-hand validation
- Check child version before parent
- Detect splits via vsplit counter change
- Retry from root on split detection
- 140M keys, 16 cores:
- Binary tree: 3.7M ops/sec
- Masstree: 5.8M puts/sec, 8M gets/sec
- 1.7× improvement from cache-craftiness
- Scales to 12× on 16 cores
Tradeoffs
Single Tree vs Partitioning
- Partitioned (per-core trees):
- Pro: no remote DRAM, no concurrency control
- Con: load imbalance with skewed workloads
- Masstree (single shared tree):
- Pro: handles skew well (3.5× better at δ=9)
- Con: 1.5× slower for uniform workloads
Range Queries vs Hash Table
- Hash table: 2.5× Masstree throughput
- Cost of range query support: 2.5× performance
System Components
- Network: 10Gb NIC with multiple queues
- Logging: per-core logs to multiple SSDs
- Batch writes, force every 200ms
- Checkpointing: periodic snapshots for recovery
- Values: multi-column support with atomic updates
Key Insights
- DRAM latency dominates performance
- Wide fanout + prefetching reduces tree depth
- Permuter enables lock-free reads during inserts
- Trie structure efficiently handles long prefixes
- Single tree better for skewed workloads