tidaldb/tidal-stress/benches/hotpath.rs
jx12n bb21e69ae6 feat(m12): vector retrieval G1/G2 — recall harness, ANN in RETRIEVE, index tuning
m12p1 (measurement truth): TidalDb::vector_search_items pure k-NN probe +
POST /vector_search (standalone + region node, merge-by-distance) +
tidal-stress --verify-recall (deterministic id-keyed corpus, in-RAM brute-force
cosine oracle, open-loop ramp → recall@k + true p99 + read-knee + JSON/gate exit).
Repaired fabricated p99 columns (mean-as-p99) in social-scale.md / scale.rs.
Verified real: recall@10=0.9997 at 20k/1536-D vs brute-force.

m12p2 (G1 unblock): ANN candidate-gen wired into RETRIEVE — for_you=preference
vector, related=seed embedding (similar_to), graceful scan-fallback. Cached
per-signal-type top-K (signals/ledger/hot_top_k.rs, decay-order-invariant) so
trending serves O(K). related over HTTP (FeedQuery.similar_to). Harness gains
--feed-profile / --seed-preferences. Verified: trending retrieve p99 3.5-7.7ms.

m12p3 (G2): per-query ef_search now honored (RwLock epoch-guard with_expansion,
shared guard for same-ef concurrency) + dimension-aware brute→HNSW crossover
usearch_min_vectors(dim) + memory_usage() + examples/ann_grid_search.rs.
Measured 1536-D/100k clustered: default M=16/ef_c=400/F16/ef_s=200 clears
G1+G2 (recall 0.997, p99 1.4ms); F16 -0.25% vs F32; Int8 rejected (-28%).
Recall corpus is now clustered (Gaussian mixture) in grid + harness.
2026-06-14 11:07:09 -06:00

83 lines
3.3 KiB
Rust

//! Hot-path micro-benchmarks for the load generator's OWN per-request cost.
//!
//! These run with NO server and NO network, so they isolate exactly the work
//! the generator does per dispatched request — request construction
//! ([`Workload::next`]) and latency aggregation ([`LatencyHistogram::record`]).
//! That is the only honest way to answer "is this allocation material?": the
//! number here is the generator's CPU floor, the thing that must stay far below
//! the network round-trip it measures, and the before/after the perf sweep is
//! graded against.
use std::hint::black_box;
use std::time::Duration;
use criterion::{Criterion, criterion_group, criterion_main};
use tidal_stress::metrics::LatencyHistogram;
use tidal_stress::workload::{Workload, parse_mix};
fn make_workload(mix: &str) -> Workload {
Workload::new(
// Three read gateways (round-robin), writes pinned to one gateway =>
// write_bases len == 1, the headline `--leader-url` path.
vec![
"http://10.0.0.1:9500".into(),
"http://10.0.0.2:9500".into(),
"http://10.0.0.3:9500".into(),
],
vec!["http://10.0.0.1:9500".into()],
parse_mix(mix).expect("mix preset parses"),
10_000, // corpus
50_000, // users
1.3, // hot_skew
24, // feed_limit
128, // embedding_dim
None, // forced_profile (use the weighted feed-profile mix)
)
}
/// `Workload::next` — the per-dispatched-request construction cost (URL String +
/// body). `peach` is the headline signal-dominated mix; `writes` forces the body
/// path on every call; `reads` isolates the URL-only feed/search path.
fn bench_next(c: &mut Criterion) {
let wl_peach = make_workload("peach");
let wl_writes = make_workload("writes");
let wl_reads = make_workload("reads");
let mut rng = rand::rng();
let mut g = c.benchmark_group("workload_next");
g.bench_function("peach", |b| b.iter(|| black_box(wl_peach.next(&mut rng))));
g.bench_function("writes", |b| b.iter(|| black_box(wl_writes.next(&mut rng))));
g.bench_function("reads", |b| b.iter(|| black_box(wl_reads.next(&mut rng))));
g.finish();
}
/// `LatencyHistogram::record` — the per-OK-request collector cost (single-writer,
/// allocation-free), plus the per-stage `percentile` report cost.
fn bench_histogram(c: &mut Criterion) {
c.bench_function("histogram_record", |b| {
let mut h = LatencyHistogram::default();
// Cheap LCG so successive records land in different buckets rather than
// hammering one — closer to a real latency distribution.
let mut state = 0x2545_F491_4F6C_DD1Du64;
b.iter(|| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let ns = (state >> 33) % 200_000_000 + 1; // ~1ns..200ms
h.record(black_box(Duration::from_nanos(ns)));
});
});
let mut h = LatencyHistogram::default();
for i in 1..=100_000u64 {
h.record(Duration::from_nanos(i * 1_500));
}
c.bench_function("histogram_p99", |b| {
b.iter(|| black_box(h.percentile(black_box(0.99))));
});
}
criterion_group!(benches, bench_next, bench_histogram);
criterion_main!(benches);