Skip to content
Sticky Sessions
step 1/5

Reading — step 1 of 5

Read

~3 min readAdvanced Algorithms

Sticky Sessions

Load balancing wants requests to be interchangeable; application state sometimes disagrees. A multi-part file upload in progress, an in-memory shopping cart, a WebSocket subscription — if the next request lands on a different backend, that state is simply gone. Session stickiness (affinity) pins a client to a backend for the lifetime of its session.

Three mechanisms, in increasing sophistication:

Cookie-based stickiness. On the first response, the LB injects a cookie naming the chosen backend:

Set-Cookie: route=server2; Max-Age=3600

Every later request carries the cookie; the LB reads it and routes straight to server2, skipping the balancing algorithm. This is HAProxy's cookie SRV insert indirect nocache, nginx's sticky cookie, and AWS ALB's AWSALB duration-based cookie. It's precise (per-browser, not per-network) and survives pool changes.

Source-IP hashing. hash(client_ip) % N. No cookie needed, works for plain TCP — but every user behind one corporate NAT or mobile carrier gateway shares an IP and lands on the same backend, and the % N remaps almost everyone whenever the pool changes.

Consistent-hash on a session ID. Hash a session token onto the ring from last lesson: stickiness without per-client LB state, and only K/N sessions move when backends change.

The part everyone gets wrong: failure

Stickiness is a preference, not a guarantee. When the pinned backend goes down, the request must fall back to normal selection — pick a fresh backend, issue a new cookie, and lose the session rather than the request. An LB that returns 502 because the cookie names a dead server has turned an availability feature into an outage amplifier. Same rule for a cookie naming a backend that no longer exists (config changed, client kept the old cookie): treat it as no cookie at all.

The strategic caveats: stickiness concentrates load (one heavy user pins to one backend and the algorithm can't spread them), and it weakens redundancy (losing a backend loses its sessions). The grown-up fix is to externalize session state to Redis or a database so backends are stateless — sticky routing is what you do when you can't.

Your exercise

Implement request() in the starter's StickyRouter. Commands: POOL <b1> <b2> ..., DOWN <b> / UP <b>, and REQUEST <client_id> [cookie] where the cookie is a backend name (or absent, or -). Logic:

  1. If the cookie names an UP backend → print <backend> sticky.
  2. Otherwise round-robin among UP backends → print <backend> new.
  3. No UP backends at all → print NONE.

The round-robin is the starter's _rr(): one global counter that indexes ups[rr_idx % len(ups)] and increments on every new assignment.

Grader traps, from the real tests:

  • Sticky hits must NOT advance the round-robin counter. Sequence on POOL s1 s2: REQUEST c1s1 new, REQUEST c2s2 new, REQUEST c3 s2s2 sticky, then REQUEST c4s1 new. If the sticky hit consumed a rotation slot, c4 would get s2 and fail.
  • Unknown cookies fall through. REQUEST u zz on pool a b prints a newzz isn't a backend, so it's just a new client.
  • Down means fall back, then re-pin. After DOWN a, REQUEST u1 a prints b new (not an error); after UP a, REQUEST u1 a prints a sticky again.
  • Everything down: REQUEST c2 x with x and y both DOWN prints NONE — even though the cookie names a real backend.

Output format is exact: backend name, one space, sticky or new.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…