Skip to content

feat(hotblocks): name the serving instance on every response#99

Merged
mo4islona merged 1 commit into
masterfrom
worktree-hotblocks-instance-header
Jul 16, 2026
Merged

feat(hotblocks): name the serving instance on every response#99
mo4islona merged 1 commit into
masterfrom
worktree-hotblocks-instance-header

Conversation

@mo4islona

@mo4islona mo4islona commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Why

hotblocks-db is a StatefulSet behind a ClusterIP Service. The portal connects to the Service's virtual IP and kube-proxy DNATs to a pod in the kernel, so getpeername() — and therefore anything reqwest can report — returns the ClusterIP. The portal cannot tell which replica answered.

This adds x-internal-hotblocks-instance: <pod> to every response, so it can.

Merging is safe. Deploying is gated.

sqd-portal strips every x-internal-* header before responding (subsquid/sqd-portal#133, released in 0.11.8) and logs this one on a 5xx. But that strip is released, not deployed, and the deploy is what matters:

Only 0.11.8 carries the strip (git tag --contains 9d08fbav0.11.8 alone); below it forward_response copies every upstream header verbatim, with no filter at all. Every hotblocks namespace is fronted by at least one portal that does not strip, and portal.sqd.dev is one of them.

Implementation

One line in the existing middleware, which wraps every route — .fallback() is applied before .layer(), and axum::serve puts nothing outside it, so 404s are covered too. No new layer, no tower-http feature.

HOSTNAME is read once via LazyLock (Kubernetes sets it to the pod name). Resolution lives in a pure instance_header() so the mapping is testable without racing on process-global env. Absent, empty, and unrepresentable names all collapse to unknown: the header is always present and never empty, so the portal never has to distinguish "absent" from "unset" — and an empty value would have reintroduced exactly that ambiguity.

Test

tests/instance_header.rs drives the real binary through the harness and asserts the header is present. Verified it catches the regression — with the insert removed it fails with every response must name its instance.

That test can't pin the HOSTNAME → header mapping: the harness inherits the parent env, and HOSTNAME is unexported on dev machines and on bare CI runners, so it only ever exercises the fallback. instance_header_tests pins the mapping directly instead — deleting the empty-guard fails them with left: "" / right: "unknown".

Scope — worth being clear

This names the replica only for requests that got a response. It does not help the case that motivated it: a request that stalls before headers has no response to carry any header, and that is exactly the /head timeout a tenant hit on 2026-07-16. For a wedged replica, hotblocks' own per-pod metrics remain the only signal. This earns its place for 5xx attribution.

🤖 Generated with Claude Code

A Service load-balances across replicas behind one ClusterIP, and the kernel
DNATs transparently, so the portal sees only the virtual IP and cannot tell which
pod answered — nor can it ask a request that never returned. Computed once:
HOSTNAME is fixed for the process's life.

The portal strips every `x-internal-*` header (sqd-portal#133), released in
0.11.8. That release is the deploy gate, not a property of this branch: portals
below it forward upstream headers verbatim, and mainnet-public still runs 0.11.3
behind an nginx that hides only Date/Server/X-Accel-*. Rolling this out to
network-hotblocks-mainnet ahead of those portals would put pod names in front of
public clients.

Resolution is a pure function so the HOSTNAME mapping is testable without an env
race: empty and unrepresentable names collapse to `unknown` alongside absent,
since an empty value would reintroduce the ambiguity the fallback exists to kill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mo4islona
mo4islona force-pushed the worktree-hotblocks-instance-header branch from e7b9a52 to 2fe819a Compare July 16, 2026 16:15
@mo4islona
mo4islona merged commit 4e71c27 into master Jul 16, 2026
3 checks passed
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