PERFORMANCE

Numbers we can reproduce.

Every figure on this page comes from a measured run, and each one states the conditions it was measured under. Raw results are in BENCHMARK_RESULTS.md in the repo. No projected numbers, no competitor numbers we did not measure. No other database has been run on the same hardware yet: the same-hardware harness (bench/compare) exists, but its Aerospike and ScyllaDB paths are only compile-checked.

By the numbers

The YCSB run, with its conditions.

YCSB 0.17.0, June 9 2026 · pure-Go build (historical: it predates the native index, the binary WAL and --net) · AWS EC2 high-memory, 495 GB RAM · 4× NVMe, raw block VLog · 400 GB LIRS cache · 200 client threads · 100M keys × ~100 B values. The YCSB 0.17.0 binding used is not in the repo, so this run cannot be re-run from it; bench/compare (go-ycsb, below) is the reproducible YCSB path.

427,697
Reads per second
Workload C, 10M ops · entire dataset in cache, 100% hits
18,064
Durable writes per second
100M-key load · WAL + fdatasync before ACK · 5 ms windows
100M
Keys loaded
~100 B values · single node
0
Failed operations
Across the 100M-key load

YCSB latency: reads vs durable writes

Same run as above. Reads are cache hits (the whole dataset fit in the 400 GB cache), so this is not a disk-read latency. Writes wait for fdatasync.

— ms →
Read · avg
YCSB workload C · 10M ops · 100% cache hits · 200 threads
0.46
461 µs
Read · P95
YCSB workload C · 10M ops · 100% cache hits · 200 threads
2.07
2,069 µs
Read · P99
YCSB workload C · 10M ops · 100% cache hits · 200 threads
2.77
2,771 µs
Write · avg
100M-key load · WAL + fdatasync · 5 ms flush windows · 200 threads
11.06
11.06 ms
Write · P95
100M-key load · WAL + fdatasync · 5 ms flush windows · 200 threads
30.8 ms
30.8 ms
Write · P99
100M-key load · WAL + fdatasync · 5 ms flush windows · 200 threads
47.3 ms
47.3 ms
OperationAvgP95P99Conditions
Read (workload C)461 µs2,069 µs2,771 µs10M ops, 100% cache hits, 427,697 ops/s
Write (100M-key load)11.06 ms30.8 ms47.3 msfdatasync-durable, 5 ms windows, 18,064 ops/s
This run did not measure cache-miss reads. When a read does miss the cache, the value is read with one Go pread of a 4 KB-aligned VLog block, checked with magic + CRC32C. That is not io_uring and not zero-copy. The in-process cache hit itself costs ~92 ns; the rest of the read latency above is spent outside the cache lookup (network round trip, protocol, 200 concurrent clients).
October 2026 · v1.11 & v1.12

Newer measurements, on a laptop.

Taken while building adaptive group commit, vector / text / hybrid search, PQ and the disk graph. All on one macOS laptop (Apple silicon) unless stated, so the absolute numbers do not predict a Linux NVMe server. None of them is a comparison with another database. Each table names the command that reproduces it.

Durable single-key writes: adaptive vs fixed group commit v1.12

Adaptive group commit is the default since v1.12 (--group-commit=adaptive). The flush window becomes an upper bound: a lone writer is synced at once, and concurrent writers are synced after an idle gap of about one fdatasync. Conditions: the device’s fdatasync cost is emulated (a sleep before the real sync), because macOS fsync returns at the drive cache. 15 ms window, 128 B values, each writer doing sequential durable Puts for 1.5 s.

