Benchmarks
Graph benchmarks
Graph database performance
Latency of the single-file engine on a graph of 10k nodes, 50k edges, measured from Rust and through the Python bindings, then write throughput with several writer threads, open time and paging. The runs are from October 3, 2026 on an Apple M5 Pro, and each table names its raw log.
Test configuration
- Graph: 10,000 nodes, 50,000 edges, 3 edge types, 10 props per edge
- Iterations: 10,000
- Vectors: 1,000 vectors of 128 dimensions (for the vector rows; set in batches of 100)
- WAL: 256 MB with auto-checkpoint off, so write timings show the raw commit cost
- Durability: sync=normal, unless a table says otherwise; every commit is group-committed
- MVCC: on (the default since 0.3.0); one run with
--no-mvccfor comparison - Seed 42; each row is the median of five interleaved runs
Rust core
Measured with the single_file_raw_bench example, MVCC on, and the same run with MVCC off.
| Operation | p50 | p95 | p50, MVCC off | p95, MVCC off |
|---|---|---|---|---|
| Key lookup (random existing key) | 42 ns | 208 ns | 42 ns | 167 ns |
| 1-hop traversal (out, random node) | 42 ns | 84 ns | 42 ns | 167 ns |
| Edge exists (random pair) | 42 ns | 83 ns | 42 ns | 83 ns |
| Batch write (100 nodes) | 17.92 µs | 46.79 µs | 17.83 µs | 37.33 µs |
| Batch write (100 edges) | 16.08 µs | 25.92 µs | 13.75 µs | 26.12 µs |
| Batch write (100 edges + props) | 65.50 µs | 82.21 µs | 64.25 µs | 75.71 µs |
| Get vector (random) | 83 ns | 208 ns | 83 ns | 166 ns |
| Has vector (random) | 42 ns | 42 ns | 42 ns | 42 ns |
| Set vectors (batch of 100) | 67.96 µs | 196.33 µs | 66.92 µs | 178.21 µs |
Source: 2026-10-03-r2-single-file-raw-rust-mvcc-normal.txt, 2026-10-03-r2-single-file-raw-rust-nomvcc-normal.txt (10k nodes, 50k edges, 3 edge types, 10 edge props, 1k vectors of 128 dims, sync=normal, MVCC on)
Python bindings
The same benchmark through the Python bindings, with benchmark_single_file_raw.py.
| Operation | p50 | p95 |
|---|---|---|
| Key lookup (random existing key) | 125 ns | 209 ns |
| 1-hop traversal (out, random node) | 250 ns | 417 ns |
| Edge exists (random pair) | 125 ns | 125 ns |
| Batch write (100 nodes) | 24.71 µs | 60.17 µs |
| Batch write (100 edges) | 18.25 µs | 24.58 µs |
| Batch write (100 edges + props) | 201.83 µs | 238.38 µs |
| Get vector (random) | 708 ns | 875 ns |
| Has vector (random) | 125 ns | 125 ns |
| Set vectors (batch of 100) | 105.46 µs | 7.23 ms |
Source: 2026-10-03-r2-single-file-raw-python-mvcc-normal.txt (10k nodes, 50k edges, 3 edge types, 10 edge props, 1k vectors of 128 dims, sync=normal, MVCC on)
Sync modes
Batch writes from the Rust benchmark on the same graph, MVCC on, in each sync mode.
| Sync mode | 100 nodes, p50 | 100 nodes, p95 | 100 edges, p50 | Set 100 vectors, p50 |
|---|---|---|---|---|
normal | 17.92 µs | 46.79 µs | 16.08 µs | 67.96 µs |
full | 72.21 µs | 110.42 µs | 56.58 µs | 162.25 µs |
off | 14.29 µs | 44.62 µs | 11.50 µs | 58.54 µs |
Source: 2026-10-03-r2-single-file-raw-rust-mvcc-normal.txt, 2026-10-03-r2-single-file-raw-rust-mvcc-full.txt, 2026-10-03-r2-single-file-raw-rust-mvcc-off.txt (10k nodes, 50k edges, one log per sync mode)
full returns once the commit is fsynced. On macOS a plain fsync leaves the writes in the drive's cache; with fullFsync (F_FULLFSYNC) the sync reaches the drive and costs milliseconds, as the write-scaling numbers below show. Every commit is group-committed in every mode: commits that arrive together share one WAL write, one header write and, in full, one sync, and a single writer waits for no one. The groupCommitEnabled and groupCommitWindowMs options have no effect.
Parallel writes
With MVCC, writer threads build their transactions in parallel. Commits that arrive together are written as one group (one WAL write, one header write, one fsync in full mode). Publishing, each commit's merge into the in-memory delta, runs one group at a time, so with syncMode=Normal large transactions gain more from extra writers than small ones, whose cost is mostly that shared pipeline. In full mode a group also shares its fsync, and the next group is written and synced while one publishes, so one-node transactions gain from more writers too.
| Transactions | Sync | 1 writer | 4 writers | 8 writers | 8 vs 1 |
|---|---|---|---|---|---|
| 200-node transactions | normal | 4.93K/s | 9.47K/s | 12.69K/s | 2.6x |
| 200-node transactions | full | 3.44K/s | 7.58K/s | 10.30K/s | 3.0x |
| 1-node transactions | normal | 386.48K/s | 427.66K/s | 462.48K/s | 1.2x |
| 1-node transactions | full | 25.28K/s | 40.39K/s | 58.90K/s | 2.3x |
Source: 2026-10-03-r2-multi-writer-throughput-mvcc-normal-200node-8w.txt, 2026-10-03-r2-multi-writer-throughput-mvcc-normal-1node-8w.txt (transactions per second, MVCC on, 1 GB WAL, auto-checkpoint off; 200-node transactions: 200 nodes and 200 edges (10 props each) per transaction, 200 transactions per writer; 1-node transactions: one keyed node per transaction, 50,000 transactions per writer; one log per cell, named by sync mode, size and writers)
Without MVCC (deprecated), write transactions run one at a time; with syncMode=Normal one writer reaches 5.02K/s with 200-node transactions and 400.17K/s with one-node transactions. With full and fullFsync, one-node transactions commit at 295.15/s with 1 writer and 1.18K/s with 8 writers, since a group shares its sync.
Source: 2026-10-03-r2-multi-writer-throughput-nomvcc-normal-200node-1w.txt, 2026-10-03-r2-multi-writer-throughput-nomvcc-normal-1node-1w.txt, 2026-10-03-r2-multi-writer-throughput-mvcc-fullfsync-1node-1w.txt, 2026-10-03-r2-multi-writer-throughput-mvcc-fullfsync-1node-8w.txt (same shapes as the table)
cd ray-rscargo run --release --example multi_writer_throughput_bench --no-default-features -- \ --threads 8 --tx-per-thread 200 --batch-size 200 --edges-per-node 1 \ --edge-types 3 --edge-props 10 --wal-size 1073741824 --sync-mode normal --mvccOpen time and paging
Opening a checkpointed database read-only checks the snapshot's structure and inflates its compressed sections on several threads:
| Database | Open + close (median) |
|---|---|
| 100k nodes, 500k edges | 2.11 ms |
| 1M nodes, 5M edges | 18.62 ms |
Source: 2026-10-03-r2-query-core-open-100k.txt, 2026-10-03-r2-query-core-open-1m.txt (query_core_bench --sections open, MVCC on)
Pages, counts and neighbor reads with every change still in the WAL and after a checkpoint folded them into the snapshot (query_core_bench, medians):
| Operation | In the WAL | After a checkpoint |
|---|---|---|
| Node page 1 (100 nodes) | 0.5 µs | 0.4 µs |
| Node page 1000 | 0.5 µs | 0.5 µs |
| Edge page 1 (100 edges) | 0.4 µs | 0.6 µs |
| Edge page 1000 | 0.5 µs | 0.8 µs |
| Count nodes (page total) | < 0.1 µs | < 0.1 µs |
| Count edges (page total) | < 0.1 µs | < 0.1 µs |
| Count nodes of one type | 2.62 ms | 1.88 ms |
| First neighbor of a 200k-edge hub, take(1) | 0.4 µs | 0.4 µs |
| All neighbors of a 200k-edge hub | 276.9 µs | 166.7 µs |
Source: 2026-10-03-r2-query-core-paging.txt (paging: 1000000 nodes, 5000000 edges, page size 100, pages 1 and 1000; types: 100000 nodes over 5 types, all(T2) and count_nodes_by_type(T2); take(1) from a node with 200000 out-edges)
SQLite baseline
docs/benchmarks/sqlite_single_file_raw_bench.py runs the batch-write benchmark against SQLite on a graph of the same size, on the same machine. It configures SQLite with WAL mode, synchronous=normal, temp_store=MEMORY, locking_mode=EXCLUSIVE, cache_size=256MB, and WAL autocheckpoint disabled. Edge props live in a separate table; edges use INSERT OR IGNORE and props use INSERT OR REPLACE.
| Batch write (100 nodes) | p50 | p95 |
|---|---|---|
| SQLite | 156.04 µs | 181.67 µs |
| KiteDB (Rust core, MVCC on) | 17.92 µs | 46.79 µs |
Source: 2026-10-03-r2-sqlite-single-file-raw-edges-normal.txt, 2026-10-03-r2-single-file-raw-rust-mvcc-normal.txt (10k nodes, 50k edges, 3 edge types, 10 edge props, WAL mode, synchronous=normal, SQLite 3.47.1)
cd docs/benchmarkspython3 sqlite_single_file_raw_bench.py \ --nodes 10000 --edges 50000 --iterations 10000 --sync-mode normalRunning benchmarks
Commands from docs/BENCHMARKS.md, run from the repository root. Rust core:
cd ray-rscargo run --release --example single_file_raw_bench --no-default-features -- \ --nodes 10000 --edges 50000 --iterations 10000 \ --wal-size 268435456 --no-auto-checkpoint --seed 42 --sync-mode normal --mvccPython bindings:
cd ray-rs/python/benchmarkspython3 benchmark_single_file_raw.py \ --nodes 10000 --edges 50000 --iterations 10000 \ --wal-size 268435456 --no-auto-checkpoint --seed 42 --sync-mode normal --mvccBoth scripts accept the same optional flags:
--edge-types N(default 3) and--edge-props N(default 10)--sync-mode full|normal|off(defaultnormal)--mvcc/--no-mvcc(default: the library default, MVCC on) and--seed N(default 42)--group-commit-enabledand--group-commit-window-ms N(accepted, but they have no effect now that every commit is group-committed)
Without --wal-size and --no-auto-checkpoint the scripts use a 64 MB WAL with auto-checkpoint on. ray-rs/scripts/bench-refresh.sh --list prints the command of every run on this page.