Cloud Run Concurrency: The Capacity Dial Behind Your Bill
Cloud Run concurrency is a capacity dial, not a magic switch. Learn how to match it to your runtime's real parallelism and memory limits, and verify the result with load testing before committing.
03 May 2026, 23:24 UTC

The Problem: Idle Containers, Rising Bills
You deploy a service to Cloud Run, traffic looks healthy, but the bill keeps climbing. You open the console and see CPU utilization sitting near zero across dozens of instances. The issue is rarely your application code; it is usually the concurrency setting left at a value that does not match how your container actually processes requests.
Concurrency controls how many simultaneous requests Cloud Run routes to a single container instance before starting another. Set it too low, and a burst of 100 requests spawns many short-lived instances that idle between requests. Set it too high, and requests queue inside a container that cannot keep up. The useful takeaway: concurrency is a capacity dial that must match your runtime's real parallelism and the memory ceiling of your container.
How Concurrency Changes the Instance Math
Cloud Run bills by instance time, not by request count. If your service receives 100 requests per second and each request takes 200 milliseconds, the total demand is roughly 20 concurrent request-seconds. With concurrency set to 1, Cloud Run needs about 20 instances. With concurrency set to 80, it needs only one or two. Fewer instances means fewer cold starts—the initialization delay when a new container starts—and less fragmented billing.
That math only works if the container can genuinely execute those requests in parallel. If it cannot, the platform still sends the traffic, but latency grows inside the instance.
Match the Setting to the Workload, Not the Hype
I/O-bound services—those waiting on database queries, external APIs, or file storage—often tolerate higher concurrency because the CPU is idle during network waits. CPU-bound services—image resizing, video transcoding, heavy JSON parsing—usually need lower concurrency so each request gets dedicated compute time.
A useful heuristic: if your p99 latency stays flat while instance count drops as you raise concurrency, you have headroom. If p99 latency climbs as instance count drops, you have crossed into queuing.
The Runtime Trap
Cloud Run's concurrency setting is a traffic director, not a code transformer. It tells the platform how many requests to hand to one container; it does not make your application framework process them in parallel.
For example, Python's Flask development server has historically processed requests sequentially. Depending on the Flask and Werkzeug versions in use, raising Cloud Run concurrency may simply queue requests inside the container until they time out. To benefit from higher concurrency in Python, run a production-grade server such as Gunicorn with a worker class that supports cooperative multitasking, or an ASGI server like Uvicorn for async frameworks. Verify the behavior for your specific framework version before assuming parallelism.
Worked Example: Auditing and Adjusting Concurrency
Suppose you run a Node.js API on Cloud Run that proxies database calls. Node.js handles I/O asynchronously on a single thread, making it a candidate for moderate-to-high concurrency. You want to move from the default to 50 and verify the impact.
First, deploy a new revision with the target concurrency. Run this from your local terminal with the Google Cloud CLI installed and authenticated, or from Cloud Shell:
gcloud run deploy my-api \
--image=gcr.io/my-project/my-api:latest \
--region=us-central1 \
--platform=managed \
--concurrency=50
Required permissions: you need the Cloud Run Admin role (roles/run.admin) or equivalent permissions to modify service configuration. The --platform=managed flag removes ambiguity with Cloud Run for Anthos environments.
Expected check: after deployment, confirm the revision is serving traffic and note the revision name. Do not assume the old instances disappear immediately; Cloud Run replaces instances gradually, and existing instances may continue running with the previous concurrency until they are drained.
Next, generate load and compare behavior. A simple tool like hey can send a fixed request rate:
hey -z 2m -q 50 https://my-api-xyz-uc.a.run.app/endpoint
This sends 50 requests per second for two minutes. While the test runs, open the Cloud Run service in the Google Cloud Console and inspect the metrics tab. Look for the instance count and request latency charts. The underlying Cloud Monitoring metric names can vary by API version, so use the console charts as the primary reference rather than memorizing metric identifiers.
Interpretation:
- Healthy: Instance count drops compared to a lower baseline, while p99 latency remains within your target.
- Concurrency too low: Instance count stays high even though CPU per instance is low. The platform is starting more containers than the workload needs.
- Concurrency too high: Instance count drops, but p99 latency climbs steadily. Requests are queuing inside the container.
Memory Is the Hard Ceiling
Concurrency does not create memory. Every simultaneous request consumes RAM for buffers, request objects, and runtime state. If you allocate 512 MiB to a container and each request needs 20 MiB of application memory, a rough lower bound for safe concurrency is around 25—before accounting for the base image, language runtime, and garbage collection overhead. Treat any per-request memory estimate as a floor, not a guarantee. If the container exceeds its limit, Cloud Run terminates the instance and you see 5xx errors or container restarts.
Rolling Back Safely
If the new concurrency causes errors, do not simply update the same revision while it is failing. Use traffic management to shift back to the previous revision:
gcloud run services update-traffic my-api \
--region=us-central1 \
--platform=managed \
--to-revisions=PREVIOUS_REVISION=100
This sends all traffic to the last known good revision. Once stable, you can investigate logs and redeploy with a corrected concurrency value. For lower-risk changes, split traffic between revisions—for example, 10 percent to the new concurrency and 90 percent to the old—and monitor before committing fully.
Actionable Next Step
Pick one service with steady traffic. Record its current instance count and p99 latency under a fixed load. Deploy a revision with concurrency raised by a small increment—say, from 1 to 10—and repeat the load test. Compare the two runs. If latency holds and instance count falls, you have found a cheaper operating point. If latency degrades, you have learned the runtime's real ceiling. Either way, you now have data instead of guesses.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.