TensorDB ships with three Criterion benchmark suites. Results from a single-machine run:
| Operation | TensorDB | SQLite | sled | redb |
|---|---|---|---|---|
| Point Read | 276 ns | 1,080 ns | 244 ns | 573 ns |
| Point Write (fast path) | 1.9 µs | 38.6 µs | — | — |
| Point Write (channel path) | ~161 µs | 38.6 µs | — | — |
| Batch Write (100 keys) | native | — | — | — |
| Prefix Scan (1k keys) | native | — | — | — |
| Mixed 80r/20w | native | — | — | — |
Key takeaways:
- Point reads are 4x faster than SQLite thanks to direct shard bypass (no channel round-trip).
- Point writes are 20x faster than SQLite via the lock-free fast write path with group-commit WAL.
- The channel-based write path (~161 µs) is the fallback for backpressure, subscriber activity, or when
fast_write_enabled=false.
# TensorDB vs SQLite (head-to-head)
cargo bench --bench comparative
# TensorDB vs SQLite vs sled vs redb (four-way)
cargo bench --bench multi_engine
# TensorDB microbenchmarks
cargo bench --bench basic
# CLI-integrated benchmark with configurable workload
cargo run -p tensordb-cli -- --path /tmp/bench bench \
--write-ops 100000 --read-ops 50000 --keyspace 20000 --read-miss-ratio 0.20The read path bypasses the shard actor channel entirely. ShardReadHandle holds an Arc<ShardShared> and reads directly from shared memory:
- Block cache check (LRU, configurable size)
- Bloom filter probe (skip SSTable if negative)
- Active memtable scan (read lock, microseconds)
- Immutable memtables scan
- SSTable level scan (L0: all files newest-first, L1+: binary search)
- Temporal filter application
No channel send/receive, no thread context switch, no shard actor wake-up. Read locks are held only during the scan itself.
The fast write path (FastWritePath) eliminates the three most expensive operations in the traditional channel-based path:
| Cost | Channel Path | Fast Path |
|---|---|---|
| Channel send/receive | ~50 µs | Eliminated |
| Per-write WAL fsync | ~100 µs | Amortized via group commit |
| Shard actor wake-up | ~10 µs | Eliminated |
How it works:
- Atomic
fetch_add(1)on the shard's commit counter →commit_ts - Encode internal key and fact value
- Write-lock memtable → insert (microseconds)
- Enqueue pre-encoded WAL frame to
WalBatchQueue - Return
commit_tsto caller immediately
The DurabilityThread runs in the background, batching WAL frames across all shards into a single fdatasync call per interval. At 100K writes/sec with a 1ms batch interval, one fsync amortizes across ~100 writes.
Fallback to channel path when:
- Memtable is full (needs flush — shard actor handles compaction)
- Change-feed subscribers are active (shard actor emits events)
fast_write_enabledis set to false
| Parameter | Default | Trade-off |
|---|---|---|
memtable_max_bytes |
4 MB | Larger = fewer flushes, better write amortization, more memory. Smaller = lower memory, more flush churn. |
sstable_block_bytes |
16 KB | Larger = better sequential scan locality, fewer index entries. Smaller = less over-read per point lookup. |
sstable_max_file_bytes |
64 MB | Max SSTable file size before splitting during compaction. |
bloom_bits_per_key |
10 | Higher = lower false-positive rate (fewer unnecessary disk reads), more memory/disk. Typical range: 8–14. |
block_cache_bytes |
32 MB | Larger = more hot data served from memory, fewer disk reads. Size based on your working set. |
index_cache_entries |
1024 | Number of SSTable index blocks cached. Increase if you have many SSTables. |
| Parameter | Default | Trade-off |
|---|---|---|
shard_count |
4 | More shards = higher write concurrency. Too many = more background work and metadata overhead. Match to CPU core count. |
fast_write_enabled |
true | Enables lock-free fast write path (~1.9 µs). Disable for strict per-write durability (falls back to channel + immediate fsync). |
fast_write_wal_batch_interval_us |
1000 | WAL group commit interval in microseconds. Lower = faster durability, more fsyncs. Higher = better throughput, larger acknowledged window. |
wal_fsync_every_n_records |
128 | Channel-path fsync cadence. Lower = stronger durability, lower throughput. Higher = better throughput, larger window between fsyncs. |
| Parameter | Default | Trade-off |
|---|---|---|
compaction_l0_threshold |
8 | Number of L0 SSTables before triggering compaction. Lower = less read amplification, more compaction work. |
compaction_l1_target_bytes |
10 MB | L1 size target. Larger = fewer L0→L1 compactions, larger files. |
compaction_size_ratio |
10 | Each level is this many times larger than the previous. Standard LSM ratio. |
compaction_max_levels |
7 | Maximum levels (L0 through L6). More levels = better space amplification for large datasets. |
- Steady state: Fast write path dominates. Lock-free atomic increment + memtable insert + async WAL enqueue.
- Memtable flush: When memtable exceeds
memtable_max_bytes, it's frozen and flushed to an L0 SSTable. During flush, the fast path falls back to the channel path briefly. - Compaction pressure: When L0 files accumulate beyond
compaction_l0_threshold, compaction merges them into L1. Write latency can spike during heavy compaction. - Group commit: The
DurabilityThreadbatches WAL writes. Durability is guaranteed within one batch interval, not per-write.
- Cache-warm reads: Block cache hit → ~100 ns.
- Bloom-filtered misses: Bloom probe returns negative → skip SSTable entirely. Cost: ~50 ns per SSTable.
- Memtable reads: Active memtable scan with read lock → ~200–400 ns.
- Cold SSTable reads: mmap page fault + block decompression (LZ4) + scan → 1–10 µs depending on block size and data locality.
- Temporal filtering:
AS OFandVALID ATpredicates add version-filtering cost proportional to the number of versions per key.
- Point lookups via SQL: Parser + planner + executor overhead adds ~10–50 µs on top of raw key lookup.
- Full table scans: Proportional to row count. Vectorized execution engine processes rows in batches of 1024 for analytical queries.
- Joins: Hash joins for equi-joins (build hash table on smaller side, probe with larger). Nested loop joins for non-equi conditions.
- Aggregates: Vectorized hash aggregate for GROUP BY. Window functions computed after grouping, before LIMIT.
-- Query execution plan with cost estimates
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;
-- Plan with actual execution metrics
EXPLAIN ANALYZE SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
-- Shows: execution_time_us, rows_returned, read_ops, write_ops, bloom_negatives, plan_cost
-- Table statistics (used by cost-based planner)
ANALYZE orders;
The CLI bench command reports:
- Write throughput (ops/s)
- Read p50/p95/p99 latency (µs)
- Bloom miss rate
- mmap block read count
- Hasher path (rust/native)
# CPU profiling with perf
perf record cargo bench --bench comparative
perf report
# Flamegraph
cargo install flamegraph
cargo flamegraph --bench comparative
# Memory profiling with heaptrack
heaptrack cargo run -p tensordb-cli -- --path /tmp/bench bench --write-ops 100000Near-term:
- Index scan execution for
WHERE pk = ?andWHERE indexed_col = ? - Parallel shard execution for scans and aggregates
- Expression compilation for hot WHERE predicates
- Predicate pushdown to SSTable block level
Medium-term:
- Pipeline execution for vectorized operators (fuse without materialization)
- Morsel-driven parallelism for table scans
- External merge sort for large ORDER BY
- Adaptive execution switching between vectorized (analytics) and row-based (OLTP)
Long-term:
- Columnar SSTable format for analytics workloads
- SIMD string operations (LIKE, SUBSTR, UPPER/LOWER)
- Late materialization (keep column references until final projection)
- Compression policies per compaction level (LZ4 for L0–L2, Zstd for L3+)