Why Cloud Run + Postgres needs a connection budget
Cloud Run and Postgres get fragile when connection growth is left implicit. We treat connections as a finite runtime budget, not as plumbing the app can multiply without consequence.
A Cloud Run instance owns a local connection pool. Cloud Run owns how many instances exist. Postgres receives the multiplication. That’s why the service needs a connection budget.
A pool of five looks modest during development. Twenty instances, two overlapping revisions, and three worker processes per container turn the same setting into hundreds of possible sessions. None of the individual defaults had to look reckless. The fleet-wide claim was still fictional.
Budget from the database outward
Start with the usable database ceiling, not the application’s desired throughput.
Reserve headroom for:
- PostgreSQL and managed-service requirements
- administration and incident access
- migrations and maintenance
- monitoring and connection helpers
- other services, jobs, and workers
- failover or operational variance
The remainder is application capacity. Allocate it explicitly across workloads.
usable application sessions
= safe database ceiling
- operational headroom
- other workload budgetsThen calculate each Cloud Run workload’s maximum claim:
service claim
= max instances
× processes per instance
× pool max per process
× rollout overlap factorThe overlap factor isn’t always two, but a deployment can run old and new revisions together. Jobs and worker pools may create pressure outside the service’s request scaling. Put every path in the budget.
Concurrency isn’t connection capacity
Cloud Run concurrency says how many requests an instance may handle simultaneously. It doesn’t say how many need Postgres or how long they hold a session.
A service at concurrency eighty can be safe with a pool of four when database use is brief and sparse. A service at concurrency five can be dangerous with several processes, a pool of ten each, and transactions that wrap external calls.
Model three separate ratios:
- requests that touch the database
- concurrent database operations per instance
- average and tail session hold time
These determine whether the local pool is sufficient. The fleet budget determines whether that pool can be multiplied safely.
A pool is a local limiter
A per-instance pool protects one process. It doesn’t coordinate with pools on other instances.
This is why “the pool is only five” isn’t a capacity statement. The service’s max instance setting supplies the other half. If max scale is unbounded or far above the database budget, the pool only slows the route to exhaustion.
A managed pooler can multiplex application connections onto fewer database sessions, but it still needs a backend limit and workload policy. It changes the equation. It doesn’t abolish it.
Minimum pools are expensive in elastic fleets
A non-zero pool minimum can improve connection latency on warm instances. It also reserves sessions on every active instance and revision, including instances doing little database work.
Keep minimums at zero or very low by default. Earn warm sessions with measured latency. Connection establishment should be optimized after the fleet can prove it won’t reserve the database into exhaustion.
Bounded pressure is the operating goal
The budget is useful only when the application respects it under load.
Set maximum instances, small pools, short acquisition deadlines, bounded retries, and database statement or transaction timeouts. When the budget is consumed, reject, queue, or defer work before Postgres becomes the queue.
The system should preserve enough headroom for operators to connect during an incident. A database that uses every session for failing application requests has removed the tools needed to repair it.
Revisit the budget when the shape changes
Recalculate after:
- adding worker processes or changing runtime concurrency
- introducing a new service, job, worker pool, or migration path
- changing pooler mode
- enabling minimum instances
- widening max scale
- increasing request or transaction duration
- resizing or migrating the database
- changing rollout strategy
Treat the calculation as infrastructure configuration, not a spreadsheet remembered by one engineer. Expose current pool and scale values beside the budget and alert when deployments can exceed it.
The boundary tells you what to change
If the useful service throughput doesn’t fit the connection budget, one of the assumptions must move.
Reduce session hold time. Use fewer sessions per instance. Limit fleet width. Separate asynchronous work. Add a pooler when multiplexing fits the transaction semantics. Increase database capacity when the workload is already disciplined and the business needs more throughput.
Raising max_connections first accepts the application’s existing claim without asking whether it’s efficient or bounded.
Cloud Run and Postgres work well together when elasticity stops at a declared database boundary. Write the multiplication down before production writes it for you.
More in this domain: Operations
Browse allAn alert is not a notification
A notification says something happened. An operational alert identifies a business situation, assigns ownership, carries enough context to act, records the response, and becomes workflow state.
Why alert feedback should be structured first
Free text helps, but structured alert feedback lets the system measure relevance, timing, duplicates, bad data, and rule quality. Human response becomes evidence the rules can learn from.
How we diagnose and fix a "too many connections" incident for Cloud Run + Postgres
A "too many connections" incident is rarely a one-line fix. It usually exposes a bad contract between Cloud Run scaling, app pool behavior, and database capacity.
AlloyDB managed connection pooling: when we'd trust it over PgBouncer
AlloyDB managed pooling is attractive because it removes a moving part, but the useful decision is whether the managed path gives enough semantic confidence, observability, and migration predictability to replace PgBouncer.
Cloud SQL to AlloyDB migration: what actually changes, what doesn't, and what we'd test first
A Cloud SQL to AlloyDB move is not a philosophical upgrade. It changes the operational boundary, and the useful work is re-proving the parts of the system that may no longer behave the same.
Related patterns
Cloud SQL vs AlloyDB: the real difference is operational boundary, not benchmarks
The useful comparison between Cloud SQL and AlloyDB is not raw speed. It is how the operating boundary changes around scaling, pooling, failover, migration, and team burden.
Managed connection pooling in Cloud SQL: when it helps and when it complicates things
Managed connection pooling in Cloud SQL can reduce bursty connection pressure, but it also changes session behavior and should be adopted like a runtime boundary, not like a harmless checkbox.
How we decide between Cloud SQL connectors, Auth Proxy, and private IP
Cloud SQL connectors, the Auth Proxy, and private IP are not interchangeable secure connection options. They change identity, routing, deployment shape, and how much network plumbing the team actually owns.
Safe scaling defaults for Cloud Run + Postgres
Cloud Run autoscaling is not a database strategy. Safe defaults keep the application from scaling itself into a Postgres incident before the team understands the workload.