← Back to Patterns

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.

By Ivan Richter LinkedIn

Last updated: Sep 1, 2026

4 min read

On this page

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 budgets

Then calculate each Cloud Run workload’s maximum claim:

service claim
  = max instances
  × processes per instance
  × pool max per process
  × rollout overlap factor

The 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 all

Related patterns