Skip to content

Make relay readiness process-local - #7341

Draft
TheSentinel454 wants to merge 3 commits into
mainfrom
tornquist/relay-readiness-overload
Draft

Make relay readiness process-local#7341
TheSentinel454 wants to merge 3 commits into
mainfrom
tornquist/relay-readiness-overload

Conversation

@TheSentinel454

Copy link
Copy Markdown
Contributor

Summary

Make Kubernetes readiness depend only on the relay process lifecycle, so a shared Postgres or Redis slowdown cannot withdraw every pod at once. Keep dependency checks on /_status, preserve WebSocket connections when the community-active database check is inconclusive, and add bounded rollout metrics for both paths.

This simplifies readiness from a serialized shared-dependency coordinator to a two-state local decision: ready or shutting down. Dependency health remains observable without controlling load-balancer membership.

Related issue

None found. This addresses the elevated relay HTTP 500 and reconnect incident investigated on 2026-09-03.

Testing

Verified at 25bc2c29ddd2027fc38a0b0b81d0c229b31b2550 on Blox with:

  • cargo fmt --all -- --check
  • cargo clippy -p buzz-relay --all-targets -- -D warnings
  • cargo test -p buzz-relay --lib readiness:: — 8 passed
  • cargo test -p buzz-relay --lib router::tests — 13 passed
  • cargo test -p buzz-relay --lib state::tests — 24 passed, 1 ignored
  • cargo test -p buzz-relay --test boot_lifecycle — 9 passed

The complete cargo test -p buzz-relay --lib run was not green: telemetry::tests::trace_context_lookup_does_not_enable_callsites failed with 1040 passed, 1 failed, and 89 ignored. Baseline characterization was inconsistent: the telemetry test passed across six baseline runs while the existing mesh-demo timeout failed intermittently. This PR does not change telemetry or mesh-demo code.

Generated with Claude Code

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is f038cbbb0d4092a72ffd93f17916f84d2b39bb43...69cf2c2f5887d86e4787fbbaffe20cb590db6442.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 69cf2c2f5887d86e4787fbbaffe20cb590db6442 to authorize a new review.
Any previous review applies only to its recorded range.

A reconnect burst exhausted the per-pod writer pools and two feedback
loops turned that into a total outage.

Readiness evaluated shared Postgres, Redis, and deletion-catalog health,
so every replica went NotReady together and the burst had nowhere to
land. The probe was also part of the load: the deletion-catalog check
acquires the writer pool, so each pod spent writer connections against
the exhausted pool every five seconds while failing. /_readiness now
answers from local process lifecycle only — shutting_down is 503,
anything else is 200 — and the dependency evaluation moves to /_status
on the same private health listener, under a `dependencies` object
carrying the fields the readiness body used to return. No startup state
is added: the health listener binds only after the database,
migrations, Redis, and pub/sub are up, so a process that can answer has
booted.

run_registered_community_connection collapsed Ok(false) and Err into
"not active", so a writer-pool timeout in is_community_active read as
confirmed archival and dropped the socket, which reconnected and
re-checked. Only a confirmed Ok(false) cancels now; a lookup failure
admits the socket with a structured warning and defers to the periodic
revalidate_live_communities backstop. Writes are unaffected and remain
fail-closed on their own per-event fence.

Telemetry keeps its existing names: buzz_readiness_checks_total narrows
to {ready, shutting_down}, the dependency families are now sampled by
/_status, dependency gauges are dropped, and one new bounded counter,
buzz_community_admission_checks_total{outcome}, counts the admission
decision. The per-pod raw-series ceiling drops from 99 to 86.

This deletes the readiness publication machinery — the mutex, probe
generations, ProbeTicket/ProbeStart, finish_probe,
finish_public_evaluation, and a second shutdown flag duplicating
AppState::shutting_down. All of it existed to order concurrent async
dependency evaluations against shutdown. Readiness is now a single
atomic load, so the one ordering guarantee still worth keeping — a
racing shutdown must win, and never leave a draining pod advertising a
ready gauge — is a post-write re-read in record_readiness_probe rather
than a generation-fenced mutex.

Co-authored-by: Claude Code <noreply@anthropic.com>

Redis had no startup gate at all. `deadpool_redis` pools dial lazily and
PubSubManager::new only allocates channels, so "Redis pub/sub connected"
was logged against a dead port and boot ran to completion. With readiness
now answering from local lifecycle alone, such a pod bound its health
listener and advertised ready for the rest of its life. state::
verify_redis_command_path acquires one connection from the command pool
and issues PING before AppState is built, and therefore before the health
listener binds, because binding is the one-way latch that makes a pod
routable. No startup_ready flag is added for the same reason. Post-start
Redis failures are unchanged: they are dependency failures and never move
readiness. Postgres startup connection behavior is untouched.

Signed-off-by: tornquist <tornquist@squareup.com>
@TheSentinel454
TheSentinel454 force-pushed the tornquist/relay-readiness-overload branch from 25bc2c2 to a8e2c48 Compare September 4, 2026 20:04
TheSentinel454 and others added 2 commits September 4, 2026 20:28
Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: Codex <noreply@openai.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