A $120 Bill for a Service That Runs Sixty Seconds a Day

Written by

in

A service I run — a Slack-triggered compliance job that fans out to a few APIs in parallel and finishes in a few seconds — turned in a Cloud Run bill over $120 for the month. It gets invoked 1-5 times a day. I did the math on that before digging further: even at five seconds of real work per call, five calls a day, that’s under a minute of actual compute across the whole month. A hundred and twenty dollars for a minute of work is the kind of number that makes you stop and check your assumptions.

It wasn’t Firestore. It wasn’t logging. It wasn’t the Slack API calls. Over 80% of the bill was Cloud Run itself.

The Setting I’d Forgotten About

Cloud Run has two modes, and I’d set mine to the expensive one without really registering that I’d done it:

gcloud run services describe SERVICE_NAME --format="value(spec.template.spec.containerConcurrency, spec.template.metadata.annotations)"

min-instances was set above zero. I’d done it months earlier to kill cold-start latency — nobody likes waiting a few extra seconds for the first request of the day. What I hadn’t fully priced in: setting it above zero means Cloud Run keeps a container alive around the clock, billed by the hour, whether a request ever shows up or not. Mine was idling roughly 1,440 minutes a day to cover a job that needed maybe one.

gcloud run services update SERVICE_NAME --min-instances=0

One flag. That’s the whole fix.

The Part That Actually Bugged Me

The service had been running for over a year without a single missed job. That’s exactly why I never looked at the bill closely — it worked, so I never had a reason to open the billing console and ask why. A service that fails gets investigated. A service that quietly does its job every day for a year doesn’t. The failure mode here wasn’t the code. It was that reliability made the waste invisible.

That’s the actual lesson, more than the specific flag: cost problems hide best in the things that work. If something’s been running clean for months, that’s not evidence there’s nothing to check — it might just mean nobody’s had a reason to check.

Cold Starts, Revisited

The reason I’d set min-instances above zero in the first place — cold-start latency — turned out to matter a lot less than I’d assumed for this use case. The service is triggered by a human submitting a Slack command and waiting for a reply. A few seconds of occasional added latency on a request that’s already interactive and infrequent is a real trade, but it’s a small one against paying for a warm container 24/7 to save it.

Scale-to-zero is the right default for anything shaped like this: infrequent, bursty, tolerant of a few seconds’ wait. If a service’s real invocation pattern looks more like a handful of calls a day than a steady stream, min-instances=0 is very likely underpriced right now and worth a five-minute check.

Related

I wrote up the business side of this same finding separately — what a missed DNC scrub actually costs versus what the tooling to prevent it costs, with real TCPA numbers: How Much Should DNC Compliance Automation Cost to Run?

More on the compliance system itself — the Slack interface, the fan-out architecture, the test floor — is on the project page.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *