Configure
Pooling
How StreamQL shares server connections between clients, and what it does when a client needs more.
Pooling modes#
| Mode | A server connection is held | Use it for |
|---|---|---|
transaction (default) | for one transaction | Most applications. The most sharing. |
session | for the whole client session | Clients that depend on session state or session-level features. |
statement | for one statement | Autocommit workloads. A BEGIN is rejected with error 0A000. |
Set the default with defaults.mode or --pool-mode, and override it per pool with [[pool]] mode. Changing a mode takes effect after a drain.
Session state and pinning#
In transaction mode a client may land on a different server connection for each transaction. StreamQL tracks session state so the client does not notice:
SET,RESETandRESET ALL, startup parameters and theoptionsparameter are tracked and replayed as a minimal diff on the connection the client gets next.- When a server connection goes back to the pool it is reset with
ROLLBACKandDISCARD ALL. - Some statements tie a client to one server connection:
LISTEN, advisory locks, temporary objects,WITH HOLDcursors, SQLPREPARE, and calls to functions outside the built-in allowlist. The session is pinned and the reason is recorded, for examplePOOL_PINNED_ADVISORY_LOCK. session.pin_unknown_functionscontrols the last case, andsession.attested_functionslists functions you declare free of session effects.defaults.max_pinned_fraction(0.9) caps the share of a pool that may be pinned.
Prepared statements#
Prepared statements work across pooled backends in transaction mode. Each client has its own statement namespace, and each backend keeps the statements it holds, up to defaults.statements_per_backend (500) with least-recently-used eviction. A Parse between transactions does not wait for a server connection: the statement is prepared lazily at Bind or Describe, so under contention a parse error surfaces there.
Queueing and deadlines#
When every server connection of a pool is busy, clients wait in a queue. After queue_timeout_ms (5000 by default) the client receives error 53000 and its session is kept. The wait is recorded as pool wait, which the console shows next to database time.
Reserve pools#
reserve = { size, after_wait_ms, max_slowdown } lets a pool open extra server connections, but only after a client has waited after_wait_ms. The reserve is withheld while recent latency is above max_slowdown times its baseline or while a connection attempt recently failed, and it is closed again on release when nobody is waiting.
Per-tenant share#
tenant = { key, share } caps the fraction of max_size one tenant may hold. The tenant comes from a tracked setting named by key, such as app.tenant_id. Excess leases queue and then fail with 53000.
Connection budget and limits#
defaults.max_sizebounds server connections per database and role.max_db_connectionsbounds server connections to one database across all users. Idle backends of sibling pools are evicted to make room.limits.max_user_connectionsbounds one backend role across all databases, andlimits.max_user_clients/max_db_clientsbound clients; both fail with53300at login.[budget]declares how many connections this member may hold against PostgreSQLmax_connections. In a fleet the primary hands each worker a share of it.
Data-plane engine#
The default engine is tokio. The per-core reactor engine is opt-in with dataplane.engine = "reactor" or STREAMQL_DATAPLANE__ENGINE=reactor. It needs plain-TCP upstreams: a client that negotiated TLS, or a pool whose servers use TLS, stays on the tokio engine with a reason code. All pooling features run on both engines, and setting the engine back to tokio reverts completely.
STREAMQL_DATAPLANE__ENGINE=reactor
STREAMQL_UPSTREAM__SSLMODE=disablePool-only mode#
Everything on this page also holds when you run StreamQL without the console. Set admin.enabled = false (or pass --no-admin) and insights.enabled = false, or use the pool-only build. The pooling modes, session-state safety, queues and limits do not change; the console, admin API and /metrics are not available. See Choose an edition.
Graceful drain#
On SIGTERM, or POST /api/v1/drain, a node turns readiness off, keeps accepting for drain.endpoint_propagation_ms so load balancers notice, stops accepting, gives open transactions drain.grace_s to finish, and closes remaining clients with error 57P01.