skip to content

Why is a PHP persistent database connection under PHP-FPM not a connection pool, and how many connections does each worker hold?

level: middleimportance: should knowfreq 40%

answer

  1. one request per worker at a time
  2. per-process cache, keyed by parameters
  3. one per worker per distinct key
  4. no borrow, return or global cap
  5. pooling needs a proxy outside PHP

basics

~20 s

Each PHP-FPM worker serves one request at a time and keeps its own persistent connection per distinct key, so nothing is shared or borrowed. It is a per-process cache: one connection per worker per DSN and credentials, idle or busy.

solid answer

~40 s

PHP-FPM workers are separate processes that each run one request at a time and share nothing. A persistent connection lives in the worker's own persistent list, so the worker's next request with the same key reuses it, but no other worker can. A pool is shared by concurrent work, lends connections out, takes them back and caps the total; PHP's persistence does none of that, which makes it a per-process cache. Each worker holds one connection per distinct key — PDO's key is DSN, user and password — so a primary plus a replica means two per worker, idle or not. Long-lived runtimes such as a Java service can pool in-process because one process serves many requests concurrently; with FPM, real pooling needs a proxy between PHP and the database.

go deeper

for a junior

Recall that each PHP-FPM worker keeps its own persistent connection, and other workers cannot use it.

for a middle

Explain why share-nothing, one-request-per-worker processes cannot form a pool, and how the lookup key sets the count per worker.

for a senior

Predict connection counts from workers and distinct keys, and spot the shared-handle surprise when two PDO objects use the same key.

for a principal

Judge when to move pooling out of PHP into a proxy, or to a long-running runtime, instead of relying on per-worker persistence.

## Share-nothing, one request per worker Under PHP-FPM, requests are served by a fixed set of **worker processes**. Each worker handles **one request at a time**, from start to finish, and PHP discards the request's variables and objects when it ends. Nothing a script creates is shared with other workers, and nothing but a few engine-level caches survives into the worker's next request. Persistent connections are one of those engine-level survivors. When a script opens a persistent connection, PHP stores it in the worker process's own **persistent list**, keyed by the connection parameters. The worker's next request that asks for the same key gets the same connection back. ## Why that is not a pool A connection **pool** is a set of open connections shared by concurrent units of work: code borrows a connection, uses it and returns it, and the pool bounds the total and makes waiting callers queue. PHP's persistent connections have none of those parts: | Pool feature | PHP persistent connection | |---|---| | Shared by concurrent requests | no — one worker, one request at a time | | Borrow and return per operation | no — kept for the whole request | | A global size limit | no — one per worker per key, however many workers exist | | Waiting when all are busy | no — a worker without one simply opens one | | Sharing across processes | no — each process has its own list | The result is best described as a **per-process connection cache**. With 40 FPM workers and one set of credentials, the database sees up to 40 connections, one per worker that has ever connected, whether those workers are busy or idle. ## How many connections one worker holds Usually one, but it is **one per distinct key**: - PDO keys on DSN, username and password (plus an optional string from `ATTR_PERSISTENT`). An app that uses a read replica and a primary holds two per worker. - A second `new PDO(...)` with the **same** key in the same request does not open a second connection: PDO's persistent lookup returns the same handle, so both objects share one database session, including an open transaction. - mysqli keeps a stack of free links per key. Two `p:` connections with the same parameters open at the same time in one request become two links, and both return to the worker's free stack afterwards. ## Where a real pool lives in PHP deployments Because the PHP process model cannot share connections between workers, pooling is done **outside** PHP when it is needed: a pooling proxy runs between the workers and the database, accepts many cheap client connections and multiplexes them over fewer server connections. How such poolers size themselves and which modes they offer is a database-side topic; from PHP's side the proxy looks like an ordinary server in the DSN, and PHP's own persistence is usually turned off in front of it. ## The contrast interviewers ask for The question is often phrased against a long-lived runtime. In a Java or Node.js service, one process serves many requests **concurrently**, so an in-process pool object hands out connections to whichever request needs one and takes them back. PHP-FPM gets its concurrency from many processes instead of from one process, so there is nothing in-process to share: a worker never has two requests to split a connection between. Persistent connections therefore save **connection setup time** and nothing else. ## What the worker's next request inherits Because the cached object is the same live connection, the next request in the worker inherits more than a socket: - the authenticated session, so no new login handshake; - any session-level settings the previous request changed; - the server-side resources it left behind, such as temporary tables. That inheritance is why persistence saves time, and also why it needs care. ## Consequences to state in an interview 1. Connection count tracks the **number of workers** (times distinct keys), not the number of concurrent queries. 2. Idle workers keep their connections until the worker exits or the database server closes them for inactivity. 3. Worker restarts — a reload, or a worker recycled after its maximum number of requests — close their persistent connections, and the replacement worker opens new ones on demand. 4. Worker runtimes that keep a PHP application in memory and serve many requests from one process can hold a real in-process pool; that is a different execution model from classic FPM.

  • What happens when one request creates two PDO objects with the same persistent DSN, user and password?
    PDO finds the existing handle under the same key and reuses it, so both objects share one database connection and one session. A transaction started through one object is the transaction the other sees. A string value for `PDO::ATTR_PERSISTENT` changes the key and gives the second object its own connection.
  • Why do persistent connections sometimes disappear from the database's process list without the app doing anything?
    They close whenever the worker process ends, for example on an FPM reload or when a worker is recycled after its request limit, and the database server may drop them after its idle timeout. PDO's MySQL driver pings before reusing a handle and reconnects if the ping fails, so the application normally just sees a fresh connection.

Each FPM worker is a cashier with a personal phone line to the warehouse: the line stays connected between customers, but no other cashier can use it, and hiring more cashiers means more phone lines, not more sharing.

saying these in an interview costs you the question

  • Calling PHP persistent connections a connection pool shared between requests.
  • Believing idle FPM workers release their persistent connections.
  • Expecting the number of connections to track concurrent queries.
  • Assuming two PDO objects with the same key get separate connections.
  • Thinking one worker can serve two requests on one connection at once.