The actor key rides in the JSON body, and no load balancer will parse a
body to route. The routing token is what lets the edge see which actor a request is for.
Without it, locality decays as 1/N — measured 1.00, 0.50, 0.12, 0.02, 0.01 for N = 1, 2, 10,
50, 100 — and a cross-host hop costs about 20× a local dispatch.
POST {base}/r/{token}/{Type}%23{method}x-sigx-actor-route: {token}
The token is a stable per-actor hint, present in both carriers with the same value. It is
a middle segment deliberately: the symbol is decoded as the last path segment, so the
token slots in ahead of it and the endpoint neither parses nor validates it. A stale, wrong or
absent token costs a network hop — never a wrong answer.
Two carriers because neither alone is enough: a path segment cannot be silently stripped by a
service mesh and a mangled one 404s loudly, but Envoy's route.hash_policy has no
path-substring option at all.
The default hash is log hygiene, not privacy. An unkeyed hash of an email is one
dictionary lookup from plaintext at any width. What it buys is keys staying out of access
logs, proxy traces, referrers and screenshots. Real unlinkability needs a keyed HMAC with a
secret the client holds — which a browser cannot hold.
The middle row is an anti-pattern, not a middle ground: the edge's hash and the cluster's
rendezvous hash are different functions over different sets, so they disagree on most keys and
guarantee a hop for every actor.
$live carries no routing token, nor does any $-reserved symbol — one held-open
response fans out to many actors. Live subscriptions still dispatch
correctly through placement, but they keep paying the hop. "~100% locality" does not cover
live components.
A migrated actor does not follow the LB.preferLocalPolicy() applies to new
activations, and the directory keeps a live actor where it is. After a scale-out, already-hot
actors stay misrouted until they deactivate.
By default a call that lands on the wrong host is proxied: one client round trip, one
internal hop, every time. The alternative is to tell the client where to go:
onMiss goes on the mount, not the host, so one cluster can serve a browser origin that
proxies and a service origin that redirects.
follow is not optional in practice. On its own a redirect is two client round trips
against one trip plus an internal hop — strictly worse than proxying. The win exists only
because the client remembers: 2 requests once, then 1 forever. A learned endpoint is
dropped as soon as it proves wrong, and falls back to the configured endpoint.
sequenceDiagram
participant Cl as Client
participant H1 as Host 1
participant H2 as Host 2
Note over H2: owns the actor
Cl->>H1: call
H1->>H2: forward over the host transport
H2-->>H1: result
H1-->>Cl: result
Note over Cl,H2: every later call repeats the hop
onMiss proxy, the default — one client trip plus an internal hop, every time
sequenceDiagram
participant Cl as Client
participant H1 as Host 1
participant H2 as Host 2
Note over H2: owns the actor
Cl->>H1: call
H1-->>Cl: 421 with owner.endpoint
Cl->>H2: retry
H2-->>Cl: result
Note over Cl: endpoint learned
Cl->>H2: every later call, one trip
H2-->>Cl: result
onMiss redirect with client follow — 2 requests once, then 1
publicAddress is required and deliberately never guessed.advertise is the internal
peer origin, typically a pod IP. Redirecting a client there hangs, and publishing it hands
internal topology to anyone who can reach the public mount. Without publicAddress the mount
proxies anyway and dev-warns once. The 421 body carries owner.endpoint, never
address.
'proxy' is the browser answer. A cross-origin redirect preflights and is refused by the
default same-origin policy; redirecting a browser needs an explicit origin allowlist plus
CORS on the owner's mount. For browsers the single-origin shape above is better.
Why 421 is safe to auto-retry: the endpoint resolves the owner before dispatching, so a
redirect has provably run no application code. That matters because 421 is a status user
agents may retry on their own, and actor calls are not idempotent.
Limits: $live is never redirected; a redirected watch is bound to its owner only until the
actor migrates, then silently proxied again; and redirect chains are bounded at both ends,
default 2 hops.
The locates / locateRemote counters give you the miss rate your edge is producing — see
Cluster stats.