All posts
    Engineering

    Serverless That Actually Scales (and Stays Cheap)

    AltSlate LabsJuly 28, 20269 min read

    "Serverless" gets sold as a silver bullet. It isn't — it's a set of trade-offs that pay off if you design for them.

    01 — fan-out is the point

    One heavy job becomes hundreds of small parallel ones

    The reason we reach for serverless isn't the "no servers" marketing. It's fan-out: turning one heavy job into hundreds of small parallel ones that finish in the time of the slowest single unit. For one production-accounting system, that took an allocation run from eight days to a few hours — same logic, restructured to fan out across compute and storage.

    This isn't a lab trick. FINRA runs market-surveillance pipelines over tens of billions of events a day on serverless compute; the ExCamera research encoded 4K video by bursting to thousands of parallel Lambdas, each handling a fraction of a second of footage. Wall-clock collapses to the slowest unit — provided the plumbing keeps up.

    S3 EVENTSNSSQSλ WORKERSSQSλ WORKERS
    The classic glue: an S3 event fans through SNS into buffered SQS queues, and Lambda drains them in parallel — batched, retried, and dead-lettered instead of firing one fragile invocation per event.

    02 — the numbers that actually bound you

    Fan-out is real, but the limits are hard

    "Infinite scale" is marketing. The quotas are specific, and you design against them. Lambda starts at a shared 1,000 concurrent executions per Region(raise on request) and scales at 1,000 new environments every 10 seconds per function. There's a subtler trap: requests-per-second is capped at 10× your concurrency — so sub-100ms functions hit a wall long before CPU does.

    1,000
    default concurrent Lambda executions per Region
    Shared across all functions; a soft quota
    10,000
    parallel child executions in a Step Functions Distributed Map
    Inline Map caps at 40 — reach for Distributed past that
    10×
    requests-per-second ceiling, relative to concurrency
    The limit that short, fast functions hit first

    Buffer with SQS rather than invoking straight off SNS: a queued source is batched (fewer invocations for the same volume) and gives you retries and a dead-letter queue for free. Throttle a Distributed Map with MaxConcurrency so the fan-out doesn't stampede whatever sits downstream.

    03 — pay-per-use, not pay-per-idle

    The right shape bills to zero when nobody's using it

    If a workload bills when nobody's using it, it's the wrong shape. Event-driven glue — queues and streams between stages, not long-lived orchestrators holding state — lets the spiky work scale elastically while the steady work stays predictable.

    COSTTIME →PROVISIONED SERVER — flat rentSERVERLESS — tracks demand, rests at zero
    A provisioned server pays a flat rent through every quiet hour. Serverless tracks the workload — spiking on demand, resting at zero.

    04 — cold starts

    A latency tax you now also pay in cash

    Before a function runs, Lambda may have to build a fresh environment — the cold start. Interpreted runtimes (Python, Node) initialise in tens of milliseconds; Java and .NET can take multiple seconds. SnapStart — snapshot-and-restore of a pre-initialised environment, now GA for Java, Python and .NET — cut one Spring Boot app's cold start from 6.1s to 1.4s at no extra charge. Provisioned concurrency keeps environments warm, but you pay per instance per hour, used or not — the pay-for-idle inversion of the whole point.

    And the tax is now literal: since August 2025, Lambda bills the Init phase on on-demand invocations of managed runtimes. For most functions it's negligible; for heavy-init workloads it can lift Lambda spend by a noticeable margin. Cold starts stopped being purely a latency story.

    05 — state & connections

    The database will not scale the way the compute does

    Here's the mismatch that catches teams: Lambda scales to hundreds of independent environments in seconds, and a relational database has a small, fixed connection ceiling. Each environment opens its own connections and doesn't share them, so a concurrency spike exhausts the database and everything starts erroring.

    λ ENVλ ENVλ ENVλ ENVλ ENVRDS PROXYpools + multiplexesDATABASEfew connections
    Put a pool in the middle (RDS Proxy) so many functions share few database connections — mind connection pinning from transactions and session state — or go connectionless with the Aurora Data API.

    06 — the crossover

    Sustained, busy workloads belong on a warm server

    AWS's own FinOps guidance says it plainly: serverless wins on spiky, low-duty-cycle work; containers and EC2 win once utilisation stays high, because always-on capacity is cheaper when it's actually always on. This isn't vendor FUD — it's first-party. Amazon Prime Video moved a monitoring service off a Step Functions + Lambda design that stalled near 5% of target load and cut infrastructure cost by over 90% with a monolith on ECS/EC2; athenahealth shifted hot functions to ECS on EC2 Spot for a ~73% monthly reduction.

    COSTUTILISATION →CONTAINER / EC2 — flatSERVERLESS — pay per usebreak-evencheaper serverlesscheaper provisioned
    Both are specific, high-duty-cycle orchestration workloads — the point isn't “serverless failed,” it's that steady, busy work crosses the line where a warm server is cheaper. The trick is knowing which side of the crossover each workload sits on.

    07 — the split

    Know which parts want to fan out and which want a warm, boring server

    Serverless punishes chatty, stateful, low-latency-at-all-costs workloads. Cold starts, connection limits, and per-invocation overhead are real. The trick is knowing which parts of a system want to fan out and which want a warm, boring server. Get that split right and you get the best of both: elastic scale on the spiky work, predictable cost on the steady work.