Notes
The bug
Here's the classic bug. You log in, and server one stores your session in its own memory. Your next request goes through the load balancer to server two, which has never heard of you. You're logged out. Worse, when server one restarts, every session on it disappears.
- server 1 stores the session
- in its own memory
- the load balancer sends the
- next request to server 2
- → "please log in again"
- and if server 1 restarts,
- every session on it is gone
Definitions
A stateful server remembers things between requests: sessions, carts, open connections. A stateless server keeps nothing between requests: each request carries, or points to, everything needed, so any server can handle it. Stateless servers are interchangeable. You can add, remove or restart them freely.
- stateful: the server remembers
- things between requests
- (sessions, carts, open sockets)
- stateless: every request carries
- or points to what is needed;
- any server can handle it
- stateless servers are
- interchangeable: add, remove,
- restart freely
Sticky sessions
Option one: sticky sessions. The load balancer always sends a given user to the same server, using a cookie. No code changes, but if that server dies the sessions die with it, load gets uneven, and every deploy logs people out. It's a quick fix, not a design.
- load balancer pins a user to
- one server (cookie or IP hash)
- + no code changes
- − server dies → sessions lost
- − uneven load
- − deploys log people out
- a quick fix, not a design
Session store
Option two: a shared session store. The cookie holds only a random session id, and the session data lives in Redis with an expiry. Any server can look it up, servers stay stateless, and logging someone out is just deleting the key. The cost is one fast extra hop, and Redis itself must be highly available.
cookie holds only a session id
session data lives in Redis:
session:9f2c → {user: Asha,
cart: [7, 12]}
TTL 30 min
+ any server works
+ revoke by deleting the key
− one more hop (~0.5 ms)
− Redis must be highly available Tokens
Option three: signed tokens, like JSON web tokens. The token itself holds the user id and an expiry, plus a signature. Any server verifies the signature without a lookup. The catch: you can't easily revoke a token before it expires, so keep access tokens short-lived and keep refresh tokens in a store.
{ sub: "user_42", role: "user",
exp: 15 minutes }
+ signature
server checks the signature,
no lookup needed
− cannot easily revoke before
expiry → keep it short-lived,
refresh tokens in a store Where state lives
State doesn't disappear; it moves. Web and API servers and background workers become stateless. Databases, caches, queues and WebSocket gateways are stateful by nature. The design goal is to push state down into a few specialised systems that are built for replication and failover.
- stateless: web/API servers,
- workers
- stateful (by design):
- databases, caches, queues
- WebSocket gateways
- (they hold live connections)
- push state down to a few
- specialised, replicated systems
Payoff
The payoff is huge. Stateless servers can autoscale in seconds, be replaced one by one during deploys without logging anyone out, and crash without losing anything. The stateful parts scale differently, through replication and sharding, which is what the database episodes cover.
- autoscaling: add servers in
- seconds, any can serve
- deploys: replace servers one
- by one, nobody logged out
- failure: a crashed server
- loses nothing
- spot / cheap instances usable
- stateful systems scale
- with replication + sharding
Say it like this
In an interview, say it in one line: the application servers are stateless, so any server can take any request and we autoscale them freely. Sessions live in Redis behind a session cookie, or in short-lived tokens. All durable state lives in the database, cache and queue.
- "App servers are stateless"
- sessions in Redis (session id
- cookie) or short-lived JWTs
- any server behind the LB
- autoscale on CPU / RPS
- state in DB, cache, queue
- WebSocket gateways: registry
- (episode 9)