tidaldb/docs/profiling/scale-baselines.md
jx12n 6651c14adc feat(m11): cluster security (m11p7) + perf instrumentation floor
m11p7 — secure the cluster, all opt-in (pre-m11p7 byte-for-byte):
- gRPC replication mTLS by default via a custom tokio-rustls acceptor +
  DynamicCertResolver; zero-drop content-hash cert rotation (k8s ..data swap,
  no pod restart, no inotify)
- inter-node HTTP TLS sharing the same resolver (one rotation, both planes) +
  per-node keyed-BLAKE3 signed x-tidal-node-token; marker-without-token -> 403
- admin audit log (operator-leg only) + per-principal rate limit (engine
  RateLimiter; sibling nodes exempt)
- k8s cert-manager manifest (certs.yaml) + scripts/gen-cluster-certs.sh fallback;
  secret.example.yaml gains TIDAL_CLUSTER_KEY (file-mounted, hot-rotatable)
- exit gate verified real: mtls.rs (gRPC foreign-pod), cluster_security.rs
  (HTTP foreign + zero-drop rotation under load), 7 security unit tests

perf — instrument floor (sweep Wave 1):
- new tidal/benches/wal.rs + tidal-server/benches/scatter.rs
- p99->mean honesty relabel; sweep manifest at docs/reviews/perf-sweep-2026-06-13.md
- add @tidal-performance agent (Martin Thompson)

new: cluster/{audit,http_tls,security}.rs, tests/cluster_security.rs,
docs/planning/milestone-11/phase-7.md
2026-06-13 01:25:35 -06:00

106 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Scale Benchmarks: 1M-Item Baselines
## Hardware
macOS Darwin 23.6.0 (Apple Silicon / x86-64 — see run date below).
**Run command:**
```bash
cargo bench --manifest-path tidal/Cargo.toml --bench scale
```
**Date:** 2026-02-23
## Dataset
| Parameter | Value |
|-----------|-------|
| Items | 1,000,000 |
| Creators | 10,000 (100 items/creator) |
| Categories | 20 |
| Embedding dim | 128 (not 1536 — reduced for bench RAM) |
| Signal coverage | 10% view, 5% like |
| Bench tool | Criterion (sample_size=10, 30s measurement, Flat mode) — **closed-loop, single-threaded; reports the mean, not the tail** |
## Measurement contract (read before trusting any number below)
> The `time:` figures Criterion reports are **single-threaded mean per-op cost
> under a closed loop** — the `[lower mean upper]` triple is a *confidence
> interval on the mean*, **not** a latency distribution. They are
> **regression tripwires**, not tail-SLO evidence.
>
> A `p99`/`p999`/tail SLO can only be honored by an **open-loop,
> coordinated-omission-corrected** measurement under real concurrency (the
> `tidal-stress` ramp). A closed-loop mean cannot observe the tail it hides:
> when the system stalls, a closed-loop harness simply *stops sending*, so the
> queue that would inflate p99 never forms. Reporting a mean under a `p99` label
> understates the tail by 10100× under load.
>
> Therefore every row below is labelled **isolated per-op cost (mean)**. Tail
> SLOs are validated separately — see `tidal-stress` (open-loop) for the
> authoritative p99/p999 under the production workload.
## Acceptance Criteria (isolated per-op cost — regression tripwires, NOT tail SLOs)
| Benchmark | Tail SLO (validated open-loop) | Isolated per-op cost (mean, closed-loop) | Tripwire |
|-----------|--------------------------------|------------------------------------------|----------|
| RETRIEVE | < 50ms p99 | **152 µs** (for_you) | well under |
| SEARCH | < 100ms p99 | **28.9 ms** (text_only) | well under |
| Signal write | < 100µs p99 | **82 ns** (rotating 1K) | well under |
The mean per-op costs sit far under the tail targets a necessary but **not
sufficient** condition for the p99 SLO. "Well under" means the *mean* clears the
target with headroom; the p99 itself is signed off only by the open-loop
`tidal-stress` run, never by this table.
## Benchmark Results
### RETRIEVE (1M items)
```
retrieve_1m/for_you time: [151.88 µs 152.13 µs 152.40 µs]
retrieve_1m/trending time: [127.96 µs 128.25 µs 128.52 µs]
retrieve_1m/new_filtered time: [ 7.5636 µs 7.5855 µs 7.6058 µs]
```
**All RETRIEVE queries < 200µs.** The 50ms target is beaten by 3 orders of magnitude.
- `for_you`: signal-scored ranking over full 1M-item universe 152µs
- `trending`: windowed view count ranking 128µs
- `new_filtered`: category filter at ~5% selectivity 7.6µs (bitmap pre-filter eliminates 95% of candidates)
### SEARCH (1M items)
```
search_1m/text_only time: [28.844 ms 28.934 ms 29.021 ms]
search_1m/text_filtered time: [ 1.8972 ms 1.9104 ms 1.9220 ms]
```
**Both SEARCH queries < 30ms.** The 100ms target is beaten by 3-50×.
- `text_only`: BM25 over 1M documents 28.9ms (most expensive path; dominated by Tantivy posting list traversal)
- `text_filtered`: BM25 with category filter reduces candidate set 1.9ms
### Signal Write (1M-item DB, rotating 1K entities)
```
signal_write_1m/write_rotating_1k_entities time: [82.033 ns 82.286 ns 82.535 ns]
```
**82 ns per write.** The 100µs target is beaten by 1,200×. DashMap hot-path write amortises to sub-100ns across 1K rotating entity IDs.
## Setup Notes
The `LazyLock<TidalDb>` pattern ensures the 1M-item database is built exactly once per bench run. Build time ~30s on the reference hardware above. The text syncer waits 3s after ingestion.
## Database Build Time
Approximately **30 seconds** on reference hardware (observed from `[scale bench] Database ready` log line).
## Analysis
tidalDB's **isolated per-op mean cost** sits well within all three
acceptance-criteria targets at 1M items. The dominant cost is SEARCH text_only at ~29ms driven by Tantivy posting list traversal across 1M documents. The LogMergePolicy tuning (< 20 segments at steady state) keeps this below the 100ms target with headroom. **The p99 tail SLOs themselves are signed off by the open-loop `tidal-stress` ramp, not by these closed-loop means** (see the measurement contract above).
Signal writes at 82ns confirm the DashMap hot-path is not a bottleneck at this scale. The 5M-entry LRU trimming threshold (DEFAULT_MAX_SIGNAL_ENTRIES) provides ample headroom for the 100K-item signal coverage in this benchmark (~200K entries = ~218MB).