Home / Blog / Stateless backends don’t remove state, they move it somewhere more expensive

Stateless backends don’t remove state, they move it somewhere more expensive

Stateless design doesn't solve every problem, and it invents a few new ones. Here is what I've actually hit in production, and where the non-stateless pieces end up.

At a startup architecture meeting, nobody pushed back when someone said “let’s go fully stateless”. Horizontal scaling would be easy, deploys would be low-risk, instances would be interchangeable. What actually happened: state didn’t disappear, it just moved.

“Stateless” carries a sleight of hand. The application server holds no state, sure. But the system as a whole still does. Redis, PostgreSQL, S3, Kafka, the session store, the rate-limit store, the feature-flag store. That’s all state. When the app servers shed it, the state gets crammed into more central places, and those places become the bottleneck.

Project one: we used JWT for authentication. “No stateful session store”, we said, pleased with ourselves. Then we wanted logout to revoke the token. JWTs don’t self-revoke, they’re valid until they expire. The fix: keep a revocation list in Redis. Welcome back, state. And now every API call hits Redis.

Second trap: caching. A stateless app server obviously can’t use local caches, no two instances would agree on the state. It all goes to Redis or Memcached. And when the cache server goes down? The app turns into a self-generated DDoS against the origin. You need a circuit breaker.

Third trap: rate limiting. You want “60 requests per minute per user”. Where do you count, in a stateless app? Atomic increment plus TTL in Redis. More Redis writes, one per request.

Fourth trap: long-running operations. A user starts an upload and wants to see progress. With stateless servers you build an upload-ID + Redis + polling chain, or you use WebSockets. WebSocket connections are stateful and pinned to a server, which means sticky sessions or a pub/sub layer.

On one project, during a microservice migration, we realized the heaviest traffic wasn’t between services; it was everyone hammering Redis. The Redis cluster became the bottleneck. The fix was to keep some state (user context) on the app server and route the user to the same instance via sticky sessions. We sacrificed some horizontal elasticity, but the state was always on the right node and the hot path got cooler.

What stateless thinking gets right is separating application logic from state. What it gets wrong is assuming statelessness is free. State doesn’t go away; it relocates, and the relocation usually makes it more expensive because access is now over the network.

A practical framework. Ask:

  • Does this state already exist, or are we creating new state?
  • Is it needed on every request, or only on specific flows?
  • What happens if we lose it?
  • What’s the latency budget?

The answers tell you where the state should live. Session-like state that affects every request needs a central store. Transient computation state is fine in memory. Long-lived data belongs in a database. Non-turbulent state is a reasonable fit for sticky sessions.

“Stateless” is the wrong word anyway. The accurate one is “share-nothing compute layer”. App servers don’t share with each other; the state is centralized in the data tier. Once you see it that way, the architecture decisions get clearer.

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch