beginner 3 min answer Multiple choice

A nightly reconciliation job reads a full day of orders and writes a summary. On a developer machine it takes about 90 minutes. The team wants to run it as a single per-request function because the rest of the service already runs that way. Why does that not work?

serverlessbeginnerplatform limitsbatchexecution model
Pick one
Show the full answer Hide the answer

The mechanism

Per-invocation platforms bound a single execution. As of 2026 the widely used function runtimes cap one invocation at about 15 minutes, with memory in the low gigabytes, request and response payloads of a few megabytes, and a small amount of local scratch disk. These are platform ceilings, not tunables - there is no setting that buys the 91st minute.

The failure is worse than a timeout, because of what a killed batch job leaves behind. At minute 15 the process disappears mid-write. If the summary is written incrementally, you have a partial summary that looks complete; if it is written at the end, you have nothing and the retry starts from zero, hits the same ceiling, and the platform's retry policy then runs it two more times. The symptom reported in the morning is "the report is wrong", not "the job failed", which is how this costs a day rather than an hour.

The order to check things in

  1. Ceilings first: duration, memory, payload size, local disk, concurrency. They are absolute, so they rule options out before any cost model is relevant.
  2. Then the shape of the work. Can it be partitioned? A reconciliation over a day of orders usually can - by hour, by store, by tenant - and each partition finishes well inside the limit.
  3. Then the economics. Utilisation decides whether always-on capacity beats per-invocation billing, and only after the first two questions have left you with more than one viable option.

What to do instead

Either partition or move the execution model. Partitioned: a small coordinator enumerates the partitions and invokes one function per partition - 24 hourly slices of a 90-minute job are about four minutes each, they run in parallel, and a failed slice retries on its own. Unpartitioned: run it as a scheduled container or batch task, where the only limit is the one you set.

The partitioned version is not free. It needs idempotent writes per partition, a way to tell complete from partially complete, and a final step that fails loudly if any slice is missing. That coordination is the price of staying on the execution model the rest of the service uses.

Why the other options fail

  • Connections. Functions open database connections fine. There is a real connection problem in this family - a few hundred concurrent invocations can exhaust a database's connection limit, which is why a pooler sits in front - but it is a concurrency problem, not a duration one, and it does not explain a single long job failing.
  • Cold start. A few hundred milliseconds to a few seconds, once, against a 90-minute run and a window measured in hours. Irrelevant here, and a good example of a cost that gets quoted because it is familiar rather than because it binds.
  • Cost. A job running 45 hours a month costs very little on either model. Per-invocation billing becomes the expensive choice when work is continuous, not when it is nightly - and quoting cost here skips the fact that the design cannot run at all.

When the simpler answer is right - and when this is the wrong fight

If the job cannot be partitioned - one sort over the whole day, or a single transaction - stop trying and run a container. Spending a sprint on a coordinator so that a once-a-night job can stay on the same execution model as the API is complexity bought for consistency's sake. The reverse also holds: if the work partitions naturally, functions give you parallelism for free and a container would need a work queue to match it.