Scale benchmark

July 26, 2026 · View on GitHub

Measures Perseus Vault at corpus sizes past the previously-published 10K numbers, driving the real binary over MCP stdio (one persistent process per size — the numbers reflect per-op cost, not process spawn). Fully offline, deterministic (seeded corpus), no network, no API key.

Measured baselines (committed report.json)

Post-optimization run — v2.19.1 main with #476 (signature-driven dedup scan), #507 (covering dense index), and #511 (hybrid sparse-arm hydration + concurrent arms) merged; AMD64 16-core, Windows 11 (full hardware + commit in report.json, sha256-fingerprinted):

Metric10K100K
Write throughput, sustained479/s40/s
Write throughput, first→last 10%1370/s → 288/s469/s → 18/s
fts5 recall p50 / p993.1 / 9.6 ms15.7 / 23.2 ms
dense recall p50 / p9912.0 / 14.3 ms25.4 / 32.8 ms
hybrid recall p50 / p9919.0 / 24.6 ms79.7 / 92.9 ms
as_of point lookup p500.17 ms0.12 ms
temporal recall p503.7 ms12.5 ms
Cold start (spawn + init + first query)56.6 ms58.2 ms
DB on disk~66 MB~890 MB

Headlines:

  • Bi-temporal stays flat at scale. as_of point lookups sit at ~0.1 ms p50 at both 10K and 100K, and transaction-time reconstruction recall is under 20 ms p99 at 100K. The differentiator holds.
  • The write path is fixed. The first baseline measured 141/s → 7/s from 10K → 100K (a ~4h bulk load, O(N·body_size) dedup scan per write); after #476 it is ~480/s → ~40/s and a 100K load takes ~40 minutes. History and methodology in PERF.md.
  • Recall is flat-ish at scale in every mode. Dense: 390 ms p50 @100K before #507's covering index, ~25 ms after. Hybrid (the default mode): 263 ms p50 @100K before #511, 79.7 ms after — the "fusion-machinery overhead" was the sparse arm hydrating every FTS match; it now ranks inside the FTS index and the two arms run concurrently. Attribution tables in PERF.md.

v2.21.0 Cloud Linux validation (non-canonical)

Fresh v2.21.0 artifacts are committed alongside the canonical baseline:

They were produced on a hosted 8-vCPU Linux container (Linux 6.12.91-fly), not the AMD64 16-core Windows 11 baseline host above. Treat them as operational validation evidence, not as a version-to-version performance comparison.

Metric10K100K
Write throughput, sustained45/s26/s
Write throughput, first→last 10%66/s → 46/s50/s → 14/s
fts5 recall p50 / p995.3 / 117.0 ms77.2 / 125.0 ms
dense recall p50 / p9940.7 / 45.6 ms80.7 / 166.3 ms
hybrid recall p50 / p9941.6 / 64.0 ms319.8 / 390.9 ms
as_of point lookup p50 / p990.16 / 0.35 ms0.20 / 44.0 ms
temporal recall p50 / p995.0 / 8.4 ms63.3 / 80.2 ms
Cold start (median)23.8 ms228.7 ms
DB on disk92.2 MB923.8 MB

Caveats recorded with the artifacts:

  • The canonical gate budgets are calibrated to the Windows baseline host. On this shared Linux container, the 10K artifact fails write and fts5-tail budgets, while the 100K artifact fails the fts5/dense/hybrid/as_of/temporal tail budgets. That is a host/provenance mismatch, not evidence of a v2.21 regression without a same-hardware A/B run.
  • Embedding coverage differed materially between environments (302 embedded rows at 100K here versus 8,115 in the canonical baseline), so dense and hybrid rows are not comparable even before hardware differences.
  • Tail outliers (for example as_of p99 at 100K) are consistent with a noisy shared container. Public claims remain pinned to report.json until a controlled same-host rerun supersedes it.

Running

cargo build --release
python benchmark/scale/run.py                          # 10K + 100K (~45 min)
python benchmark/scale/run.py --sizes 10000            # quick (~1 min)
python benchmark/scale/run.py --sizes 1000000          # 1M — manual/nightly (~7h extrapolated)
python benchmark/scale/run.py --skip-embed             # no dense index build

Raw runs write to OS temp by default; the committed report.json is a curated artifact — regenerate deliberately with --out benchmark/scale/report.json.

Latency budgets (enforced by gate.py)

Per-size budgets with ~3× headroom over the measured baselines (more on sub-millisecond metrics where absolute jitter dominates), so CI-runner variance doesn't flake and a failure means a genuine regression. Override any budget via SCALE_BUDGET_<NAME>; select the size via SCALE_GATE_SIZE.

Budget10K100KEnv override
Write throughput (sustained)≥ 150/s≥ 15/sSCALE_BUDGET_WRITE_DOCS_PER_SEC
Write throughput (last 10%)≥ 100/s≥ 7/sSCALE_BUDGET_WRITE_LAST10_DOCS_PER_SEC
fts5 recall p99≤ 30 ms≤ 100 msSCALE_BUDGET_FTS5_P99_MS
dense recall p99≤ 60 ms≤ 150 msSCALE_BUDGET_DENSE_P99_MS
hybrid recall p99≤ 100 ms≤ 250 msSCALE_BUDGET_HYBRID_P99_MS
as_of p99≤ 5 ms≤ 5 msSCALE_BUDGET_AS_OF_P99_MS
temporal recall p99≤ 15 ms≤ 50 msSCALE_BUDGET_TEMPORAL_RECALL_P99_MS
Cold start (median)≤ 500 ms≤ 500 msSCALE_BUDGET_COLD_START_MS

Hybrid's 100K budget was tightened by #511 (1000 → 250 ms) and now sits deliberately BELOW the pre-#511 measured p99 (282–337 ms), so the sparse-arm hydration disease cannot silently return.

CI (scale-gate.yml): the 10K gate runs on every push to main (about a minute of load); the 100K run is weekly (~45 min post-#476) and on workflow_dispatch.

1M note

1M is manual/nightly: at the post-#476 write rate a 1M load extrapolates to roughly 7 hours — feasible on a dedicated box, not in a CI job. If anything degrades non-linearly on your hardware (recall, as_of, cold start), file a follow-up with the profile.