---
id: platform/postgres-pooling
title: Postgres pooling before scaling
description: Rehearse direct versus transaction-pooled Postgres connections with a disposable Postgres and PgBouncer fixture before increasing application replicas.
keywords: [bex, postgres, pgbouncer, connection pool, scaling]
sidebar_position: 48
last_updated: 2026-09-23
---

# Postgres pooling before scaling

Before you increase application replicas, rehearse how those processes open
Postgres connections. Aggregate client slots grow with replica count, pool size,
and temporary rollout overlap. A transaction-mode PgBouncer keeps backend
connections closer to a configured pool size.

Source pin (Bex checkout behavior, not a claim about `api.bex.co`):
`f69e2c52c24a` — operator `poolerSpec` uses PgBouncer **transaction** mode;
Blueprint `connectionPool: pgbouncer` enables the pooler; services may reference
`fromDatabase.connectionPoolString` only when that pooler is enabled. Connection
info exposes `internalConnectionPoolString` /
`externalConnectionPoolString` alongside direct URLs when ready.

## Downloadable rehearsal

The [`postgres-pooling`](/examples/postgres-pooling/README.md) example (also in
the [examples catalog](/examples?task=postgres)):

```bash
curl -fsSLO https://bex.co/examples/postgres-pooling-latest.zip
curl -fsSLO https://bex.co/examples/postgres-pooling-latest.zip.sha256
# verify the zip SHA-256 against the sidecar's first field
unzip postgres-pooling-latest.zip
cd postgres-pooling
npm ci
npm run setup
npm run migrate
npm run load -- --path both --replicas 3 --requests 30
npm run inspect
npm run teardown -- --containers
```

Requires Docker. Missing Docker is an unmet prerequisite — not a successful
skip. Fixture ports are loopback-only (`55435` Postgres, `55436` PgBouncer).

## What to read from the report

- **Client slots** vs **pooler backends** are different budgets (`npm run budget`).
- Successful HTTP requests alone do **not** prove pooling — compare
  `backendsDuring` for direct vs pooled scenarios and keep pooled backends near
  `default_pool_size`.
- Use a **direct** URL for migrations/admin that need session state (`SET`, temp
  tables). Transaction pooling does not preserve session identity across
  checkouts (PgBouncer features for the pinned 1.23.x line).

## Blueprint sketch

```yaml
databases:
  - name: app-db
    plan: basic-256mb
    connectionPool: pgbouncer
services:
  - name: api
    type: web
    fromDatabase:
      name: app-db
      property: connectionPoolString
```

Copy hostnames and TLS requirements from your installation's connection-info
response. Do not paste local fixture URLs into production.

See also [Postgres](./postgres.md) and [Scaling](./scaling.md).
