Skip to content

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-mvcc for 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.

Operationp50p95p50, MVCC offp95, MVCC off
Key lookup (random existing key)42 ns208 ns42 ns167 ns
1-hop traversal (out, random node)42 ns84 ns42 ns167 ns
Edge exists (random pair)42 ns83 ns42 ns83 ns
Batch write (100 nodes)17.92 µs46.79 µs17.83 µs37.33 µs
Batch write (100 edges)16.08 µs25.92 µs13.75 µs26.12 µs
Batch write (100 edges + props)65.50 µs82.21 µs64.25 µs75.71 µs
Get vector (random)83 ns208 ns83 ns166 ns
Has vector (random)42 ns42 ns42 ns42 ns
Set vectors (batch of 100)67.96 µs196.33 µs66.92 µs178.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.

Operationp50p95
Key lookup (random existing key)125 ns209 ns
1-hop traversal (out, random node)250 ns417 ns
Edge exists (random pair)125 ns125 ns
Batch write (100 nodes)24.71 µs60.17 µs
Batch write (100 edges)18.25 µs24.58 µs
Batch write (100 edges + props)201.83 µs238.38 µs
Get vector (random)708 ns875 ns
Has vector (random)125 ns125 ns
Set vectors (batch of 100)105.46 µs7.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 mode100 nodes, p50100 nodes, p95100 edges, p50Set 100 vectors, p50
normal17.92 µs46.79 µs16.08 µs67.96 µs
full72.21 µs110.42 µs56.58 µs162.25 µs
off14.29 µs44.62 µs11.50 µs58.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.

TransactionsSync1 writer4 writers8 writers8 vs 1
200-node transactionsnormal4.93K/s9.47K/s12.69K/s2.6x
200-node transactionsfull3.44K/s7.58K/s10.30K/s3.0x
1-node transactionsnormal386.48K/s427.66K/s462.48K/s1.2x
1-node transactionsfull25.28K/s40.39K/s58.90K/s2.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)

Shell
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 --mvcc

Open time and paging

Opening a checkpointed database read-only checks the snapshot's structure and inflates its compressed sections on several threads:

DatabaseOpen + close (median)
100k nodes, 500k edges2.11 ms
1M nodes, 5M edges18.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):

OperationIn the WALAfter a checkpoint
Node page 1 (100 nodes)0.5 µs0.4 µs
Node page 10000.5 µs0.5 µs
Edge page 1 (100 edges)0.4 µs0.6 µs
Edge page 10000.5 µs0.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 type2.62 ms1.88 ms
First neighbor of a 200k-edge hub, take(1)0.4 µs0.4 µs
All neighbors of a 200k-edge hub276.9 µs166.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)p50p95
SQLite156.04 µs181.67 µs
KiteDB (Rust core, MVCC on)17.92 µs46.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)

Shell
cd docs/benchmarkspython3 sqlite_single_file_raw_bench.py \  --nodes 10000 --edges 50000 --iterations 10000 --sync-mode normal

Running benchmarks

Commands from docs/BENCHMARKS.md, run from the repository root. Rust core:

Shell
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 --mvcc

Python bindings:

Shell
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 --mvcc

Both scripts accept the same optional flags:

  • --edge-types N (default 3) and --edge-props N (default 10)
  • --sync-mode full|normal|off (default normal)
  • --mvcc / --no-mvcc (default: the library default, MVCC on) and --seed N (default 42)
  • --group-commit-enabled and --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.