Comparison: bunostrdb vs nak vs deed
Cross-tool benchmark across ~20 fields: binary size, startup, one-shot commands, streaming throughput, store ingest, query shapes, and memory — nak (Go) and deed (Zig) against bunostrdb (in-process library).
Harness: benches/spark/ (spark.sh drives the CLI tools, spark-bun.ts the library leg). Reproduce with bash benches/spark/spark.sh (or just bench_spark).
Conditions (2026-09-27)
- bunostrdb at
ece9b25, nativelibnostrdb.sobuilt from the pinned submodule (C9c55578family). nak: release build from master91e362b(nak version v0.19.3-248-g91e362b, 33M, Sep 2026).deed0.3.1, builtReleaseSafe+ stripped with pinned Zig 0.16.0.- Corpus: 10,000 deterministic signed events from
benches/gen-dataset.ts(7,500×kind-1 + 2,500×kind-30023), shared by all three legs. - Machine: 12 cores, load average 10–13 during the runs (emulator, Vite, build jobs). Absolute numbers are load-contaminated — read ranges and ordering, not single values. Legs ran sequentially; min–max over 2–4 reps per leg (startup/one-shot best-of-30/50, bunostrdb queries best-of-5, deed
--localbest-of-30).
A. Size / startup / memory
| Field | bunostrdb | deed | nak |
|---|---|---|---|
| Binary | libnostrdb.so 4.0M + shim 16K (needs Bun runtime) | 2.5M stripped | 33M release |
version best (median) of 30 | bun --version 1.22ms (1.50ms); CLI --help spawn ~450ms | 0.96ms (1.44ms), ~0.3ms own share over /bin/true 0.62ms | 124ms (133ms) |
Peak child RSS, 10k sign/verify (Linux RUSAGE_CHILDREN) | — | 12 / 15M | ~110 / 111M (--version alone 69M) |
B. One-shot commands (best of 50)
| Field | deed | nak |
|---|---|---|
| key generate | 0.73ms | 131ms (startup floor) |
| decode npub | 0.73ms | 131ms |
| sign 1 event | 0.86ms | 136ms |
| verify 1 event | 1.57ms | 142ms |
Every nak one-shot sits on its ~125ms startup floor; the per-op cost only shows in the streaming leg. bunostrdb signs in-process: 0.26–1.25ms per event across reps (800–3,900/s, load-dependent).
C. Streaming throughput (10k records via stdin)
| Field | bunostrdb | deed | nak |
|---|---|---|---|
| sign | 1.7–5.8k/s (NoteBuilder FFI loop) | 12–18k/s | 5.9–6.0k/s |
| verify | — (verify happens at ingest; see D) | 15–22k/s | 4.7–5.6k/s |
| decode npub | — | ~570k/s | ~60k/s |
D. Store ingest (10k events)
| Field | bunostrdb | deed | nak |
|---|---|---|---|
| submit / queue | 27–88ms batch (processEvents, 113–375k/s) | — | n/a — no persistent local store (its LMDB backend holds outbox hints/spell cache only) |
| commit-visible | 0.22–3.2s (3–45k/s); verify-ON run 2.9s, same band | 8.0–8.1s (~1.2k/s) via loopback relay.py, each signature checked | closest analogue: serve + req round-trip (see E) |
| store file | LMDB 17–21M | 16M | — |
Verification cost lands on the ingester threads and overlaps the commit band: skipVerify: true and verify-ON commits fall in the same range on this box.
E. Queries
| Field | bunostrdb (in-process, best of 5) | deed --local (whole command incl. ~0.9ms startup, best of 30) | nak (loopback serve round-trip) |
|---|---|---|---|
| by id | 0.4–6ms typical (one 110ms outlier under load) | 0.89ms | 0.30s |
| author + kind-1, limit 50 | 0.01–0.03ms | 0.86ms | — |
| kind-1, limit 500 | 0.10–0.17ms | 2.49ms | 1.46s |
| kind-1 all (7.5k), ids | 1.7–4.8ms | 21ms full JSON | — |
| kind-1 full JSON | 19–34ms | — | — |
| kind-1 count | 3.8–12.5ms | — | — |
tag t= (empty index; corpus has no tags) | 0.02–0.05ms | not measured | — |
text search benchword (128 cap) | 0.36–1.8ms | n/a (no API) | n/a |
| profile lookup (empty) | 0.12–0.14ms | 0.85ms | — |
Takeaways
- nak's cost is process startup: ~125ms per invocation from the release build. Streaming rates (6k sign / 5k verify / 60k decode per s) are its honest per-op numbers.
- deed owns one-shot and streaming: sub-ms commands, 15–22k verify/s, ~570k decode/s, in a 2.5M binary. Its
--localstore reads stay tiny whole-command (0.9ms point, 2.5ms/500, 21ms/7.5k). - bunostrdb owns in-process queries: 0.01–0.2ms point/author/ capped lookups, 2–5ms id-scan of 7.5k, plus the only text search (~0.5ms). Batch submit is 27–88ms for 10k; the commit band overlaps signature verification either way. Per-event FFI signing is its slowest CPU op next to deed.
Reproduce
bash benches/spark/spark.sh # or: just bench_spark
# Smaller corpus / custom work dir (binaries via env):
N=2000 NAK_BIN=/path/to/nak DEED_BIN=/path/to/deed DEED_RELAY_PY=/path/to/relay.py SPARK_WORK=/tmp/spark bash benches/spark/spark.sh
# Library leg only:
bun benches/spark/spark-bun.ts <events.jsonl> [--verify-on]Pin notes: deed must be built with its pinned Zig; relay.py ignores the request filter, so the store leg asks for every kind the corpus holds; nak serve needs a few seconds to load the corpus before it listens.
