Module split

Module split 

Source
Expand description

Service-level traffic splitting (DW-040, feature analysis section 5 “Traffic”): canary releases, blue-green switches, and the sticky cookie that pins a session through them.

§The pick

A split service dispatches each request to ONE of its target upstreams by a deterministic weighted pick over the SAME FNV-1a hash the balancer’s consistent-hash ring uses (balance::key_hash — the stability argument for that choice, toolchain-stable hashing, applies identically here):

  • key = the sticky cookie’s VALUE when the service configures crate::config::StickyAffinity and the request carries the cookie — a session lands on the same branch for every request while the weights are unchanged;
  • key = the request id otherwise — per-request distribution whose realized ratios converge on the configured weights statistically (the issue’s acceptance shape: “split ratios verified statistically”).

The slot is hash % total_weight, walked over the targets’ cumulative weights. DISPLACEMENT IS BOUNDED BY AN INVARIANT TOTAL: when a weight change KEEPS the total constant, only the changed share of hash space moves — 95/5 -> 90/10 moves exactly the 5% that became canary, and the blue-green flip 100/0 -> 0/100 (total still 100) moves exactly the stable side. A change that ALTERS the total changes the modulus and therefore reshuffles EVERY key — ramp a canary by RE-BALANCING the pair (95/5 -> 90/10), never by growing the canary alone (95/5 -> 95/10 reshuffles all sessions). The pick is STATELESS — no counters, no locks, no per-service runtime state — which is what makes the blue-green switch instant: a re-publish rebuilds the compiled split, and the very next request dispatches by the new generation.

§Stickiness is layered

The cookie guarantees BRANCH affinity (which upstream). Endpoint affinity within a branch comes from the branch upstream’s own balancer: when it runs ip_hash, the sticky value becomes the ring key (the proxy passes it as the dispatch hash key), so the session pins one endpoint through the same ketama machinery that pins client IPs. With other balancers the branch pick is the affinity guarantee and the endpoint is free to float — documented, not hidden: ring-based endpoint pinning and round-robin spreading are different tools and the operator picks per branch.

The cookie value is an opaque affinity handle generated by the gateway (time + a process counter, hex): NOT a secret, carrying no identity, never trusted as one. Reusing a presented cookie is safe precisely because its only power is choosing among upstreams the operator already configured — a client “forging” one can only pick a branch it could have landed on anyway.

One documented edge: a session whose FIRST request is a response cache HIT is served without a dispatch, so no cookie is minted until its first cache MISS — while hits last there is nothing to pin (no upstream was contacted), and the miss mints exactly as the uncached path would.

Structs§

ServiceSplit
The compiled weighted split of one service (DW-040): built per config generation by the upstream registry, held by the dataplane dispatch path. Immutable once built; the pick is stateless.
SplitEntry
One compiled target of a service split (DW-040).

Functions§

mint_affinity_id
Mint a fresh affinity handle: hex(unix ms) hex(counter) — 24 printable hex characters. Opaque, unique per process, NOT a secret and never treated as one (its only power is choosing among upstreams the operator already configured).
read_cookie
Read one cookie’s value from a request’s Cookie headers (the first match across all header lines wins; Cookie is a semicolon-separated name=value list per RFC 6265). Returns the trimmed value; a malformed pair cannot match a validated cookie name and is skipped silently (the gateway then mints a fresh handle — the safe direction: a bad cookie re-sessions rather than mis-routing).