Magento 2 Database Connection Pooling: Reduce Overhead and Scale Faster

php dev.to

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_connections is 151. Exceed it and new requests receive Too many connections errors.

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:

  1. PHP-FPM persistent connections — simple, built into PHP
  2. ProxySQL — a dedicated MySQL proxy with true connection pooling
  3. MySQL connection management via wait_timeout and 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
        ],
    ],
],
Enter fullscreen mode Exit fullscreen mode

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 SET statements 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Point Magento at ProxySQL

Update app/etc/env.php:

'host' => '127.0.0.1',
'port' => '6033',  // ProxySQL port
Enter fullscreen mode Exit fullscreen mode

What ProxySQL adds on top

  • Connection multiplexing — 500 PHP workers sharing 50 MySQL connections
  • Read/write split — route SELECT queries 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
Enter fullscreen mode Exit fullscreen mode

max_connections

Size this based on your actual peak concurrency, not guesswork:

max_connections = 200
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

ProxySQL dashboard query

SELECT hostgroup, srv_host, status, ConnUsed, ConnFree, ConnOK, ConnERR
FROM stats_mysql_connection_pool;
Enter fullscreen mode Exit fullscreen mode

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.

Source: dev.to

arrow_back Back to Tutorials