Every comparison of serverless and traditional cloud architecture starts the same way: serverless runs on demand and you pay for what you use. That is true, and it is the least interesting part.

What you learn only by building on it is a consequence. With serverless you do not decide whether to scale horizontally. You always do. And to scale horizontally, your application has to be stateless.

Horizontal scaling comes in the package

In a traditional setup you start with one server. It handles every request, so it can remember things between requests. Scaling is a later decision: a bigger machine first, a second one when you have to.

In serverless the platform creates an instance when requests arrive and throws it away when they stop. Two requests from the same user can run on two different instances, and the next one can run on a third that did not exist a second ago. You never chose to run many copies. It is how the model works.

Stateless is the price

If any instance can serve any request, no instance can be the one that remembers. Whatever sits in an instance’s memory or disk is a bet that the next request lands on the same instance, and sooner or later it does not.

In practice that moves a few things out of the process:

  • Sessions. A session kept in memory disappears. It goes in a shared store, or in a signed cookie the client carries.
  • Uploaded files. Writing to local disk loses the file. It goes to object storage.
  • Caches. An in-memory cache is one cache per instance, each with different contents. A shared cache fixes that.
  • Scheduled work. A timer inside the process dies with the process. An external scheduler triggers the function instead.
  • Database connections. Many instances opening their own connections can exhaust the database. A pooler sits in between.

None of this is hard on its own. What is easy to miss is that the code works on your machine, where there is one instance, and breaks in production, where there are several.

Traditional architecture lets you postpone it

On a single long-lived server, state in memory is free. The app works, you ship, and nothing complains. The bill arrives the day you need a second server: every session, cache and file assumption has to be undone at once, under pressure.

Serverless charges you up front. You are stateless from the first deploy because the platform will not let you be anything else. It feels like a restriction. It also means the day traffic grows, there is nothing left to untangle.

The extreme case

Otro Lunes is the cleanest example I have. The server holds nothing. The model call runs in a Route Handler that takes seven answers and returns a plan, and the plan is saved in the browser. There are no accounts and no database. Any instance can serve any request because no request depends on a previous one.

When I would still pick a server

Serverless fits spiky and unpredictable traffic and work that finishes quickly. A long-lived process fits better when:

  • the load is steady, so you pay for an instance you would use all day anyway;
  • a request runs for a long time or holds a connection open, as with websockets;
  • starting up is expensive, such as loading a large model into memory, and you want to do it once.

The question I ask before choosing

Can any instance serve any request? If the honest answer is no, I either fix that first or I pick the architecture that lets me keep the state. Choosing serverless and keeping the state anyway gives you the costs of both.