connection-pooling — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited connection-pooling (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
What it is: connection-pooling is the discipline for keeping a bounded set of database connections open, handing them to units of work briefly, and returning them without letting application concurrency overwhelm database-side connection capacity.
Mental model: A pool is a wait queue plus a small number of expensive database sessions. Size it from peak concurrent database work, measure wait time separately from query time, and choose proxy modes by the database features the application needs.
Why it exists: Opening a connection per request is expensive, and every open connection has a standing cost. The pool protects the database from client fan-out while keeping application work from waiting unnecessarily.
What it is NOT: It is not query-plan tuning, index design, replica routing, shard coordination, durable job retry design, stream backpressure, or transaction isolation semantics.
Adjacent concepts: query-optimization owns slow query work; indexing-strategy owns access paths; replication-patterns owns read/write replica routing; sharding-strategy owns data partitioning; transaction-isolation owns concurrency correctness; background-jobs owns durable worker retries; streaming-architecture owns value-stream backpressure.
One-line analogy: A connection pool is a taxi rank for database sessions: too few taxis leaves work waiting, too many taxis clog the road, and adding taxis does not make trips faster.
Common misconception: Bigger pools are not automatically more capacity; beyond the database's useful concurrency, larger pools move the bottleneck from pool wait time to CPU, locks, cache churn, and connection exhaustion.
The discipline of managing a finite set of database connections shared across many application threads, requests, or processes. Covers the connection cost (server-side process/thread, memory, locks), why pooling is required (open-cost amortization, throughput cap, load-shedding), application-level vs proxy-level pools, the three PgBouncer modes (session, transaction, statement) and their feature compatibility, the canonical pool-sizing math via Little's Law and HikariCP's analyses, the failure modes catalog (connection exhaustion, idle-in-transaction, hot-loop reconnect, prepared-statement breakage, cross-connection state leaks, long-tail accumulation), the operational concerns (wait-time monitoring, connection rotation, reconnect backoff, health checks), and the database-specific connection models (Postgres process-per-connection, MySQL thread-per-connection, serverless variants).
The pool is a throughput throttle, not a resource budget. Sizing too large doesn't make slow queries faster — it makes the database thrash and shifts the symptom from "queue waiting for connection" to "queue waiting for CPU, buffer cache, or locks." Sizing too small produces queue waits. The right size is the smallest pool that doesn't queue under peak load — typically much smaller than teams initially set.
Pooling mode determines feature surface. The choice between session, transaction, and statement pooling is not just operational — it determines what features the application is allowed to use at the database. Transaction pooling buys multiplexing in exchange for session-feature loss; statement pooling buys further multiplexing in exchange for transaction loss. Knowing which features the application uses, and the cross-product against the pooling mode, is preconditional to choosing a mode.
The pool is the place where database-level health becomes application-level latency. A workload contending on the pool surfaces as request queueing in the application, not as slow queries in the database log. Pool instrumentation (pool.active, pool.idle, pool.waiting, pool.acquire_time) is the operational hygiene that makes contention legible.
Little's Law: concurrency = arrival rate × average service time.
| Workload | Arrival rate | Avg query time | Concurrency | Pool size |
|---|---|---|---|---|
| OLTP point query | 10,000 req/sec | 1 ms | 10 | 12–15 |
| OLTP transaction | 1,000 req/sec | 10 ms | 10 | 12–15 |
| Mixed read/write | 2,000 req/sec | 25 ms | 50 | 60–80 |
| Analytical | 100 req/sec | 500 ms | 50 | Pool partitioning recommended |
The pool size is peak concurrency + small headroom. Teams that size by request rate (treating pool size as a per-app-server quota) over-size by 10x or more, then discover the database is thrashing.
HikariCP's documented advice: start with cores * 2 + effective_spindle_count (e.g., 8 cores → pool size 18); raise only when measured queue waits prove a larger pool helps. Most OLTP pools are <20 per app instance.
| Feature | Session | Transaction | Statement |
|---|---|---|---|
| Prepared statements (server-side) | ✅ | ✅ (1.21+) / ❌ (pre-1.21) | ❌ |
SET session variables | ✅ | ❌ (use SET LOCAL) | ❌ |
SET LOCAL (transaction-scoped) | ✅ | ✅ | ❌ |
| Advisory locks | ✅ | ❌ | ❌ |
LISTEN / NOTIFY | ✅ | ❌ | ❌ |
WITH HOLD cursors | ✅ | ❌ | ❌ |
| Temporary tables across transactions | ✅ | ❌ | ❌ |
| Transactions | ✅ | ✅ | ❌ |
| Multiplexing benefit | 1x | High (10–100x) | Highest |
Default rule: Transaction mode for production scale; verify the application uses no session-spanning features (or, if it does, audit each one). Session mode when full Postgres feature surface is required.
| Symptom | Likely cause | First diagnostic |
|---|---|---|
too many connections error | Pool size × instances > max_connections; reconnect storm | Sum app pools + replica pools; check reconnect rate |
| Request latency spike, query latency normal | Pool exhaustion (queries holding connections too long) | pool.acquire_time_p99 vs query latency |
| Intermittent "prepared statement does not exist" | PgBouncer transaction mode, pre-1.21 | Upgrade PgBouncer or disable server-side prepares |
| Random session-variable values | SET (not SET LOCAL) under transaction pooling | Audit SET use; switch to SET LOCAL |
| Connections held for hours; transaction-id age growing | Idle-in-transaction | pg_stat_activity for long idle in transaction |
| Brief outage during deploy | Reconnect storm | Stagger app startup; add reconnect backoff |
| Slow degradation over weeks | Long-tail connection age (memory bloat, stale prepared statements) | Enable maxLifetime rotation |
After applying this skill, verify:
max_connections with headroom. The database-side total is bounded, not just the per-instance pool.SET, advisory locks, LISTEN/NOTIFY, and WITH HOLD cursors has been audited. Compatible patterns confirmed; incompatible patterns refactored.pool.acquire_time, pool.active, pool.waiting. Connection contention shows up as a first-class signal, not as opaque application latency.idle_in_transaction_session_timeout is set (Postgres) so leaked transactions don't hold pool slots indefinitely. Application code does not perform external service calls inside database transactions.maxLifetime / server_lifetime) is configured so connections refresh and don't accumulate long-tail bloat.| Instead of this skill | Use | Why |
|---|---|---|
| Tuning a slow query | query-optimization | query-optimization owns query-level cost; this owns connection-level cost |
| Designing indexes | indexing-strategy | indexing-strategy owns access-path design |
| Routing reads vs writes across replicas | replication-patterns | replication-patterns owns the routing layer above pooling |
| Partitioning data across shards | sharding-strategy | sharding-strategy owns the data-partition layer; pooling sits beneath it per shard |
| Choosing transaction isolation level | transaction-isolation | isolation owns the per-transaction concurrency contract |
| Designing the schema | entity-relationship-modeling | entity-relationship-modeling owns design; pooling is operational |
max_connections, idle_in_transaction_session_timeout, and related configuration.indexing-strategy; the chapter on operational concerns.<!-- skill-graph-context:start (generated — do not edit by hand) -->
Classification
backend-engineeringtrueengineering/dataWhen to use
connection-pooling, what should max pool size be, PgBouncer transaction mode, too many connections error, connection exhaustion, prepared statements not working with PgBouncerNot for
Related skills
replication-patterns, performance-engineering, query-optimization, transaction-isolationindexing-strategy, sharding-strategy, query-optimization, replication-patterns, transaction-isolation, background-jobs, streaming-architecture, performance-engineeringConcept
Grounding
universalhttps://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing, https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole, https://www.pgbouncer.org/usage.html, https://www.pgbouncer.org/faq.html, https://www.postgresql.org/docs/current/runtime-config-connection.html, https://www.postgresql.org/docs/current/runtime-config-client.html, https://www.jstor.org/stable/167570, https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html, https://github.com/supabase/supavisorKeywords
connection pooling, PgBouncer, HikariCP, pool sizing, session pooling, transaction pooling, statement pooling, prepared statements, idle in transaction, Little's Law<!-- skill-graph-context:end -->
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.