WritersModeP50P99Writes/sWrites per fdatasync
Emulated sync = 300 µs (NVMe-like)
1fixed16.01 ms17.76 ms631.0
1adaptive0.45 ms0.52 ms2,1861.0
8fixed16.02 ms16.39 ms5018.0
8adaptive0.96 ms1.22 ms8,2838.0
64fixed15.94 ms16.81 ms4,05364.0
64adaptive1.67 ms2.64 ms37,50463.9
256fixed16.40 ms33.15 ms15,291249.3
256adaptive3.18 ms11.54 ms73,999255.2
Emulated sync = 2 ms (slow / network disk)
1fixed18.10 ms23.68 ms55—
1adaptive2.41 ms2.52 ms417—
64fixed18.18 ms19.12 ms3,541—
64adaptive5.40 ms7.07 ms11,691—
256fixed18.32 ms35.17 ms13,705—
256adaptive6.96 ms18.56 ms32,277—

Batches are as large as in fixed mode (about 64 writes per sync at 64 writers), so adaptive mode does not cost more IOPS under load. The 2 ms run did not record writes per sync. --group-commit=fixed restores the old behaviour, where every batch waits the whole window.

VELTRIX_GC_TABLE=1 go test ./storage -run TestGroupCommit_LatencyTable -v

YCSB A–F through the server, VeltrixDB only

Conditions: go-ycsb core workloads (bench/compare/cmd/ycsb) against a local server on the same laptop. 20,000 records × 10 fields × 100 B, 20,000 operations, 16 threads, zipfian, whole-record writes. macOS fsync does not reach the platter, so the write latencies here are a lower bound. This is not the June YCSB 0.17.0 run above, and the two are not comparable.

WorkloadOpOps/sAvgP99P99.9
loadINSERT37,495419 µs1.29 ms2.04 ms
A (50/50 read/update)total34,640453 µs1.30 ms2.98 ms
READ17,230262 µs0.77 ms1.55 ms
UPDATE17,434642 µs1.63 ms3.14 ms
B (95/5)total52,255303 µs0.65 ms0.74 ms
C (read only)READ55,287287 µs0.39 ms0.53 ms
D (read latest)total55,163288 µs0.39 ms0.59 ms
E (short scans)SCAN23,651638 µs1.43 ms1.97 ms
F (read-modify-write)total40,067392 µs0.81 ms0.98 ms
READ_MODIFY_WRITE13,397915 µs1.22 ms1.49 ms
cd bench/compare && START=0 DBS=veltrixdb RECORDS=20000 OPS=20000 THREADS=16 WORKLOADS="a b c d e f" ./compare.sh

compare.sh can also drive Aerospike and ScyllaDB one at a time on the same machine, but those drivers have only been compiled, not run, and no shared run has been published. Workload E uses VeltrixDB’s RANGE over the ordered key index.

Vector search v1.11 & v1.12

Conditions: the first 100,000 words of GloVe-100 (angular), 100 dimensions, 500 queries, exact cosine ground truth. In-process, one query at a time, macOS (Apple silicon, GOMAXPROCS = 18). Each cell is recall@10 and p50 latency.

Layoutef = 64ef = 128ef = 256ef = 512
float320.842 · 0.25 ms0.906 · 0.42 ms0.953 · 0.77 ms0.983 · 1.43 ms
int80.839 · 0.61 ms0.905 · 0.55 ms0.952 · 0.72 ms0.983 · 1.18 ms
pq (m = 25)0.797 · 0.56 ms0.870 · 0.72 ms0.927 · 1.17 ms0.969 · 1.56 ms
pq + disk graph0.788 · 0.55 ms0.875 · 0.66 ms0.931 · 1.06 ms0.966 · 1.60 ms

At this size the quantized layouts are slower per query than float32, because they re-rank candidates against the full vectors read back from the VLog. They exist to save RAM, and at 100 dimensions the graph edges, not the vectors, take most of it. float32 at ef = 128: p99 0.63 ms, 2,351 QPS on one thread.

scripts/ann-dataset.py glove-100-angular /tmp/ann --train 100000 --test 500 VELTRIX_ANN_DIR=/tmp/ann go test ./storage -run TestRealEmbeddings -v -timeout 30m

Over the network. GloVe-100, first 20,000 words, 300 queries, int8, 8 concurrent clients. Server and client on the same macOS machine (bench/compare/cmd/vecbench).

