advanced 2 min answer

A grocery-delivery marketplace moves its order-status API from long-running containers to per-request functions to stop paying for idle capacity overnight; traffic swings about 20x between trough and the evening peak. What did the team buy, and when does the bill arrive?

serverlesscontainersconnection-poolingcold-startcost
Show the full answer Hide the answer

What they bought

Scale-to-zero on a 20x swing is a genuine saving, and it is the one case where per-invocation billing clearly wins. At 5% of peak for eight hours a night, always-on capacity sized for the evening is idle for roughly a third of the day. They also bought the disappearance of a class of work: no instance rightsizing, no autoscaling policy to tune, no patching cadence.

What they pay

Per unit of CPU-time, functions are priced at a premium over always-on compute. On major-cloud list prices in 2025, a function sized to receive roughly one full vCPU costs about twice a container task of the same size running the same wall-clock hour, and three to five times an instance under a committed-use discount. The crossover therefore sits somewhere around a quarter to a half sustained utilisation: below that, functions are cheaper; above it, the saving reverses and grows.

The larger bill is structural. The function execution model handles one request per sandbox, so concurrency and process count are the same number. Every design assumption that came from long-running processes now breaks:

  • Database connections scale with concurrency, not with instance count. A Postgres instance holds a few hundred connections before memory per backend (roughly 5-10 MB each) and context switching degrade it. 800 concurrent invocations means 800 connection attempts unless a pooler sits in front, and the failure at peak is too many connections rather than slow queries.
  • In-process caches stop working. A warm hit rate that was 90% across thirty long-lived processes becomes near-zero across thousands of short-lived sandboxes, which pushes the load back onto the database the cache was protecting.
  • Cold start lands on p99, not p50. Every scale-up step adds initialisation to a real user's request, and traffic that ramps steeply into the evening peak ramps the cold-start rate with it.

When the bill arrives

Not at migration. It arrives on the first evening peak after the product succeeds, because all three effects are proportional to concurrency: the connection ceiling, the cache miss rate and the cold-start count all worsen exactly when traffic is highest. The team then adds a connection pooler, provisioned warm capacity and a shared cache, at which point the cost profile resembles containers with extra parts.

Keeping the option to reverse

Keep the handler a thin adapter over a framework that also runs in a container, keep all state and pooling outside the function, and write down the utilisation number that would flip the decision before migrating. Check it monthly against the actual bill.

When this is the wrong answer

For the overnight batch jobs and the webhook receivers next to this API, functions remain the right call at any scale — bursty, short, stateless, no connection fan-out. The flip is not "serverless versus containers" for the whole estate; it is per workload, and the deciding facts are sustained utilisation and whether the work holds a scarce connection. A marketplace with a flat daily curve and a busy relational database should not have moved this API at all.