Stateless services

BackendTier 3 · scale vocabularyPhase 5 · It got popular

Designing servers so none of them remembers a specific user, letting any server handle any request. Required for horizontal scaling.

If a server keeps a user's session in its own memory, that user is stuck to that one machine and cannot be freely moved or balanced. Stateless services exist by keeping no per-user memory between requests, so any server can handle any request and you can add or remove servers freely.

If server A remembered you were logged in, but the load balancer sent your next request to server B, B wouldn't know you. So servers are kept stateless: they store nothing about individual users between requests. The "who is this" proof travels in the request (a token) or lives in a shared store (like Redis) all servers can read.

This is what lets you add and remove servers freely: since none of them holds anything unique, requests can go to any of them. Statelessness is the quiet precondition for scaling out.

A junior PM, stuck

We scaled from one server to three last week, and now support has a handful of customers whose carts empty out halfway through ordering, always right at checkout. When I asked the engineer he said "something is not stateless" and pasted ten log lines from the three servers. I can see the same customer and different server names, but I cannot follow how a cart that was clearly there just disappears.

Nothing disappeared, and these ten lines show you where the cart actually is. The load balancer now spreads one customer's taps across three servers, and the log tags each line with which server handled it, so the whole bug is visible once you read that tag. Watch one thing as you go: which server writes the cart, and which server the checkout lands on. Those being different is the entire incident. Read the server name on each line, then take the steps with me.

App server logs, user u_88231 cart, right after the scale-out
2026-03-06T12:01:40ZINFOlb scaled out, now routing across server-1 server-2 server-3
2026-03-06T12:02:03ZINFOserver-1 cart add user=u_88231 item=p_1042
2026-03-06T12:02:03ZINFOserver-1 cart saved to local memory user=u_88231 items=1
2026-03-06T12:03:20ZINFOserver-1 cart add user=u_88231 item=p_1047 items=2
2026-03-06T12:03:55ZINFOserver-2 GET /api/products 200 user=u_88231
2026-03-06T12:04:01ZINFOserver-3 POST /api/orders received user=u_88231
2026-03-06T12:04:01ZINFOserver-3 cart lookup local memory user=u_88231
2026-03-06T12:04:01ZWARNserver-3 cart miss user=u_88231 items=0
2026-03-06T12:04:01ZERRORserver-3 checkout aborted reason=empty_cart user=u_88231
2026-03-06T12:04:03ZINFOserver-1 cart still in memory user=u_88231 items=2

Click a step to see the lines it points at.

Chasing a data-loss or database bug when nothing was lost. The last line shows the cart intact on server-1, so the cart was never deleted, it was just unreachable from server-3.
Trying to reproduce it on your own machine or one server. With a single server every request lands in the same place and the cart is always found, so the bug only appears once traffic is split across servers.
Asking to "make the load balancer always send a user to the same server" as the fix. Sticky routing hides the problem but re-pins each user to one machine, which gives back the ability to move traffic freely that scaling out was for.
Not recognizing that "move the cart to Redis" is the actual ask. The real fix is keeping the cart in a shared store every server can read, so any server can complete the checkout, which is what the engineer means by stateless.
Filing it as "checkout randomly fails" with no server tags. "12:04:01, u_88231 checkout on server-3 finds items=0, but server-1 still holds items=2" points straight at the split, while "randomly" makes the engineer start from nothing.

Reply in the thread with: "Read the server tags. The cart is written to server-1's local memory, but after the scale-out the checkout POST landed on server-3, which has no cart, so it aborts empty even though server-1 still holds two items. Nothing is lost, it is stranded on one server. The fix is putting the cart in a shared store every app server can read." You used the server tag on each line to turn "vanished" into a precise cause.

It's why session data lives in a shared store, not on one machine, and why "just add servers" is even possible.
"It only breaks on some servers" can mean something isn't truly stateless; a useful pattern to recognize.

"Sessions moved to Redis so the app servers can stay stateless."

Appears in Phase 5, It got popular.