efrecall@10QPSp50p99
640.89424,6370.28 ms0.68 ms
2560.98515,6290.48 ms0.91 ms
cd bench/compare && go run ./cmd/vecbench -data /tmp/ann -train 20000 -quant int8 -threads 8

RAM per vector at 768 dimensions. Go heap per vector, 10,000 synthetic clustered vectors, macOS. The full float32 vectors always stay on NVMe; this is what RAM holds.

LayoutHeap / vectorMapped file / vectorvs float32
float323,383 B—1×
int81,078 B—3.1× less
pq, m = 96493 B—6.9× less
pq, m = 96 + disk graph378 B216 B8.9× less heap
VELTRIX_VECTOR_MEMORY=1 go test ./storage -run TestVectorMemoryTable -v

Tested up to 100K real vectors and 10K at 768 dimensions; nothing at 10M+ yet. No other vector database has been run on the same hardware. Tuning: performance tuning, vector search.

Stability: soak and crash chaos

TestResultReproduce
Search soak, 60 s, 4 writers + 4 searchers, 5K-id space (macOS)201,573 writes and 292,079 searches; 0 results outside the oracle of acknowledged writes; 288,025 self-queries with 0 misses; RSS flat at ~1.0 GB; exact oracle match before and after restartVELTRIX_SOAK_DURATION=60s go test ./tests/integration -run TestSearchSoak -v (nightly: 20 min on Linux)
Crash chaos, SIGKILL mid-write (macOS)3 cycles with 8,524 / 8,380 / 11,851 acknowledged writes, all visible after restart and index rebuild; no deleted id returnedVELTRIX_CHAOS_CYCLES=3 go test ./tests/integration -run TestSearchCrashRecovery -v (nightly: 10 cycles)
Raft failover read barrierwithout it, a new leader missed the acknowledged write in 20 / 20 test rounds; with it, 0 / 20go test ./consensus -run TestWaitLeaderApplied_FailoverVisibility -v

Writes in flight at the kill are not checked. After each cycle the test rewrites every such id to a fresh, fully acknowledged version, so later cycles check it again.

Transparency

Measure it on your own cluster.

Every node serves Prometheus metrics on /metrics (--metrics-addr, default :2112, alongside /healthz and /readyz). Below is an illustrative layout of what it exposes: metric names only, no values. It is not a snapshot of a real cluster.

ILLUSTRATIVE · metric names from metrics/prometheus.go every series carries a node label
Live keys
gauge
veltrixdb_storage_index_size_keys
Cache hit rate
gauge
veltrixdb_cache_hit_rate
Read latency
histogram
veltrixdb_storage_read_latency_seconds
GC emergency runs
counter
veltrixdb_vlog_gc_emergency_runs_total
Write path & durabilitystorage / vlog

veltrixdb_storage_write_latency_seconds
veltrixdb_storage_wal_flushes_total
veltrixdb_storage_wal_batch_entries_total
veltrixdb_vlog_garbage_ratio{disk}
veltrixdb_vlog_disk_slow{disk}
veltrixdb_storage_native_index_bytes

Integrity & clusterscrub / cluster

veltrixdb_scrub_corruption_total
veltrixdb_scrub_records_total
veltrixdb_cluster_nodes_total
veltrixdb_replication_lag_nanoseconds
veltrixdb_server_active_connections

Histograms give P50 / P95 / P99 with histogram_quantile() native_index_bytes is 0 on the Go map index (native index, since v1.6)
Active Development · September–October 2026

What we're working on in the engine.

All of this has shipped, in v1.6, v1.7, v1.11 and v1.12 (v1.13 was a documentation release; v1.16 is the latest). The experimental and investigating items ship as opt-in switches. Each item lists what we measured and under which conditions, including the result that did not go our way. No dates are promised.

