Sticky sessions vs. a shared session cache — how do you handle user session state behind a load balancer?
Pin a user to one server and you inherit its uptime problems; move sessions to a shared cache and you trade a bit of latency for real horizontal scaling.
LEARN BY DOINGPut your intuition to work3 steps
Sticky sessions vs. a shared session cache — how do you handle user session state behind a load balancer?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#The Problem Only Shows Up With More Than One Server
A single backend process can keep session state in memory — a plain hash map keyed by session ID — and it works fine, because every request from a given client lands on that same process by definition. The moment you put a load balancer in front of more than one instance, that assumption breaks: two consecutive requests from the same browser can land on two different servers, and the second server has never heard of the session the first one created. Login state, shopping cart contents, multi-step form progress — anything held in a server's local memory — appears to vanish at random.
There are two standard fixes, and they trade off in almost opposite directions: keep the client pinned to one server so local memory keeps working (sticky sessions), or stop relying on any one server's memory and put session state somewhere every server can reach (a shared session cache).
#Option 1: Sticky Sessions (Session Affinity)
The load balancer arranges for every request from a given client to land on the same backend instance for the life of the session. It typically does this one of two ways: by inserting its own affinity cookie (e.g., an AWS ALB's AWSALB cookie) that encodes which target the client was routed to, or by hashing something stable about the client — source IP, or an existing application cookie — to a consistent backend. Either way, the application code doesn't have to change at all: req.session.userId = 123 just works, because the session lives in the memory of whichever single process keeps answering that client.
The cost shows up later, not immediately. Load balancing degrades from "spread work evenly" to "spread new sessions evenly, then live with whatever mix each server ends up holding" — a server that happens to pick up several long-lived, chatty sessions runs hotter than its neighbors with no way to rebalance mid-session. Worse, session affinity actively fights two things you want in production: rolling deploys (draining a server that's about to be replaced means either waiting for its sessions to end naturally or forcibly dropping them) and autoscaling down (removing an instance destroys every session currently pinned to it, with no clean way to migrate them first).
#Option 2: A Shared Session Store
Move session state out of process memory entirely and into something every backend instance can read and write — Redis and Memcached are the usual choices, though a relational table works too at smaller scale. The load balancer goes back to pure round-robin or least-connections, because it no longer matters which instance handles which request; every instance fetches the same session by ID from the shared store on every request that needs it.
This costs a network hop per session read/write — typically well under a millisecond to a co-located Redis instance, negligible next to the rest of a request's latency budget. In exchange, sessions survive instance restarts, deploys, and scale-down events cleanly, and the load balancer can distribute load purely on capacity rather than on where old sessions happen to live. The shared store becomes a new dependency, though: if it goes down, every server loses access to session state at once, which is why production deployments run it with replication and failover (Redis Sentinel or a managed cluster) rather than a single instance.
#Choosing Between Them
| Sticky sessions | Shared session store | |
|---|---|---|
| App code changes | None | Read/write session via a client library |
| Load distribution | Uneven over time | Even, independent of session age |
| Failover | Session lost if the pinned server dies | Session survives any single app server dying |
| Rolling deploys | Must drain or drop sessions per server | Any server can pick up any request immediately |
| New dependency | None | The shared store itself (needs its own HA) |
| Extra latency per request | None | One fast network round trip |
A third path worth naming, even though it's not really "session storage" at all: stateless tokens (JWTs) push the session data into a signed token the client holds and resends on every request, so no server-side lookup is needed at all. That trades a storage problem for a revocation problem — you can't cheaply force-expire a single JWT before its exp claim, only work around it (short expiries plus a refresh token, or a denylist that reintroduces the exact shared-state problem you were trying to avoid).
🧠 Check your understanding
0/2 · 0/2 answered1. What is the main operational risk that sticky sessions introduce during a rolling deploy?
2. What is the primary latency cost of moving session state to a shared Redis store instead of local memory?