Repository navigation
feat(divide): add optional sticky session affinity composed with load balancing (#8037) - #8098
Conversation
Aias00
left a comment
There was a problem hiding this comment.
Approved. The sticky session affinity feature is cleanly designed and securely implemented: the binding travels in a gateway-issued, HMAC-SHA256-signed cookie (base64url payload.signature of ruleId|hashKey|expiry), verification walks all configured secrets so keys can be rotated, and isActive() fails safe when secrets/cookieName are blank or maxAge is non-positive. A binding is only honored when its target is still a member of the already-admitted (canary/health/breaker) candidate list, and STRICT mode rejects (503) only when a valid binding exists but its target is ineligible - a request without a binding is a new session and is allocated normally. The P2C inflight accounting is correctly paired on a binding hit, and the cookie is written after target selection but before response commit so a downstream retry never rewrites the binding. Equals/hashCode/toString on DivideRuleHandle were updated for the new sticky field. Nice work.
Implements the first delivery phase of #8037: optional HTTP session affinity for Divide, composed with — never replacing — the configured load-balancing algorithm.
Summary
For a new session the base algorithm selects the target. For an existing binding, the affinity layer reuses the bound target — but only while it is still a member of the candidate set that has already passed canary partitioning and circuit-breaker admission, so a binding can never override namespace boundaries, label partitions, health or breaker admission.
Modifications
StickySessionConfig(rule handle, nested like the canary config):enabled,cookieName,maxAgeSeconds,secretsandonUnavailable(REBINDdefault /STRICT).HttpOnlycookie whose value isbase64url(ruleId | upstream identity | expiry)plus anHMAC-SHA256signature.DividePluginResolution:specify-domainuses, so all attempt accounting — least-active inflight, P2C lifecycle, breaker keys — applies unchanged).REBINDselects with the base algorithm and rewrites the cookie.STRICTrejects the request with503instead of silently moving the session.specify-domainrequest takes precedence over affinity and is never bound. Disabled rules keep the plain load-balancing behavior: no cookie, no allocation.Verifying this change
New session writes a signed cookie (
HttpOnly/Path/Max-Age/SameSite) that resolves back to the selected target.A valid binding pins two consecutive requests to the bound target under round-robin.
A bound target that is removed, or blocked by the circuit breaker, is not honored:
REBINDre-selects inside the remaining candidates and rewrites the cookie.STRICTrejects with503when the bound target is unavailable; tampered, expired and foreign-rule cookies are treated as a miss; a cookie signed with a previous key still verifies.specify-domainoverrides affinity; default-off rules keep plain behavior with noSet-Cookie; least-active accounting returns to its baseline on a binding hit.STRICTdoes not affect cookie-less new sessions: they are still allocated by the base algorithm; broken configurations (blank secrets, non-positive max age) behave as disabled.A P2C binding hit reserves the attempt and refreshes the EWMA timestamp like a P2C selection; the accounting returns to baseline on completion.
The binding key is the upstream identity: two upstreams sharing a URL but differing in group do not absorb each other's binding; HTTPS requests get a
Securecookie.All related tests and checkstyle pass:
Out of scope (later phases per the issue)
Redis/distributed binding stores, deterministic-key distribution benchmarks, admin-side validation UI, and protocols other than HTTP.