Repository navigation
fix(bench): fair Redis-vs-Vertex measurement + single-shard/high-QPF readiness (supersedes #145/#146/#147) - #148
Merged
Conversation
…readiness Applies the fixes from an adversarial review of the stacked Vertex PRs (#145/#146/#147). Every measurement asymmetry in the open-loop / duration search harness ran one direction — it understated Redis — and there was no clean operating point to drive a fast single-shard/high-QPF Redis. Changes: Fairness (blocker/major): - Redis prime + two-barrier clock start (open-loop AND closed-loop-duration), mirroring Vertex: every worker connects + issues a discarded warm-up FT.SEARCH, then the measurement clock is stamped and released only once all workers are warm. Connection setup and the cold first query now fall OUTSIDE total_time and the latency percentiles. Barrier is deadlock-safe: a worker that fails to connect still waits both barriers. - Hoist all Redis client-side request construction out of the timed window: configured_index_name() resolved once (was a per-query env::var), and FT.SEARCH assembly split into build_ft_search_cmd() (pre-timer) + exec, so the timed window wraps only the RPC round-trip + reply parse, matching the Vertex boundary. - client_saturated is now decided ONLY by measured signals (client/system CPU); the pure parallel>cores "oversubscribed" term is demoted to an advisory field so a legitimately high-concurrency run against a fast shard is not auto-flagged. Correctness / robustness: - num_candidates clamps to >= top (was max(1)); Vertex requires approximateNeighborCount >= neighborCount. - Redis recycled-index length guard (clean Err instead of a worker panic). - Redis search connections get 120s read/write socket timeouts. - Overload-shed runs report rps=0 with drop/schedule-delay accounting instead of Err("No searches completed"). - Dropped requests no longer double-count as late / pollute schedule_delay. - --warmup-seconds allowed in closed-loop-duration mode (discarded warm-up phase for both engines); open-loop rep selection no longer keys on rps. - Reject non-finite/absurd --search-duration/--max-lateness-ms/--warmup-seconds. - CPU-sample window caveat documented (brackets whole search()). Verified: cargo build clean, cargo clippy 0 warnings, cargo fmt applied, 477 unit tests pass. (integration_dragonfly needs a live Docker service.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docker Build ValidationPlatforms tested:
Tests performed:
|
This was referenced Jul 19, 2026
fcostaoliveira
added a commit
that referenced
this pull request
Jul 22, 2026
… 0 default (#200) (#201) In the Vertex search paths an UNSET num_candidates was sent as `0` (and an unset fraction_leaf as `0.0`) — Vertex's "use the deployed index default" sentinel. So a sweep point that forgot to thread num_candidates ran at the index default (~150) while still being LABELLED with the config's claimed value: fewer candidates = less work = flattering QPS/latency at lower recall, breaking recall-matching against the Redis `ef` sweep (fairness gate #4 of the #148 review). Fix (resolve_approx_neighbor_count + resolve_search_knobs): - Honor the config's num_candidates when set (clamped to `top`, Vertex's floor). - When unset, fall back to the index's OWN configured approximateNeighborsCount (self.approx_neighbors) EXPLICITLY — the value the index was built with, sent as a real number, never the hidden 0 sentinel. approximate_neighbor_count is now always Some(...), so request()/build_find_neighbors_body never emit 0. - Log the effective knobs per config ("Vertex effective search knobs: approximateNeighborCount=… (config|index-default), fraction_leaf_nodes_to_search=…") so a sweep point is honestly labeled instead of silently measured at default. - Applies to REST + public/private gRPC (single resolution before the transport branch). - Pure resolver unit-tested: config honored, unset→index-default, clamped to top, never 0. README: document num_candidates/approximateNeighborCount resolution + logging. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Consolidates the stacked Vertex PRs #145 (approx-neighbor tuning), #146 (open-loop fixed-rate mode), #147 (Vertex gRPC transports + duration-bounded closed loop) and applies the fixes from a 24-agent adversarial review of that stack. Merging this lands all three PRs' functionality with the fairness defects corrected — it supersedes them.
Why
The open-loop / duration search harness had measurement asymmetries that all ran one direction — understating Redis — and there was no clean operating point to drive a fast single-shard / high-QPF Redis to its true throughput. This mattered because the Redis-vs-Vertex GloVe report is produced by this code, and a single-shard/highest-QPF Redis rerun is planned.
Changes
Fairness (blocker/major)
FT.SEARCH, then the measurement clock is stamped and released only once all workers are warm. Connection setup and the cold first query now fall outsidetotal_timeand the latency percentiles. The barrier is deadlock-safe — a worker that fails to connect still waits both barriers.configured_index_name()resolved once (was a per-queryenv::varinside the hot loop), andFT.SEARCHassembly split intobuild_ft_search_cmd()(pre-timer) +exec_ft_search(). The timed window now wraps only the RPC round-trip + reply parse, matching Vertex's boundary.client_saturateddecided only by measured signals (client/system CPU); the pureparallel > cores"oversubscribed" term is demoted to an advisory field so a legitimately high-concurrency run against a fast shard is not auto-flagged saturated.Correctness / robustness
num_candidatesclamps to>= top(wasmax(1)) — Vertex requiresapproximateNeighborCount >= neighborCount.Errinstead of a worker panic that aborts the run).rps=0with drop/schedule-delay accounting instead ofErr("No searches completed")— preserves the strongest overload signal.lateor pollute theschedule_delaypercentiles.--warmup-secondsallowed in closed-loop-duration mode (discarded warm-up for both engines); open-loop rep selection no longer keys onrps(which is pinned to the offered rate).--search-duration/--max-lateness-ms/--warmup-secondsinstead of panicking inDuration::from_secs_f64.search()).Deliberately NOT changed (adversarially rejected)
Vertex per-iteration body build,
grpc.ready()per call, and query-recycling recall — verified to either favor Redis or be non-issues.Rerun runbook (run-config, not code)
For the single-shard/highest-QPF Redis rerun to be fair, the operator must still: sweep client concurrency and report the QPS/latency knee (never a single over-saturated point); use
--repetitions 1with a real--warmup-seconds; match recall (tune Redisefand Vertexnum_candidates/leaf-fraction to the same measured recall, print both); pin Vertexnum_candidates=300(else it silently falls back to the index default of 150 — cheaper/lower-recall, flattering Vertex); assertfailed_queries == 0in every published cell; and size the loadgen so required concurrency andparallelstay ≤ cores.Testing
cargo buildclean ·cargo clippy0 warnings ·cargo fmtapplied · 477 unit tests pass.integration_dragonflyneeds a live Docker service (green in CI).🤖 Generated with Claude Code