Back to notes

January 24, 2026 · 5 min read

A Serverless Cost Surprise That Taught Us to Model Usage Better

A serverless function we expected to cost about forty dollars a month showed up on the bill at over two thousand. The culprit wasn't traffic — it was a retry loop and an assumption about execution time that was wrong by 40x.

PostmortemCost

We estimated a serverless image-processing function at roughly $40 a month based on expected traffic and a rough guess at execution time. The first month's bill came in at $2,140. Nobody noticed a traffic spike because there wasn't one — total invocation count matched our estimate almost exactly. The cost came from somewhere else entirely.

What the estimate got wrong

Our $40 estimate assumed an average execution time of about 300ms per invocation, based on a quick local test against a few sample images. In production, a specific class of large, high-resolution uploads — maybe 8% of total traffic — triggered execution times closer to 12 seconds, because our local test images happened to all be small. Serverless billing is a function of execution time times memory allocation times invocation count, and we'd modeled the first variable off a sample that wasn't representative of real-world inputs.

A cost estimate built on a handful of local test files isn't an estimate. It's a guess wearing a spreadsheet.

The retry loop made it worse

On top of the execution-time miss, the function was configured with automatic retries on timeout, and the large-image invocations were timing out against a function-level timeout we'd set too conservatively low for that case, triggering up to three retries per failed invocation — each retry itself burning most of the same long execution time before failing again. That 8% of traffic wasn't just costing more per invocation, it was costing up to 4x more per invocation due to retries, compounding an already-wrong estimate.

  • Pulled a representative sample of real production file sizes before re-estimating anything, instead of trusting a handful of local test fixtures.
  • Raised the function timeout to comfortably cover the actual worst-case execution time for large images, instead of a conservative default that triggered unnecessary retries.
  • Added a pre-processing size check that routes unusually large uploads to a separate, dedicated function with its own memory and timeout profile, instead of one function trying to handle both cases well.
  • Set up a cost-per-invocation-class alert so a shift in the distribution of input sizes shows up as a cost signal immediately, not a monthly bill surprise.

The fix dropped the bill to around $180 a month — still above the original $40 estimate, because the original estimate was simply wrong about real-world execution time, not because anything was still broken. The lesson wasn't 'serverless is expensive.' It was that a cost model built on synthetic test inputs will miss exactly the tail cases that actually drive the bill, and tail cases in execution time matter more in serverless billing than they do in a fixed-capacity server you're paying for either way.