Overview
Storage benchmarks measure the performance ofRotorTree — the persistent variant with write-ahead logging (WAL), checkpointing, and optional mmap tiering for cold data.
All storage benchmarks use
FlushPolicy::Manual to isolate insertion performance from fsync overhead. In production, you’d typically use FlushPolicy::Interval with a background fsync thread.Test Configuration
All benchmarks intree_bench_storage.rs use:
- FlushPolicy:
Manual(no automatic fsync) - CheckpointPolicy:
ManualorEveryNEntries(varies by benchmark) - TieringConfig:
default()(all in memory, mmap only after checkpoint) - verify_checkpoint:
true(recompute root on recovery)
Insertion Benchmarks
Single Insert
Inserts leaves one-by-one to the WAL:- Counts: 1K, 10K, 100K leaves
- Branching factors: N=2, 4, 8, 16
- What it measures: WAL append overhead per insertion
This benchmark does not wait for fsync (no
token.wait()). It measures in-memory insertion + WAL append, not durability latency.Batch Insert
Inserts all leaves in a singleinsert_many call:
- Counts: 1K, 10K, 100K, 1M leaves
- Branching factors: N=2, 4, 8, 16
- What it measures: Batch WAL write efficiency
WAL Operations
Flush
Measures fsync latency for flushing buffered WAL entries to disk:- Counts: 1K, 10K, 100K leaves
- Branching factors: N=2, 4, 8, 16
- What it measures: fsync overhead after batch insertion
Flush latency depends heavily on OS page cache behavior and storage device characteristics (SSD vs HDD, IOPS, etc.).
Open/Recovery
Measures time to open an existing tree and replay the WAL:- Counts: 1K, 10K, 100K leaves
- Branching factors: N=2, 4, 8, 16
- What it measures: Crash recovery time (WAL replay + optional root verification)
Checkpoint Benchmarks
Sustained Checkpoint
Measures performance under continuous insertion + checkpointing + proof generation:- Counts: 100K, 1M leaves (inserted in 10K chunks)
- Checkpoint frequency: Every 5, 25, 100, or 500 WAL entries
- Branching factors: N=2, 4, 8, 16
- What it measures: Realistic sustained workload with automatic checkpointing
This benchmark simulates a blockchain node that:
- Processes blocks in batches
- Checkpoints periodically to prevent unbounded WAL growth
- Serves proof requests for historical leaves
Mixed Workload
Measures performance of a realistic mixed workload:- Pre-populate with 10K leaves
- Flush to disk
- Insert N more leaves (100, 1K, 10K, or 100K)
- Get current root
- Generate proof for middle leaf
- Verify proof
Mmap Tiering
Configuration
TheTieringConfig controls which tree levels stay in memory vs get mmap’d from data files after checkpoint:
pin_above_level: usize::MAX(default): mmap everything after checkpointpin_above_level: 0: keep everything in memory (no mmap)pin_above_level: L: levels > L stay in memory, levels ≤ L get mmap’d
Writes always go to WAL + memory. Reads are always from memory or mmap. You cannot do an apples-to-apples comparison with other Merkle tree databases that read from disk on every query.
Capacity Examples
- N=4, MAX_DEPTH=16: ~4.3B nullifiers in 41 GiB
- N=8, MAX_DEPTH=10: ~1B nullifiers in 37 GiB
Performance Comparison
The README includes a detailed benchmark comparing no-mmap vs full-mmap performance: Test setup:- m4 pro, 14c, 48gig
- 1B leaves inserted in 1M chunks
- Manual checkpoint after each chunk insertion
Key findings:
- Proof verification is nearly constant (always reading from memory/mmap)
- Proof generation varies based on snapshot acquisition cost
- Mmap adds latency for cold data access but enables much larger trees
For most use cases, you don’t need crash persistence if there’s a bootstrapping sync mechanism. Just use the single-threaded in-memory variant — it’s MUCH faster with lower variance for small to medium workloads.
