Bonstructer Open app

Engineering · Cloud costs

We Paid for 106 Cloud Run Instance-Hours a Day Because of a Feature That Fires Once Every Two Days

Updated · 6 min read

On 2026-09-22 the Cloud Run compute line for our backend was almost the whole bill: 60 to 106 billable instance-hours a day for a service that handled about 0.5 requests per second. The cause was a live-reply feature built on server-sent events, made expensive by session affinity and always-on CPU.

What we saw

I build Bonstructer, a receipt notebook app, and run the backend as a single Cloud Run service. On 2026-09-22 I looked at the bill and the compute line was almost entirely that service. Billable instance time had gone from 1 hour a day on 09-09 to somewhere between 60 and 106 hours a day. That came to roughly $10 to $18 a day.

The traffic did not explain it. The service handled about 0.5 requests per second. At that volume an instance should be idle most of the day, and idle instances on Cloud Run cost close to nothing.

There was no database or Kubernetes cluster to blame. Firestore, model calls, Cloud Build, Artifact Registry and logging were all minor. It was Cloud Run compute, and only that.

Why: three causes that multiplied each other

1. Server-sent events that never ended

The app has a feedback feature: you send an idea or a problem report, and a reply shows up in the app. To make replies appear live I had added a server-sent events endpoint. Every signed-in Android app, web tab and admin tab opened a stream, held it for the full 540 second request timeout, and reconnected within 2 seconds.

That is how Cloud Run bills: an open request is an active instance. It does not matter that nothing is being sent on the stream. While a request is in flight, the instance counts as busy and you pay for it. I counted about 27 streams that were open permanently, so no instance ever got to scale to zero. The feature itself is rare: 7 replies in 14 days on my own account.

2. Session affinity kept old revisions alive

The service was deployed with session affinity. Affinity pins a client to the revision it first connected to. Combined with clients that reconnect every few minutes, this meant old revisions kept receiving traffic after I had moved traffic away. Revision 00621 still took 185 to 510 requests per 30 minutes, 2.5 hours after a deploy. Five revisions held about 20 instances between them. I deploy often (9 to 83 times a day on busy days), so every deploy left another revision pinned.

3. CPU always allocated

The service ran with CPU always allocated, which bills every instance-second at the full 2 vCPU, not only while a request is being handled. The median CPU use was 1 percent. So each of those pinned instances was billed at full size for doing almost nothing.

The fix

I removed the streaming from the clients instead of tuning it.

A reply now appears up to two minutes later instead of instantly. For a feature that fires every two days I accepted that.

After numbers

At 07:31 UTC on 2026-09-23, once the browsers got the 410, billable time was 0.3 instance-hours a day. The night before it was still 48 hours, because old browser bundles kept streaming until someone reloaded them. Firestore reads sit around 13k an hour overnight, with the feedback polls about half of that, well under $1 a day.

One leftover: older Android builds (0.1.245 to 0.1.273) still held streams open and added 4 to 5 instance-hours a day on 09-23. I could not refuse them from the server, because they retry every 2 seconds on any error and reconnect instantly on a clean close. The fix was to promote the closed-testing build to one that only polls, then retire the route for everyone once logs show no more streams. I do not have an after-fix dollar figure per day in my notes, only the instance-hours.

Checklist before you add streaming to Cloud Run

Read the change against each of these before you write it.

Then write the expected cost effect down in one or two sentences before you ship, and look at billable_instance_time within an hour after. We now keep that as a rule for every change, and the target is low single digits of instance-hours a day.

If you run streaming on Cloud Run with a mostly idle service, I would like to know how you keep it from dominating the bill.

I'm the developer of Bonstructer, a receipt notebook app: bonstructer.com.

Sources

FAQ

Does an idle server-sent events stream cost money on Cloud Run?

Yes. An open request keeps an instance active, and you are billed for it even when nothing is sent on the stream. About 27 permanently open streams meant no instance ever scaled to zero.

What replaced the streaming in Bonstructer?

Polling with a weak ETag and 304 answers: every 120 seconds while the tab is visible, every 45 seconds on the Ideas page, and never in the background.

What is a good target for Cloud Run instance-hours on a mostly idle service?

Low single digits of instance-hours a day. One 2 vCPU instance kept busy around the clock is about $4 a day.