Skip to content

perf(relay): bounded-concurrency multi-filter query execution (S2)#1457

Merged
tlongwell-block merged 2 commits into
mainfrom
perf/s2-concurrent-filter-exec
Jul 2, 2026
Merged

perf(relay): bounded-concurrency multi-filter query execution (S2)#1457
tlongwell-block merged 2 commits into
mainfrom
perf/s2-concurrent-filter-exec

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

What & why

Follow-up S2 reserved by #1452's follow-up list; sequenced after #1418 (merged, 7da936ff). Part of the buzz-gui-performance arc.

NIP-01 multi-filter requests (POST /query catch-all pass in api/bridge.rs, WS REQ historical delivery in handlers/req.rs) executed one db.query_events await per filter, serially. A K-filter request paid K sequential DB round trips — the structural cost behind get_channels' N per-channel last-message filters and any client-side request batching (which otherwise only saves HTTP RTT + NIP-98/auth tax, not DB time).

Design

Both loops split into three phases:

  1. Construct — pure query construction + validation, in filter order (bridge: access-scope skips, before_id 400, page offsets all decided here).
  2. Querydb.query_events via futures_util buffered(FILTER_QUERY_CONCURRENCY = 4) — bounded, order-preserving concurrency.
  3. Post-process — strictly in original filter order.

Because buffered (not buffer_unordered) yields in input order, phase 3 is byte-identical to the old serial loop:

  • NIP-01 OR/dedupe order: seen_ids insertion order = filter order; an event failing filter A stays eligible for filter B.
  • Per-event access gates unchanged and still result-level: event_in_accessible_channel, filters_match, reader_authorized_for_event, author-only.
  • Conformance trace rows emitted in filter order.
  • First-error-wins semantics preserved (WS: EOSE + return on first failed filter; bridge: 500 on first DB error).
  • Special-pass filters (feed_types, depth_limit, search) handled in earlier loops — untouched. Per-filter LIMITs unchanged.

One deliberate edge shift (bridge only): before_id validation (400) for a later filter now surfaces before any earlier filter's DB query runs — deterministic client errors now always precede transient DB errors. No caller depends on the old interleaving (validation errors were already request-fatal).

Why N=4: bounded well below the pool ceiling so one multi-filter request can't monopolise Postgres connections; a guard test pins the range so raising it forces a bench re-run.

Verification

  • cargo test -p buzz-relay: 453 passed, 0 failed (whole package, not scoped).
  • New filter_query_pipeline_preserves_filter_order: proves ordered yield when the first filter is the slowest (the case where buffer_unordered would break dedupe order).
  • New filter_query_concurrency_is_bounded guard.
  • cargo fmt + clippy clean; pre-commit/pre-push hook suites green.

Not in this PR (deliberate)

The dedicated DISTINCT ON (channel_id) last-message query for get_channels step 5 — separate follow-up diff so each change carries its own correctness argument. S2 helps every multi-filter query; DISTINCT ON kills the worst single call site.

npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d and others added 2 commits July 2, 2026 10:48
…nded concurrency (S2)

NIP-01 multi-filter requests previously awaited db.query_events once per
filter, serially — a K-filter POST /query paid K sequential DB round trips
(the structural cost behind get_channels' N last-message filters and any
batched client query). Both loops now run three phases:

  1. pure query construction + validation, in filter order
  2. DB reads via futures buffered(FILTER_QUERY_CONCURRENCY=4) —
     order-preserving bounded concurrency
  3. post-processing, strictly in original filter order

Because buffered() yields in input order, phase 3 is byte-identical to the
old serial loop: NIP-01 OR/dedupe order (seen_ids insertion = filter order),
per-event access gates (event_in_accessible_channel, filters_match,
reader_authorized_for_event, author-only), conformance trace row order, and
first-error semantics are all unchanged. Special-pass filters (feed_types,
depth_limit, search) are handled in earlier loops and untouched; per-filter
LIMITs unchanged.

One deliberate edge shift, bridge only: before_id validation (400) for a
later filter now surfaces before any earlier filter's DB query executes —
deterministic client errors now always precede transient DB errors.

New tests: filter_query_pipeline_preserves_filter_order (proves ordered
yield when the first filter is slowest) and a concurrency-bound guard.
cargo test -p buzz-relay: 453 passed, 0 failed.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
… assert

CI clippy runs --all-targets -D warnings, which lints test code and rejects
assertions_on_constants. The compile-time const assert at the definition
site is stronger anyway: violating the FILTER_QUERY_CONCURRENCY range now
fails every build, not just a test run.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
@tlongwell-block tlongwell-block merged commit a9e752e into main Jul 2, 2026
29 checks passed
@tlongwell-block tlongwell-block deleted the perf/s2-concurrent-filter-exec branch July 2, 2026 15:18
tlongwell-block pushed a commit that referenced this pull request Jul 2, 2026
* origin/main:
  perf(relay): bounded-concurrency multi-filter query execution (S2) (#1457)
tlongwell-block pushed a commit that referenced this pull request Jul 2, 2026
* origin/main:
  perf(relay): bounded-concurrency multi-filter query execution (S2) (#1457)

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
wpfleger96 added a commit that referenced this pull request Jul 2, 2026
…into HEAD

* origin/paul/nip-am-agent-turn-metrics:
  fix(profile): consolidate agent profile runtime metadata (#1451)
  fix(desktop): simplify workspace rail badges (#1462)
  perf(desktop): instant channel switching — non-blocking first paint, persisted snapshots (#1452)
  perf(relay): bounded-concurrency multi-filter query execution (S2) (#1457)
  fix(desktop): classify timeline prepends so history loads don't bump unread (#1416)
  fix(desktop): quiet gate for workspace switches instead of boot splash (#1449)
  fix(read-path): reach complete threads, dense-second timelines, and all people in the GUI (#1418)
  E1+E3: reduce relay ingest/fan-out DB round trips; ack p99 −7–16%, fd p99 −6–28%, p999 tails −29–53% vs PR #1453 tip (#1454)
  perf(relay): defer post-commit dispatch and avoid verify clone (#1453)
  fix(relay): include git hook tools in runtime image (#1326)
  feat(chart): per-pod emptyDir git scratch when persistence disabled (multi-replica HA) (#1450)
  fix(relay): remove media bearer-token auth (#1444)
  fix(desktop): stop search shortcut from hijacking the sidebar (#1447)

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant