System designCore10 min

Stateless services & sessions

A web server that remembers its visitors works perfectly until there are two of them. Making servers stateless means moving everything they remember somewhere every copy can reach, so that any server can answer any request, and losing one loses nothing.

Watch it evolve1 / 8
The problem

A five-step quote on one server

A car-insurance site sells quotes through a five-step form. Its one server keeps each visitor's progress in memory. Leo has filled in steps 1 and 2.

80
Start
Stage 2
Stage 3
Final

1 · The problem

A second server forgets what the first one was told

A car-insurance site sells quotes through a five-step form: the car, the drivers, the address, the cover, then the price. It runs on one server, and that server keeps each visitor's progress in memory: who is logged in, and the answers so far. Leo has filled in steps 1 and 2.

Renewal season arrives, so the team adds a second server behind a load balancer, which takes turns sending requests to each. Leo submits step 3:

Leo's browserLoad balancerServer Ahas Leo's steps 1–2Server Bhas never seen Leostep 3steps 1–2 went herestep 3 lands hereLeo's browserLoad balancerServer Ahas Leo's steps 1–2Server Bhas never seen Leostep 3steps 1–2 went herestep 3 lands here

Server B has no memory of Leo, so it asks him to log in again and starts the form over. With two servers taking turns, about half of his requests hit the one that doesn't know him.

The vocabulary:

  • State is anything a server remembers between one request and the next.
  • A session is the state kept about one visitor, found again on each request through a session ID, usually stored in a cookie.
  • A stateful server keeps state that only it holds. A stateless server keeps nothing between requests that another copy would need, so any copy can serve any request.

Stateless doesn't mean the system forgets. The state still exists; it just lives somewhere other than the server's memory.

2 · The quick fix

Sticky sessions hide the state until a server goes away

The quickest fix is to make the load balancer send each visitor back to the same server every time. These are sticky sessions, also called session affinity. On the first request the balancer picks a server and sets a cookie naming it; later requests carrying the cookie go to that server. On Amazon's Application Load Balancer, for example, the cookie lasts anywhere from one second to seven days.

Leo's quote works again, as long as nothing changes. But servers change all the time:

Event

What happens with sticky, in-memory sessions

A server crashes

Its visitors move to a server that has never seen them. With 4 servers, about a quarter of people mid-quote start over.

A deploy restarts every server

Every visitor mid-quote loses their progress once, unless the deploy waits for every session to end.

The fleet scales out

New servers get only new visitors; existing ones stay stuck to the old, busy servers.

The fleet scales in

The removed server's sessions vanish with it.

One visitor sends heavy requests

All of it lands on one server; the balancer can't spread it.

The deploy row is the one teams hit first. A balancer can drain a server before removing it, letting in-flight requests finish (300 seconds by default on Amazon's balancer), but a quote form left open over lunch outlives any reasonable drain. Sticky sessions don't remove the state from the server; they only route around it while nothing changes (Load balancing).

3 · The fix

Move the session to a store every server can read

The standard fix keeps the session ID in the cookie and moves the session itself to a shared session store, usually an in-memory key-value store such as Redis or Memcached. Every server reads it at the start of a request and writes it back at the end:

Server B has never seen Leo and doesn't need to. The cost is a read and a write to the store on each request, each a network round trip.

The session ID is now the key to someone's account, so it is built with care:

  • Random and meaningless: at least 64 bits from a secure random generator, never a user ID or anything guessable.
  • A protected cookie: marked Secure (HTTPS only), HttpOnly (page scripts can't read it) and SameSite (not sent on most requests started by other sites).
  • A new ID after login, so an ID planted before login is useless afterward.
  • Two expiries: an idle timeout, refreshed on every request (30 minutes here), and an absolute timeout after which the visitor logs in again whatever they are doing.

Sessions are small. 300,000 active sessions at 4 KB each is 300,000 × 4 KB = 1.2 GB: memory on one machine. The real cost is that the store is now on every request's path. If it goes down, every visitor is logged out at once, so it needs replicas and failover like any database (Database replication). And anything a visitor would be upset to lose, like a half-finished quote, is better saved as a draft in the database than kept only in a session that expires.

4 · The alternative

Or hand the state to the client, signed

A second option keeps no session on the server at all. The server writes the session data itself into a signed token, a signed cookie or a JSON Web Token (JWT), and gives it to the browser. On each request, any server holding the signing key checks the signature and trusts the contents, with no lookup. Change one character and the signature no longer matches.

Compared

Session in a shared store

Signed token held by the client

Work per request

One lookup in the store

A signature check, no lookup

What travels

A short ID

All the data, on every request; cookies are limited to about 4 KB

Who can read it

Only the servers

Anyone holding it: signed is not encrypted, unless you also encrypt it

Log out or ban now

Delete the session

Valid until it expires, unless servers check a list of revoked tokens

Best for

Web sessions with logout and changing data

Short-lived access checked by many services

The revocation row decides most cases. If Leo's account is suspended at 10:00 and his token says it is valid until 10:15, every server will accept it until 10:15. The usual answer is short expiry with a way to get a fresh token, or a list of revoked tokens, and that list is shared state again. Keep token contents small and non-sensitive, and their lifetimes short. How login and tokens fit together is the subject of Authentication & authorization.

5 · The catch

Sessions aren't the only state hiding on a server

A server can be stateful without keeping a single session. The quote site has several other habits that break as soon as there is a second copy:

State on the server

What goes wrong

Where it belongs

Leo's uploaded license photo, on local disk

The next request lands elsewhere; a replaced server takes the file with it

Object storage (Object & blob storage)

Car prices cached in memory

Nothing much: a lost copy is reloaded

Keep it, as a cache only (Caching strategies)

The nightly renewal-reminder job, on every server

Ten servers send each customer ten emails

One scheduler, or a lock (Distributed locks)

Rate-limit counters in memory

Each server counts on its own, so the limit becomes ten times looser

A shared counter store (Rate limiting algorithms)

Open WebSocket connections

A connection lives on one server by nature

Accept it; on reconnect, resume from shared state (WebSockets & long polling)

The rule of thumb: a server's memory and disk may hold anything that is safe to lose when the request ends, such as a temporary file or a cache. Anything that must outlive the request goes to a service built to keep it.

6 · The payoff

Once state lives elsewhere, servers become interchangeable

After the move, the quote site looks like this:

Load balancerWeb serversany of them, any requestSession storereplicatedany serverread, writeLoad balancerWeb serversany of them, any requestSession storereplicatedany serverread, write

The servers hold nothing that outlives a request. All of the state sits in stores that are built and replicated to keep it.

That buys almost everything else on the scaling path:

  • Any balancing rule works, including least-busy rules that move a visitor from server to server (Load balancing).
  • Autoscaling can add and remove servers without notice (Autoscaling).
  • Deploys roll through the fleet one server at a time, and nobody loses a quote.
  • A crash costs only the requests in flight, and those can be retried on another server.
  • More servers mean more capacity, until the shared store becomes the bottleneck (Scalability).

That last point is the honest cost. Statelessness doesn't remove the hard part; it concentrates it. The web tier becomes easy to scale because the store and the database now carry all the state, and they are the parts that are hard to scale and must be replicated.

7 · In practice

Put each piece of state where losing it hurts least

Where to keep what:

State

Keep it in

Why

Who is logged in, on a website

A shared session store

Instant logout, small cookie, any server

Access for APIs and service-to-service calls

Short-lived signed tokens

No lookup on every hop

Work the user would hate to lose

The database, as a draft

Survives expiry, restarts and new devices

Files

Object storage

Every server reads the same copy

Per-server memory only

A single server, a prototype, or a WebSocket

Acceptable while there is one copy, or the connection is the state

A checklist:

  • Assume any request can reach any server, and test it: run two copies locally and alternate between them.
  • Use sticky sessions only as a temporary crutch, and never as the only copy of anything that matters.
  • Make session IDs random, cookies protected, and expiries real: idle and absolute.
  • Replicate the session store, because its failure logs everyone out.
  • Keep tokens small and short-lived, and decide up front how to revoke them.
  • Hunt the hidden state: local files, scheduled jobs and in-memory counters.

Check yourself

5 questions

1. The site has two servers taking turns, and each keeps sessions in its own memory. What does Leo experience?
2. Sessions are sticky and kept in memory on 4 servers. One server crashes. What happens?
3. Why should the session ID be a long random value rather than the user's account number?
4. Leo's account is suspended at 10:00. His signed token is valid until 10:15, and servers check only the signature. When do his requests stop being accepted?
5. Ten stateless web servers each run the nightly renewal-reminder job from their own schedule. What goes wrong?

Takeaways

Remember this

  • A stateless server keeps nothing between requests that another copy would need, so any server can answer any request.
  • Sticky sessions route around state on a server; a crash, deploy or scale-in still loses it.
  • The usual fix is a random session ID in a protected cookie and the session in a replicated shared store.
  • Signed tokens remove the lookup but can't be revoked before they expire without a shared list, so keep them small and short-lived.
  • Statelessness concentrates state in stores built to keep it; it doesn't make state disappear.