Designing a Distributed Session Store: Why Sticky Sessions Break Under Load josedacruz, November 6, 2026November 6, 2026 TL;DR: Sticky sessions keep a user’s login and cart data glued to whichever server first handled their request. It’s simple to set up, but it quietly breaks the moment you restart a server, deploy new code, or add more servers to handle traffic. Moving session data out of the app server and into a shared store like Redis fixes this — every server becomes interchangeable, which is what actually makes scaling and painless deploys possible. For small apps with steady, low traffic, though, sticky sessions are still the simpler and cheaper choice. Picture a team building an online store. Early on, when a customer logs in, the app just keeps their session — their identity, their cart, whether they’re logged in — in a variable in memory on whichever server handled the request. It works great in a demo. Then Black Friday hits, the team adds three more servers to handle the load, and suddenly customers are getting logged out mid-checkout for no obvious reason. That mystery is almost always the same root cause: session state that lives on one specific machine, in a world where requests no longer reliably land on that machine. How it’s often done A “session,” in web terms, is just a small bundle of facts about one visitor: are they logged in, what’s in their cart, what step of a multi-page form they’re on. Browsers don’t remember any of this on their own between requests — each HTTP request is stateless, meaning the server treats it as if it’s never seen that visitor before, unless something ties the requests together. That “something” is usually a session ID, stored in a cookie in the visitor’s browser. The simplest way to make this work is to store the actual session data — the cart contents, the login flag — directly in the memory of the server that first created it. That’s fast and requires zero extra infrastructure. The catch is that the next request from that same visitor has to land on that same server, or the data won’t be there. So teams add “session affinity,” better known as sticky sessions. The load balancer — the piece of infrastructure that spreads incoming traffic across multiple servers — is configured to remember which server handled a visitor’s first request (usually by reading a cookie) and route all of that visitor’s future requests to that exact same server. The user is pinned to Server A. The moment Server A restarts, their in-memory session is gone — even though nothing about the user’s own request changed. Where it falls apart Sticky sessions work right up until something changes about the servers themselves, which in any growing system is constantly. The first crack shows up during deploys. Every time the team ships new code, servers restart. Any session sitting in that server’s memory disappears with it. Users who happened to be mid-checkout get silently logged out, and there’s no error message that explains why — from their side, the app just “forgot” them. The second crack shows up under scaling. If Server A is handling more logged-in, cart-heavy users than Server B just because of how the load balancer happened to assign them early on, you can’t fix that imbalance by adding more servers — the sticky users are stuck where they are. Autoscaling, where the infrastructure automatically adds or removes servers based on traffic, barely helps either: a freshly added server starts empty, and an old server can’t be safely removed without kicking off everyone currently glued to it. The third crack is a bit more subtle: it makes your servers not interchangeable. In a healthy setup, any server should be able to handle any request, which is what lets you replace, resize, or restart servers without users noticing. Sticky sessions quietly violate that assumption, and the cost of that shows up later, usually during an incident, when someone is trying to route around a broken server and can’t do it cleanly because of who’s “stuck” there. A better way: externalize the session The fix is to stop storing session data on the app server at all. Instead, the app server does its work and then writes the session data to a shared store that every server can read from and write to — commonly Redis or Memcached, both of which are fast, in-memory data stores built for exactly this kind of short-lived lookup. With this change, a server holds nothing between requests. It reads the session ID from the visitor’s cookie, looks up that session in the shared store, does its job, and moves on. This is usually called a “stateless” app server, meaning it doesn’t privately remember anything about who it talked to last — all the state lives somewhere external and shared. Any server can now handle any request from any visitor, in any order, because the data those requests depend on isn’t locked to one machine anymore. Every app server is now interchangeable. The load balancer can send a request anywhere, because the session lives in the shared store, not on any one server. This one change unlocks a lot at once. Deploys stop logging people out, because restarting a server no longer touches anyone’s session — it was never stored there. Autoscaling works properly, because a brand-new server is just as capable as an old one; it just reads from the same shared store. Load balancing gets simpler too, since the load balancer can use plain round-robin routing (each request just goes to the next server in line) instead of tracking who’s pinned where. You do give up a couple of things. There’s now a network hop to the session store on every request that needs session data, which adds a small amount of latency — usually a millisecond or two if the store is close by, which is a fair trade for most apps. And the session store itself becomes something you need to keep available, typically by running it in a small cluster with replication (keeping copies of the data on more than one machine, so losing one doesn’t lose the data). When NOT to switch None of this means sticky sessions are simply “wrong.” For a small internal tool, an early-stage product with one or two servers, or anything where losing a session on deploy is a minor annoyance rather than a real cost, sticky sessions are still the right call. They need no extra infrastructure, no additional network hop, and no new service to monitor and keep available. Adding a Redis cluster to hold sessions for an app that will realistically never run more than one server is pure overhead — it’s one more thing that can break, for a scaling problem you don’t actually have yet. A good rule of thumb: if losing a session on deploy would genuinely upset real users, or if you’re actively autoscaling servers up and down based on traffic, externalize the session. If you’re not there yet, don’t build the shared store in advance — build it when the pain from sticky sessions actually shows up. It’s also worth knowing there’s a third option that sidesteps the shared store entirely: put the session data in the cookie itself, cryptographically signed so the client can’t tamper with it (commonly done with JWTs, a token format that packs data and a signature together). This avoids both the server-memory problem and the extra network hop, at the cost of making sessions harder to instantly revoke and keeping you limited to fairly small amounts of data. None of these options is free. Sticky sessions trade scalability for simplicity; a shared store trades a bit of complexity for servers that are fully interchangeable. The takeaway Sticky sessions aren’t a bug in your architecture — they’re a reasonable starting point that stops being reasonable once you need your servers to be disposable and interchangeable, which is exactly what scaling and painless deploys require. The moment “add another server” or “restart during a deploy” starts causing real user pain, that’s the signal to move session state out of the server’s memory and into a shared store. Until then, the simpler option is still the better one. Related architecture distributed systemsload balancingscalabilitysession managementsystem design