Every Magento 2 page request that hits MySQL must first establish a database connection. On a busy store this sounds innocent, but at scale it becomes one of the biggest hidden bottlenecks: thousands of short-lived connections opening and closing per minute, each consuming CPU time, memory on the MySQL server, and precious milliseconds on the client side.
Database connection pooling solves this by keeping a set of connections open and reusing them across requests. In this post we'll cover exactly what that means for Magento 2, how PHP's connection handling works under the hood, and the practical steps you can take today to reduce connection overhead and improve throughput.
Why Connection Overhead Matters
When PHP opens a new MySQL connection it performs a TCP handshake, authenticates credentials, negotiates character sets, and allocates resources on both sides. On a modern server this takes roughly 1–5 ms per connection. That may sound trivial, but consider:
- A Magento page that fires 20 queries needs only one connection — yet without pooling it creates and destroys that connection on every single request.
- Under a flash sale with 500 concurrent users, you could have 500 simultaneous connection attempts hitting MySQL at the same instant.
- MySQL's default
max_connectionsis 151. Exceed it and new requests receiveToo many connectionserrors.
The result: degraded performance exactly when you need it most.
How Magento 2 Handles Connections
Magento 2 uses its own Magento\Framework\DB\Adapter\Pdo\Mysql which wraps PHP's PDO extension. By default, PDO opens a new connection per request and closes it when PHP's process ends. There is no built-in connection pool at the application level.
However, there are three layers where you can introduce pooling:
- PHP-FPM persistent connections — simple, built into PHP
- ProxySQL — a dedicated MySQL proxy with true connection pooling
-
MySQL connection management via
wait_timeoutand pool sizing
Let's look at each in detail.
Option 1: PHP Persistent Connections
PHP's PDO supports persistent connections via the PDO::ATTR_PERSISTENT attribute. When enabled, PHP-FPM reuses an existing connection from the worker process instead of opening a new one.
How to enable it in Magento 2
Open app/etc/env.php and add persistent to your database configuration:
'db' => [
'table_prefix' => '',
'connection' => [
'default' => [
'host' => 'localhost',
'dbname' => 'magento',
'username' => 'magento',
'password' => 'secret',
'model' => 'mysql4',
'engine' => 'innodb',
'initStatements' => 'SET NAMES utf8;',
'active' => '1',
'persistent' => true, // <-- add this
],
],
],
The trade-offs
Pros:
- Zero infrastructure change required
- Connection overhead drops significantly for long-running PHP-FPM workers
Cons:
- Each PHP-FPM worker holds one persistent connection. With 50 workers you have 50 permanent connections to MySQL.
- Transactions left open by a crashed worker can block subsequent requests on that same worker.
- Not suitable for multi-tenant setups or setups with frequent
SETstatements that alter session state.
Verdict: Good for small-to-medium stores on a single server. Not recommended for large clusters without careful testing.
Option 2: ProxySQL — True Connection Pooling
ProxySQL is an open-source, high-performance MySQL proxy. It sits between Magento and MySQL and maintains a pool of backend connections that it multiplexes across incoming application connections.
Architecture
Magento (PHP-FPM) → ProxySQL (:6033) → MySQL (:3306)
Magento connects to ProxySQL on port 6033. ProxySQL holds, say, 50 persistent connections to MySQL and distributes Magento's queries across them — even when 500 PHP workers are making requests simultaneously.
Installing ProxySQL on Ubuntu
wget https://github.com/sysown/proxysql/releases/latest/download/proxysql_2.7.1-ubuntu24_amd64.deb
dpkg -i proxysql_2.7.1-ubuntu24_amd64.deb
systemctl enable proxysql && systemctl start proxysql
Basic ProxySQL configuration
-- Connect to ProxySQL admin
mysql -u admin -padmin -h 127.0.0.1 -P6032
-- Add your MySQL backend
INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (1, '127.0.0.1', 3306);
-- Add Magento user
INSERT INTO mysql_users(username, password, default_hostgroup) VALUES ('magento', 'secret', 1);
-- Configure connection pool size
UPDATE global_variables SET variable_value='50' WHERE variable_name='mysql-max_connections';
UPDATE global_variables SET variable_value='12000' WHERE variable_name='mysql-wait_timeout';
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;
LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;
Point Magento at ProxySQL
Update app/etc/env.php:
'host' => '127.0.0.1',
'port' => '6033', // ProxySQL port
What ProxySQL adds on top
- Connection multiplexing — 500 PHP workers sharing 50 MySQL connections
-
Read/write split — route
SELECTqueries to replicas automatically - Query routing rules — send heavy analytical queries to a separate backend
- Query mirroring — test new backend without affecting production
- Statistics — per-query latency, error rates, connection pool stats
Performance impact
In benchmarks on a Magento 2 store with ~300 concurrent users:
| Metric | Without ProxySQL | With ProxySQL |
|---|---|---|
| Avg response time | 420 ms | 310 ms |
MySQL SHOW PROCESSLIST connections |
280+ | 48 |
| Requests/sec | 180 | 260 |
Connection count dropped by 83% and throughput increased by 44%.
Option 3: MySQL-Side Tuning
Even without a proxy, proper MySQL configuration reduces the pain of many connections:
wait_timeout and interactive_timeout
By default MySQL keeps idle connections open for 8 hours (wait_timeout = 28800). Lower this to reclaim resources:
# /etc/mysql/mysql.conf.d/mysqld.cnf
wait_timeout = 60
interactive_timeout = 60
max_connections
Size this based on your actual peak concurrency, not guesswork:
max_connections = 200
Too low: connection errors under load. Too high: MySQL allocates memory for each potential connection upfront, wasting RAM.
Thread pool plugin
On MySQL Enterprise or Percona Server, enable the thread pool to limit simultaneous query execution even when many connections are open:
plugin-load-add = thread_pool.so
thread_pool_size = 16 # typically equal to CPU cores
This dramatically reduces context-switching overhead under burst traffic.
Combining the Layers
The best production setup layers all three approaches:
PHP-FPM (persistent=false) → ProxySQL (pool of 50) → MySQL (max_connections=100, thread_pool)
Use persistent connections only if you cannot deploy ProxySQL. If you have ProxySQL, disable PHP-level persistence — ProxySQL does it better and more safely.
Monitoring Connection Health
Always instrument after making changes.
MySQL connection stats
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Connection_errors%';
SHOW STATUS LIKE 'Max_used_connections';
ProxySQL dashboard query
SELECT hostgroup, srv_host, status, ConnUsed, ConnFree, ConnOK, ConnERR
FROM stats_mysql_connection_pool;
New Relic / Datadog
If you use APM, watch the Database connection wait time metric. A healthy store should spend < 1 ms waiting for a connection. Anything above 5 ms means your pool is undersized or MySQL is overloaded.
Practical Recommendations
| Store Size | Recommendation |
|---|---|
| < 50 req/s | PHP persistent connections, tune wait_timeout
|
| 50–300 req/s | ProxySQL with pool size 20–50 |
| 300+ req/s | ProxySQL + read replicas + thread pool |
Start simple. Enable persistent connections first, benchmark with a tool like k6 or Apache Bench, then add ProxySQL if you still see connection contention.
Summary
Database connection overhead is a silent killer of Magento 2 performance at scale. The fix isn't complicated, but it requires understanding the layers:
- PHP persistent connections give quick wins with no infrastructure changes
- ProxySQL is the production-grade solution that truly pools and multiplexes connections
-
MySQL tuning (
wait_timeout,max_connections, thread pool) reduces server-side pressure
Pick the right level for your current scale, instrument properly, and you'll find that response times drop and MySQL stays calm even during your busiest sales days.