relay: gate push enqueue on live leases; batch matcher pipeline (T1b/T1a-repair/T2b)#2145
Merged
Conversation
Two write-amplification fixes for the push pipeline (T1a repair, T1b, T2b of the write-amp plan): Migration 0023 (T1b push gate): skip the push_match_queue enqueue for communities with no active, endpoint-enabled, unexpired push lease. In lease-less communities (most of them) every durable message paid the full matcher cost — enqueue + claim + lease scan + delete — to conclude "notify no one". The gate lives in the events trigger so every durable producer is covered. The lost-wake race (event reads "no lease" while a lease activation commits concurrently) is closed with a per-community advisory lock: event inserts take it SHARED, lease transitions that can make eligibility true (accept_lease_event, replace_lease) take it EXCLUSIVE, plus a product-recovery backfill on activation. Migration 0024 (T1a repair): the 0022 TTL-refresh trigger took FOR UPDATE on the channel row before testing ttl_seconds, so every durable message in a permanent channel serialized at commit time on that tuple, each holding it across its WAL flush — one hot channel meant fully serialized commits (observed live: 0.07ms -> ~15ms commit latency at 200 QPS). The trigger now synchronizes on a shared per-channel advisory lock; TTL transitions in update_channel take the same key EXCLUSIVE, so the stale-prefetch proof from 0022 is preserved without serializing the permanent-channel hot path. T2b (matcher batching): the relay push matcher now claims per-community batches (single SKIP LOCKED CTE, cap 64) and loads events, leases, and channel membership pairs in one statement each; completes and retries are set-wise under the shared claim fence. One batch costs a constant number of statements regardless of size. The poison-job reap moves off the claim path to a 30s sweep (its scan is not served by the due partial index), and idle matcher/delivery workers back off 250ms -> 2s. The per-job claim API is deleted, not deprecated — no second code path. Tests: batch fence contract, single-community batch claim, T1a barrier (8 concurrent permanent-channel commits, zero channel tuple locks), T1b forced lost-wake ordering + activation backfill, exhausted-job sweep. Full buzz-db + buzz-relay suites green on a fresh 0001-0024 database (lone red: api::mesh_demo echo test, also red on main). Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Max's red-team of cf018e1 found the remaining T2b hole: every successful (event, lease) match still called enqueue_push_wake inside the matcher loop — its own transaction, a lease SELECT ... FOR UPDATE, one outbox insert, one commit. Live-lease workload therefore stayed O(events x matching leases) transactions/WAL flushes, and the "constant statements per batch" claim only held up to the wake enqueue. New set-wise primitive push::enqueue_wakes: one transaction per batch — lease revalidation via a single ordered FOR UPDATE over the distinct (author, installation) set (the ORDER BY pins lock order against replace_lease and sibling pods), one multi-row UNNEST INSERT ... ON CONFLICT DO NOTHING RETURNING under the existing (community, endpoint_hash, event_id) dedup key, and one set-wise duplicate lookup so per-request outcomes (Enqueued / Duplicate / InactiveLease) stay exact. enqueue_wake becomes a batch-of-one wrapper — one code path, same shape as the claim API. The matcher goes pure: match_job evaluates leases with no DB access and returns the WakeRequests a job owes; process_match_batch flushes all of them in one enqueue_wakes call. A failed flush sends only the contributing jobs back for an idempotent rematch (the dedup key absorbs wakes that did commit); wake-less jobs still complete. Also honors TEST_DATABASE_URL in test_usage_metrics_lock_has_single_owner_and_releases_on_drop, which hardcoded the shared dev DB and failed whenever a live local relay held the usage-metrics advisory lock. Tests: setwise_enqueue_maps_outcomes_per_request covers fresh insert, duplicate-of-committed, same-batch dedup collision, stale generation, unknown lease, and a second live lease in one batch. buzz-db PG suite 202/202 green on a fresh isolated 0001-0024 database; full-gate rerun and scenario B before/after numbers to follow on this SHA. 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 20, 2026
…vider * origin/main: (111 commits) fix: skip membership lookup for open relays (#2107) fix(desktop): make invite QR downloadable (#2168) Archive managed agents when deleted (#2135) perf(relay): cache Git pack hydration (#2169) chore(deps): update rust crate rustls to v0.23.42 (#2151) chore(deps): update dependency @tanstack/react-virtual to v3.14.6 (#2153) Add Agent Config Core: harness capability model behind agent config surfaces (#2158) chore(deps): update all non-major dependencies (#2152) chore(deps): update rust crate getrandom to v0.4.3 (#2150) fix(relay): bound and observe Git read operations (#2167) fix(desktop): derive default clone URL for relay-hosted repos (#2166) fix(desktop): mask composer rounded corners (#2165) feat(desktop): add PR merge conflict recovery (#2164) fix(desktop): mask scrolled content behind channel and thread composers (#2163) feat(desktop): add inline PR diff comments (#2162) feat(desktop): add commit-scoped PR review decisions (#2161) fix(desktop): batch project work item queries (#2160) feat(desktop): make missing project checkouts actionable (#2159) Fix harness default model states in onboarding (#2156) relay: gate push enqueue on live leases; batch matcher pipeline (T1b/T1a-repair/T2b) (#2145) ...
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.
Part of the write-amplification work (with #2125 T1a TTL-skip and #2126 T2a audit batching). Three coupled fixes to the push pipeline's per-message DB cost:
T1b — push gate (migration 0023)
Skip the
push_match_queueenqueue for communities with no active, endpoint-enabled, unexpired push lease. Today every durable message in a lease-less community (most of them) pays the full matcher cost — enqueue + claim + lease scan + delete — to conclude "notify no one".Lost-wake correctness: a naive
EXISTScheck could read "no lease" while a lease activation commits concurrently, silently dropping that user's first wake. Closed with a per-community advisory lock held to txn end:accept_lease_event,replace_lease) take it EXCLUSIVE.The conflict forces a total order: either the event's check sees the committed lease, or the activation strictly follows the event's commit — in which case no wake was owed. A backfill on activation re-enqueues recent events as product-recovery coverage (not part of the proof).
T1a repair — TTL trigger shared lock (migration 0024)
The 0022 trigger takes
FOR UPDATEon the channel row before testingttl_seconds, so every durable message in a permanent channel serializes on that tuple at commit time (deferred trigger), each holding the lock across its WAL flush. Observed live at 200 QPS: commit latency 0.07ms → ~15ms, non-CPU DB load ~9/vCPU with CPU under 45%. This is the current throughput ceiling for a hot channel.Repair keeps 0022's stale-prefetch proof but moves synchronization to a per-channel advisory lock: event inserts take it SHARED (permanent-channel commits proceed concurrently, no tuple lock, no row write); a TTL transition (
update_channel) takes the same key EXCLUSIVE before its UPDATE. Either ordering closes the stale-NULL hole. Ephemeral channels still pay their conditional row update — only they need it.T2b — batched matcher pipeline
The relay push matcher claimed one job per transaction (claim + event load + lease scan + membership check + complete, per job) and polled every 250ms forever when idle. Now:
claim_id) shared by the batch.3173861ed): Max's red-team ofcf018e1bdfound every successful (event, lease) match still calledenqueue_push_wakeper pair — its own transaction + leaseFOR UPDATE, keeping live-lease workload at O(events × matching leases) commits. Fixed: matcher evaluation is now pure (match_job, no DB access); all wake requests for a claimed batch flush through onepush::enqueue_wakestransaction — a single orderedFOR UPDATElease revalidation over the distinct (author, installation) set (ORDER BY pins lock order againstreplace_leaseand sibling pods), one multi-rowUNNESTinsert under the existing (community, endpoint_hash, event_id) dedup key, and index-aligned per-request outcomes (Enqueued / Duplicate / InactiveLease).enqueue_wakeis a batch-of-one wrapper — one code path. A failed flush sends only contributing jobs back for an idempotent rematch. With this, the constant-statements-per-batch claim holds through the whole pipeline.Tests
New PG tests: batch fence contract; single-community batch claim; T1a barrier (8 concurrent permanent-channel commits held at a barrier — zero
channelstuple locks, all commits proceed, row untouched); T1b forced lost-wake ordering + activation backfill; exhausted-job sweep;setwise_enqueue_maps_outcomes_per_request(fresh insert, duplicate-of-committed, same-batch dedup collision, stale generation, unknown lease, second live lease in one batch). Migration inventory pins for 0023/0024, including a comment-stripped assert that noFOR UPDATEre-enters the executable trigger body.Full
buzz-db+buzz-relaysuites green at3173861edon a 0001–0024 database (including all--ignoredPG tests): buzz-db 202/202, buzz-relay 715/716. Lone red:api::mesh_demo::demo_join_forwarded_arm_round_trips_echounder the full parallel-p buzz-relayrun — reproduced identically onmain(c1bca1b), passes in isolation on both; pre-existing, unrelated.Independent verification (Max, at
3173861ed)Fresh isolated PG17 + newly built release relay, new identity: focused patch tests green; two channels driven concurrently at ~300 QPS × 10 s over 60 WebSocket connections — 2,728/2,728 accepted, exact PG reconciliation, 0 duplicate event IDs; after drain: 0 match-queue rows, 0 wake-outbox rows, 0 eligible leases, 0 channel tuple locks; relay log free of errors/panics/deadlocks. Round-1 blocker (per-match enqueue) confirmed structurally closed; no remaining merge blocker.
Second independent E2E round (Max, same tip, separate DB/relay/identity): permanent + ephemeral (
ttl=3600) + TTL-transitioned (permanent→TTL→permanent) channels; threads and tags reconciled exactly in raw event rows; 3 channels at 200 QPS × 8 s over 72 connections — 2,055/2,055 accepted, exact per-channel PG reconciliation, 0 duplicate IDs, all no-lease invariants held after drain, relay log clean. Across both rounds: 4,783/4,783 accepted messages present as durable PG rows — zero lost.Benchmarks (at
3173861ed)Wamp harness (same rig as #2125/#2126: isolated PG + mock push gateway, 180 s steps, idle-control-subtracted net commits/msg, quiesce-complete each step, wake outbox drained to delivered). All steps 0 rejected; q100 accepted 18,004/18,004 in both scenarios.
Scenario B — live push lease (the workload T2b exists for):
The baseline was flat in qps (per-message cost never amortized). The after-curve improves with load — the batching signature: constant per-batch statements spread over bigger claimed batches. At 100 qps, scenario B (10.50) is cheaper than the old scenario A baseline (~11.0): the ~6.5 commits/msg the live lease used to add is nearly amortized away.
Scenario A — hot permanent channel, zero leases (T1b push-gate + T1a repair):
Roughly half the per-message commit cost gone: with no live lease, the push-gate skips the per-message push transactions outright instead of batching them. Latency held healthy throughout (scenario A q100: p50 4.3 ms / p99 6.1 ms; scenario B q100: p50 4.0 ms / p99 7.4 ms).
Raw artifacts:
/tmp/wamp-t2b-bench/{A,B}/(per-step bench/summary/latency/statement captures).Lock-domain note:
buzz_push_gate:(0023),buzz_channel_ttl:(0024), and the audit lock (buzz_audit:, #2126) are distinct key domains; the deferred TTL lock is acquired at COMMIT, after any push-gate shared lock taken during insert, and no path takes two domains exclusively — no cross-domain deadlock ordering.