“MySQL server has gone away”, first thing in the morning, on a worker that had no traffic all night. That was week one after we moved a project from FPM to RoadRunner.

For all these years FPM managed our database connections. Not with clever code. By dying. Process ends, connection closes, transaction rolls back, session variables reset. Nobody thought about connection lifecycle, because there was none.

Move the same code to RoadRunner or Swoole workers and the topic is back. A connection lives for hours now. Two things go wrong, and both showed up within a week.

First, staleness. MySQL drops an idle connection after wait_timeout. A worker that slept overnight wakes up, runs a query and gets “gone away”. The fix is dull: check the connection when the worker picks up a request, reconnect if it is dead. A cheap ping, or one retry on that specific error. Doctrine and most pools can do it, if you turn it on. Nobody turns it on before the first incident.

Second, leftover state, and this one is nastier. A request opens a transaction, throws halfway. FPM would have rolled it back at death. The long-lived worker keeps the connection, transaction still open, and hands it to the next request. Now someone else’s queries run inside a doomed transaction, locks are held, and the symptoms point everywhere except the cause. Cleanup between requests must be explicit: if a transaction is active when the request ends, roll it back and log it loudly. It is a bug upstream.

And do the arithmetic. Workers times connections per worker, across all services, plus crons, migrations and humans with consoles. Compare with max_connections. Dying FPM processes kept that number honest by accident. Persistent workers hold every slot they ever opened. If the numbers do not fit, that is what pgbouncer and ProxySQL are for.

Count your connections before the database counts them for you.