Single-Node Benchmark Evidence — August 2026
This document records a measured, reproducible single-node comparison
of ArcherDB against Valkey and PostGIS on identical hardware, using the
repo’s own harnesses. Raw outputs live in
reports/benchmarks/evidence-20260803/.
Headline (single node, all ArcherDB writes durable + checksummed):
| System | Configuration | Insert throughput |
|---|---|---|
| ArcherDB (native client, max batches) | durable, consensus-committed | 831,000 events/s |
| ArcherDB (Python SDK, pipelined) | durable, consensus-committed | 659,000 events/s |
| Valkey 8.1 GEOADD (best of any pipelining) | no persistence | 174,000–202,000 ops/s |
Valkey 8.1 GEOADD (appendfsync always, pipelined) |
durable per op | 109,000 ops/s |
Valkey 8.1 GEOADD (appendfsync always, sequential) |
durable per op | 1,474 ops/s |
| PostGIS 16 (GIST, batched inserts) | durable, no consensus | 29,000–31,000 rows/s |
ArcherDB’s durable, replicable write path is 4–4.8× faster than Valkey’s volatile GEOADD ceiling, 7.6× faster than Valkey configured for comparable durability, and ~27× faster than PostGIS — while also serving spatial queries with sub-millisecond p99 latency.
Test environment
- AMD EPYC (KVM guest), 8 vCPUs, 24 GB RAM, Linux 6.8.0-124-generic.
- Virtio disk: ~571 µs per 4 KiB O_DIRECT+O_DSYNC write (7.2 MB/s), 901 MB/s for 1 MiB durable writes. Datacenter NVMe with power-loss protection has 10–20× lower sync-write latency, which disproportionately improves ArcherDB’s small-batch and Valkey’s fsync-always numbers.
- ArcherDB:
-Drelease -Dconfig=lite, single replica, O_DIRECT + O_DSYNC WAL (two durable writes per request), Aegis-128L checksums on every message. The lite tier shares the 10 MiB message envelope with all production tiers. - Valkey 8.1.9: official docker image, loopback inside the container
netns. Default configuration (
--save '' --appendonly no, io-threads=1) unless noted; the fsync rows useappendonly yes+appendfsync always. - PostGIS 16-3.4: official docker image, schema + GIST index from
scripts/competitor-benchmarks/setup-postgis.sh,psycopg2.execute_valuesbatched inserts. - Runs are serial; each measurement waits for system load < 2.0.
ArcherDB: insert throughput vs. request batch size
Native load generator (archerdb benchmark, in-process
VSR client, one request in flight per client). Every request is
consensus-committed and durable on disk before its reply.
| Events per request | Clients | Events | Throughput |
|---|---|---|---|
| 240 | 1 | 200K | 182,586 events/s |
| 1,000 | 1 | 500K | 344,232 events/s |
| 8,000 | 1 | 1M | 620,143 events/s |
| 8,000 | 8 | 1M | 637,246 events/s |
| 65,535 | 1 | 1M | 661,747 events/s |
| 81,916 (max) | 1 | 1M | 901,823 events/s |
| 81,916 (max) | 1 | 2M | 830,677 events/s |
Reading the curve:
- Small requests are bounded by the durable-write floor: each request pays two serialized O_DIRECT+O_DSYNC writes (~1.2 ms on this disk) regardless of size. Batching amortizes that cost; this is the design point, not a trick — one 10 MiB request carries up to 81,916 128-byte events.
- At large batches the single replica core becomes execute-bound (~1 µs/event: S2 cell computation, cuckoo RAM-index upsert, LSM insert).
- Concurrent clients neither help nor hurt materially at equal event totals (8,000-event batches: 620K/s at 1 client vs 637K/s at 8) — single-node writes are intentionally sequential in the WAL for crash-recovery correctness. Sustained multi-million-event runs shed ~8% (max batches) to ~25% (8K batches) to LSM compaction pacing; reducing per-commit compaction overhead at high commit rates is tracked as follow-up work.
ArcherDB: query latency (single node, 2M events loaded)
Measured in the same run as the 2M-event insert
(N5-bmax-c1.txt):
| Query | p50 | p99 |
|---|---|---|
| UUID point lookup | 92 µs | 323 µs |
| UUID batch (per entity) | 1 µs | 3 µs |
| Radius (1 km) | 91 µs | 191 µs |
| Polygon | 87 µs | 327 µs |
PostGIS on the same box and dataset shape: UUID lookup 3,461/s (~289 µs each, best case, no concurrency), radius 1,508/s (~663 µs), polygon 5.6/s (~178 ms).
Python SDK
All runs: fresh single-node server, 393K–400K events, zero errors.
| Shape | Throughput |
|---|---|
benchmark_geo.py defaults (8,000-event requests,
sequential) |
421,282 events/s |
| 65,535-event requests, sequential | 475,540 events/s |
insert_events_pipelined (8,000-event requests, window
8) |
659,389 events/s |
| same shape, strictly synchronous | 316,514 events/s |
Pipelining (GeoClientSync.insert_events_pipelined /
NativeClient.insert_events_pipelined) overlaps client-side
packing with server round trips for a 2.1× gain over synchronous
submission at the same batch size.
Before the August 2026 fixes the same harness measured 59–71K events/s: it capped requests at 240 events (a stale 32 KiB assumption) and the SDK built events one ctypes field at a time. If you have older numbers on file, they measured the harness, not the server.
Valkey comparison notes
- GEOADD is Valkey’s closest analog to an ArcherDB event insert. Its ceiling on this box is ~174–202K ops/s regardless of pipelining depth or connection count (single-threaded server, ~5 µs/op); plain SET reaches 752K ops/s, which bounds any Valkey-based design from above.
- Default Valkey acknowledges writes from memory only. A crash loses
every write since the last RDB snapshot — with snapshots disabled (the
default
--save ''in many deployments), the entire dataset. ArcherDB replies only after the write is fsync’d and consensus-committed. - With
appendfsync always(the durability-comparable configuration), Valkey drops to 109K ops/s pipelined — and 1,474 ops/s for sequential clients, because each op pays the same ~0.6 ms sync-write cost that ArcherDB amortizes across up to 81,916 events per request.
Reproduction
# ArcherDB native curve (fresh temp server per run):
./zig/zig build -Drelease -Dconfig=lite
./zig-out/bin/archerdb benchmark --event-count=1000000 --event-batch-size=8000
./zig-out/bin/archerdb benchmark --event-count=2000000 # max batches
# Python SDK (server on :3001):
./zig-out/bin/archerdb format --cluster=0 --replica=0 --replica-count=1 0_0.archerdb
./zig-out/bin/archerdb start --addresses=127.0.0.1:3001 0_0.archerdb &
python3 benchmark_geo.py --events 400000 --batch-size 8000 --addresses 127.0.0.1:3001
# Valkey (docker):
docker run -d --name valkey valkey/valkey:8.1 valkey-server --save '' --appendonly no
docker exec valkey valkey-benchmark -n 2000000 -c 8 -P 64 -r 10000000 -q \
GEOADD fleet 13.361389 38.115556 m:__rand_int__
# PostGIS (docker):
docker run -d --name postgis -e POSTGRES_USER=bench -e POSTGRES_PASSWORD=bench \
-e POSTGRES_DB=geobench -p 127.0.0.1:5433:5432 postgis/postgis:16-3.4
# schema: scripts/competitor-benchmarks/setup-postgis.sh
python3 scripts/competitor-benchmarks/benchmark-postgis.py --port 5433 \
--event-count 200000 --batch-size 8000 --jsonCaveats: single-node lite tier on shared virtualized hardware; a
background load gate (< 2.0) was enforced but the host is not
dedicated. Numbers on dedicated hardware with power-loss-protected NVMe
will be higher, especially for small batches. Multi-node cluster
benchmarks are tracked separately (docs/BENCHMARKS.md
targets: ≥770K events/s on 3 nodes).