Last month I dropped my cloud computing bill by 80%. I didn't rewrite anything in Rust or migrate away from serverless. The savings came from realising that a 'genius' 30-second configuration fix I had applied weeks earlier was a silent budget assassin running 24 hours a day.
If you are running Next.js, Node, or Go on Google Cloud Run and rely on background tasks, you are likely at risk of the exact same trap.
The problem: scale-to-zero kills background jobs
Serverless platforms like Cloud Run are cost-effective because they scale to zero. You pay only for the milliseconds a container is actively processing a request. The moment your handler fires a response, Cloud Run throttles the container CPU to near zero.
This creates a hard problem for async work. I was building a webhook flow:
- Cloud Run API receives a webhook from a third-party service.
- The system needs to parse the payload, sync a batch of heavy images to a GCS bucket, and update the database.
- I need to return a `200 OK` immediately so the third party does not time out.
The moment the `200 OK` fires, GCP assumes the work is done. CPU is throttled to near zero. The Node.js event loop jobs - image sync, DB writes - stall, freeze, or fail silently. Network connections drop. The work vanishes.
The expensive workaround: --no-cpu-throttling
I hit the docs and found what looked like a magic wand: CPU always allocated (`--no-cpu-throttling`). I flipped the flag in 30 seconds. The background job issue vanished. The webhooks succeeded, the images synced, and I posted on LinkedIn about how easy the fix was.
When I opened the GCP billing dashboard the next month, my Cloud Run instances were logging 168 billable instance-hours per week. The CPU was kept 'allocated' around the clock waiting for background jobs, so I was billed for 24/7 compute. I was paying premium serverless prices to run what was effectively a traditional always-on VM.
| CPU throttling ON (default) | CPU throttling OFF (--no-cpu-throttling) | |
|---|---|---|
| Billing model | Pay per active request ms | Always-on: pay 24/7 |
| Idle cost | Zero | Full instance-hour rate |
| Weekly instance-hours (light traffic) | ~2–4 hrs | 168 hrs |
| Background job reliability | Poor (jobs stall) | Good |
| Relative monthly cost | Baseline | ~10–15x baseline |
The real fix: event-driven decoupling with Cloud Tasks
To get that 80% reduction I re-architected the flow. The goal: let Cloud Run safely scale to zero the moment a request ends, while guaranteeing that the heavy background jobs actually execute.
Step one: turn CPU throttling back on. Instant 24/7 billing leak plugged.
Step two: migrate async work to Google Cloud Tasks. Cloud Tasks is specifically built for explicit, point-to-point HTTP execution. You target a specific worker endpoint, guarantee delivery, and get fine-grained control over retries, rate-limiting, and execution timing. This is why I chose it over Pub/Sub - Pub/Sub is great for fan-out messaging, but for webhook processing with one worker and controlled retries, Cloud Tasks is the right primitive.
The new, cost-optimised architecture
- Ingestion (fast and cheap): The main Cloud Run API receives the webhook. CPU is throttling-enabled.
- The handoff: Instead of executing the heavy image sync inline, the API parses the required IDs, builds a JSON payload, and enqueues a Cloud Tasks job targeting the dedicated worker endpoint.
- The quick exit: The API returns `200 OK` instantly. The request ends. Cloud Run throttles CPU to zero. Billing stops.
- The worker (on-demand compute): Cloud Tasks fires an HTTP request to the dedicated worker endpoint. Cloud Run spins up fresh compute for exactly this task. The worker completes the heavy sync and scales back down.
Results: faster, cheaper, and actually resilient
- 80% cost reduction. Billable instance-hours plummeted. The platform idles for free between requests.
- Bulletproof reliability. Cloud Tasks has built-in retries with exponential backoff. If an image sync fails due to a network hiccup or a database lock, Cloud Tasks retries the worker automatically without touching the main API or losing data in a frozen event loop.
- Zero main-thread blocking. The webhook ingestion API now responds in milliseconds, completely decoupled from processing time.
The lesson
Serverless infrastructure is cheap because it idles for free. The moment you force it to stay awake to paper over an architectural flaw, you are paying always-on rates for always-on compute - and getting none of the resilience benefits of a real worker queue.
If background jobs are stalling your APIs, don't flip the throttling flag. Build a worker, enqueue a task, and let the cloud scale to zero.
The fix is never the flag. The fix is the architecture.