Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/scalable-bench-design.md

Scalable bench design

How nornir's benchmark pipeline scales — from one developer running nornir bench run on a laptop to many machines streaming results into one warehouse served over the network. This is the scaling companion to bencher.md (the bench framework / contract) and warehouse.md (the Iceberg store). Both design rationale and a reference for the work still open.

The shape of the problem

A bench result is one BenchRun (version, machine, cores, date, a flat set of <corpus>[_<variant>] metrics + optional per-test pass/fail). At scale there are three independent axes:

The design keeps the producer (running benches) and the store (Iceberg) loosely coupled so each axis scales on its own.

Today's path (the baseline)

child repo  examples/nornir-bench.rs  ──prints one BenchRun JSON line──►  nornir
nornir  ──parse──►  append_bench_run  ──►  bench_runs + bench_results (Iceberg)
                                       └─►  bench_history.jsonl (chmod -w receipt)

A child repo opts in via [dev-dependencies] nornir + impl Bencher + register_bench! + examples/nornir-bench.rs; nornir is invisible to production builds. The JSONL file is the on-disk receipt + migration source; Iceberg is the source of truth (last_run reads iceberg-first).

This is correct but has two scaling limits: the JSON-file hop on the live path, and a single-threaded warehouse write.

Write path & parallelism — the catalog pipeline.rs pattern

warehouse::iceberg::append_batch is the simple path: one task builds one ParquetWriter, writes one RecordBatch, close(), fast_append().commit(). iceberg-rust does not parallelize this — the parquet ArrowWriter encodes serially; it just hands you the writer + transaction primitives.

The scalable topology is the one skade-katalog prototyped in its bench harness (bench/src/pipeline.rs, the "znippy pattern"):

N encode workers              bounded channel            1 writer task
RecordBatch -> Parquet bytes  ──►  mpsc(depth)  ──►  sequential put + fast_append
     (CPU, all cores)                                  (I/O + commit, 1 core)

Many CPU threads encode Parquet in parallel; one thread does the I/O + commit (the single-writer invariant the redb catalog requires anyway).

When to adopt it: only for bulk writes — the embeddings table (768-dim float lists across many chunks) and large bench-result imports (many machines × many runs ingested at once). For nornir's typical append (one BenchRun, a few rows → one snapshot) the parallel encode just adds overhead; the catalog's own header warns the single-writer split can lose to per-task encode+write on fast NVMe at small scale. Measure before adopting; leave append_batch serial until a bulk path proves the win.

Bencher under the service — RPC ingest, no JSON hop

To scale across machines, the producer should record straight into the warehouse over the network instead of via a local JSON file:

bench host (any machine)  ──RecordBench RPC──►  nornir-server  ──►  append_bench_run
                                                (sole warehouse-lock owner)

Storage layout for scale

Open work (tracked in plan.md)