Is Your Application Ready for Connection Pooling?

Evaluate whether your application will benefit from and is compatible with a transaction-mode connection pooler like PgDog.

Connection pooling can dramatically reduce database overhead and improve application scalability — but not every application is a good fit out of the box. This guide walks you through two things: whether your application is likely to benefit from pooling, and whether it is compatible with a transaction-mode pooler like PgDog.

Will your application benefit?

Before checking compatibility, it's worth asking whether pooling will actually help. The main value of a pooler is multiplexing — letting many application connections share a smaller number of real database connections. Three signals indicate your app is a good candidate.

Short transactions

If your application executes queries that are fast and frequent, connections are released back to the pool quickly and can be reused by other clients. This is the core mechanic that makes pooling valuable.

If your application runs only a few slow queries, connections stay checked out for longer, leaving little opportunity for sharing. Pooling will still work, but the benefit will be minimal.

High idle connection ratio

A high ratio of idle to total connections suggests that clients are holding open connections without actively using them. A pooler can absorb these idle clients without tying up real database connections.

On the other hand, if your application maintains a small, stable number of connections with very little idle time, the overhead reduction from pooling will be modest.

Connection spikes

If your connection count periodically surges well above its typical average — triggering alerts or requiring you to raise max_connections in Postgres — a pooler is particularly valuable. The pooler acts as a buffer: Postgres sees a stable, capped number of connections while the pooler absorbs the spike and temporarily queues clients waiting for a free slot.

Is your application compatible?

Transaction-mode poolers work by assigning a database connection to a client only for the duration of a single transaction. This creates several compatibility constraints. Some are hard blockers; others depend on how your application uses a feature.

Long-lived transactions

If your application opens a transaction and holds it open for an extended period without committing, the pooler cannot reclaim that connection during that time — it stays locked to the client. This is generally considered an anti-pattern for PostgreSQL usage regardless of pooling.

This may be acceptable if long-lived transactions are rare and short-lived ones make up the bulk of your workload.

Session state modifiers

Setting session-level options is not compatible with connection pooling. Because each transaction may be executed on a different underlying connection, any settings you apply will not persist between transactions. This affects:

  • SET search_path
  • SET ROLE
  • SET timezone
  • SET statement_timeout
  • SET application_name
  • SET LOCAL used outside of an explicit BEGIN

Workaround: Apply these settings at the start of each transaction rather than relying on them persisting across the session.

LISTEN / NOTIFY

PgDog's architecture is expected to support LISTEN/NOTIFY even in transaction mode, but this has not been fully verified. If your application relies heavily on this feature, test carefully before deploying behind the pooler.

IAM authentication

IAM authentication is not currently supported by PgDog. IAM relies on short-lived, rotating passwords, which PgDog cannot handle. There is no general workaround at this time.

If you are on GCP, enabling Cloud SQL Proxy via DBO may be a viable path, but the full stack (including PgDog's password-based authentication handshake) has not been tested end-to-end.

Anonymous prepared statements

Some database drivers — notably the legacy Go lib/pq driver — prepare statements using a blank (anonymous) name. When connections are multiplexed, the prepare and execute steps can end up on different connections, causing a mismatch.

Workarounds:

  • Enable PgDog's extended_anonymous mode, which provides partial support for this pattern.
  • Switch to a driver that does not use anonymous prepared statements in autocommit query execution mode.

Server-side prepared statements (simple query protocol)

Statements prepared with the simple query protocol (e.g. PREPARE get_user AS SELECT * FROM users WHERE id = $1) are session-scoped and will not carry over across pooled connections by default.

Workarounds:

  • Use the extended protocol for prepared statements instead.
  • Enable prepared_statements = "full" in PgDog, which adds query parsing to handle this — note that this mode is more compute-intensive.

Temporary tables

Temporary tables (CREATE TEMP TABLE foo) are bound to the database session, not the transaction. If your application creates a temp table and expects it to persist across multiple transactions, this will break under transaction-mode pooling.

This is fine if your application creates and drops temp tables within a single transaction.

Session-level advisory locks

Session-level advisory locks (pg_advisory_lock, pg_advisory_lock_shared, etc.) are tied to a specific database session. Under pooling, this behavior is preserved by pinning the client to a specific pooled connection — but this requires query_parser = "on" in PgDog and reduces pooling efficiency.

Workarounds:

  • If possible, route traffic that uses advisory locks directly to the database, bypassing the pooler entirely. This is often practical for workloads like database migrations.
  • Where applicable, switch to transaction-scoped advisory lock functions:
    • pg_advisory_xact_lock()
    • pg_advisory_xact_lock_shared()
    • pg_try_advisory_xact_lock()
    • pg_try_advisory_xact_lock_shared()

Backend PID

pg_backend_pid() is not compatible with connection pooling. With pooling, a client connection is no longer mapped 1:1 to a database connection, so this value will not be stable or meaningful. Any application logic that depends on a consistent backend PID will not work behind a pooler, and there is no workaround.

Held cursors

Cursors declared with WITH HOLD persist beyond the transaction that created them and are bound to the session:

DECLARE c CURSOR WITH HOLD FOR SELECT ...;
FETCH 10 FROM c;
FETCH 10 FROM c;

Held cursors are not compatible with transaction-mode pooling. Because they span transactions and are session-scoped, there is no way to use them behind a pooler without refactoring to a different query pattern.

Summary

FeatureCompatible?
Long-lived transactionsYes
Session state modifiers (SET ...)OK if set per transaction
LISTEN / NOTIFYNo
IAM authenticationNo
Anonymous prepared statementsYes
Server-side prepared statements (simple protocol)Yes
Temporary tablesOK if scoped to a single transaction
Session-level advisory locksYes
Backend PID (pg_backend_pid())No
Held cursorsNo