Doctrine DBAL connection reuse limits with PDO persistent connections
0 reputation · 07 Aug 2020, 12:57 UTC
0 reputation · 07 Aug 2020, 12:57 UTC
Clarify the documented connection management behavior of Doctrine DBAL and ORM in a typical PHP request lifecycle and the operational limits that apply to connection reuse.
Doctrine DBAL does not implement an application-level connection pool. A DBAL Connection wraps a single PDO instance per request and the ORM typically uses one EntityManager per request. Connection exhaustion is therefore external to Doctrine and driven by web server worker limits, PHP-FPM process count, database max_connections, and PDO persistent connection configuration. Within a single request, holding the connection for a long time, long-running transactions, or opening additional connections instead of reusing the shared DBAL Connection can increase pressure.
An unresolved design decision is whether to enable PDO persistent connections for Doctrine, given the trade-off between reduced handshake overhead and risks of stale connections and higher connection accumulation across workers.
35275 reputation · 07 Aug 2020, 17:55 UTC
Connection holds a PDO object that is only instantiated when the first query is executed (or when connect() is called explicitly). After that, every call to executeQuery, prepare, or any other method that requires a database connection will use the same PDO instance until the request ends or the connection is closed manually.PDO instance alive across requests within the same worker process. Doctrine itself does not control the number of persistent connections; the limits are governed by:
pdo_mysql.max_persistent (or the equivalent for the driver) – maximum number of persistent connections per PHP worker.
max_lifetime of a persistent connection.
max_connections setting – each persistent connection counts as a live DB session even when idle.
workers × max_persistent. If this product exceeds the database’s max_connections, new requests will block or fail.PDO instance per request. Therefore, for N concurrent requests handled by M workers, the database may see up to N × M concurrent connections (minus those that are idle or reused). This can quickly exhaust the database’s max_connections if many workers are active or if persistent connections are left open across requests.EntityManager in code that may never hit the database; the underlying PDO stays closed until needed.PDO instance is reused for all subsequent operations. Closing the EntityManager does not close the PDO until the request terminates.PDO::ATTR_PERSISTENT is set, PHP keeps the socket open across requests. Doctrine merely passes the flag to PDO; it does not track or limit the number of persistent sockets.workers = 10 and pdo_mysql.max_persistent = 5, you could have up to 50 persistent connections live at once.max_connections – This is the hard cap. If the sum of active connections across all workers exceeds this value, new connection attempts will fail with “Too many connections” errors.SHOW PROCESSLIST on MySQL) after a burst of requests. Count how many distinct connections belong to your application’s user.
max_connections.
Connection instances and confirm that each opens its own PDO (persistent or not) by checking the process list or using Doctrine’s debug tools.
max_connections.
Connection objects in a single request unless you have a specific need; it will increase the number of active connections.
To fine‑tune your environment, could you share the current values of pdo_mysql.max_persistent and the database’s max_connections for your deployment?
Use comments to ask for clarification. Post a solution as an answer.
6,510 reputation · 07 Aug 2020, 21:59 UTC
With PDO::ATTR_PERSISTENT enabled, each PHP-FPM worker process maintains its own persistent connection handle. The database connection count therefore correlates directly with pm.max_children (or the active worker count under dynamic process management), not with concurrent request count. An idle worker still holds a database connection, which can saturate max_connections during low-traffic periods if the worker pool is large.
A practical mitigation is to configure PHP-FPM with pm.max_requests to recycle workers periodically, forcing persistent handles to reset. Alternatively, disable persistent connections and rely on the database's connection pooling (e.g., PgBouncer for PostgreSQL) where connection reuse is managed at the infrastructure layer with explicit idle timeouts.