01
Shipped
Index
Off-heap native index
On cgo builds (macOS included, and the Docker image) each shard’s index lives in a C++ table off the Go heap: 64 B records, 8 B tag|position slots, a per-shard key arena. Go’s garbage collector no longer scans it. The cost is ~19 ns more per cache-miss lookup, and large key counts need vm.max_map_count ≥ 262144. VELTRIXDB_INDEX=map opts out. Since v1.6.
0.27 ms
full GC at 5M keys
(was 21 ms)
02
Shipped
Durability
Binary WAL
A 48-byte little-endian header and a CRC32C over the whole record. It fixes a real bug: keys containing | or a newline truncated text-WAL replay, so acknowledged writes could be lost on a crash restart. The reader accepts binary and legacy text records mixed; --wal-format=text is the rollback. Since v1.6.
2.5×
faster replay
4× faster encode
03
Shipped
Write path
No skiplist re-insert on overwrite
Overwriting a live key no longer re-inserts it into the ordered index; only absent or tombstoned keys are inserted. Measured on server batch writes (8 clients × 1024-key MPUT, 1M-key space, macOS): 1.45M → 3.54M keys/s, P99 9.6 → 4.2 ms.
3.54M
keys/s batch writes
macOS
04
Experimental
Network
C++ network front-end (--net=cpp)
Opt-in, standalone mode only, binary PUT/GET/DEL/PING/MPUT/MGET only. On a 4-CPU CI runner it cut read P50 by ~45% (154 vs 286 µs) and poll read throughput rose 10%, but read P99 got worse (~3.0 vs 1.8 ms), batch writes were ~12% lower and io_uring did not beat poll. It is not faster overall. Since then, reads that need a disk read are finished off the event loop (deferred reads); that change has not been re-measured yet.
154 µs
read P50
P99 worse
05
Investigating
Storage
Bisecting the C++ storage layer
On the same runner, the C++ storage extras all on (io_uring write bridge + SQPOLL, batch engine, native index) measured 1.12M keys/s batch writes vs 1.77M with them off. Which part costs what is not known yet; scripts/net-bench.sh runs each engine variant separately to find out. Meanwhile the io_uring write bridge is opt-in (VELTRIXDB_URING_BRIDGE, default off).
1.12M
vs 1.77M keys/s
attribution pending
06
Shipped
Durability
Adaptive group commit
The WAL and VLog flush windows are now upper bounds, not waits. A lone writer is synced at once; concurrent writers share one fdatasync after an idle gap of about one sync. With an emulated 300 µs sync and the 15 ms default window (macOS), one writer’s P50 went 16.0 → 0.45 ms and 64 writers 4.0K → 37.5K writes/s at the same writes per sync. --group-commit=fixed restores the old behaviour. Full table. Since v1.12.
0.45 ms
1-writer P50, emulated sync
(was 16.0 ms)
07
Shipped
Search
Vector, text and hybrid search; PQ and the disk graph
Vector namespaces with filters and deletes, int8 quantization, BM25 text search and hybrid (rank-fused) search shipped in v1.11. v1.12 added product quantization (QUANT pq) and GRAPH disk, which moves layer-0 HNSW edges to a memory-mapped file. On GloVe-100 (100K words, in-process, macOS) float32 reaches recall@10 0.953 at ef = 256 with p50 0.77 ms. Searches are refused while the index rebuilds after a restart. All numbers.
378 B
heap / 768-dim vector, pq + disk
(float32: 3,383 B)
Batch writes · macOS
3.54M keys/s
8 clients × 1024-key MPUT · 1M-key space · P99 4.2 ms
Batch writes · CI runner
1.77M keys/s
4 CPUs · tmpfs · client on same host · Go front-end · P99 12.2–12.7 ms
Reads · CI runner
177–182K ops/s
same runner & configuration · P99 1.8–2.0 ms
Mixed-workload write P99
2.57ms
same runner · 1 ms flush window

Don't trust our numbers. Reproduce them.

git clone https://github.com/VeltrixDB/veltrixdb && cd veltrixdb && ./scripts/bench.sh
Pass/fail gates on packing density and GC emergency runs · the CI rows above come from scripts/net-bench.sh, the October 2026 tables from the commands printed under each (see BENCHMARKING.md)