PHP і вузьке місце «коннекти до бази»: пулери, проксі та практика
У багатьох стеках запити вже не «вбивці», а прод усе одно впирається в too many connections, обмеження слотів або підвисання після деплою. Часта причина — не повільний SQL, а арифметика з’єднань: модель запиту в PHP дає сплески connect + auth + TLS, а в бази є жорстка стеля паралельних backend’ів. Пулери та керовані проксі тримають невеликий стабільний пул серверних сесій за великою кількістю короткоживучих клієнтів PHP.
Пов’язані матеріали: БД під навантаженням · Sail: бази та Docker
Зміст
- Чому PHP посилює проблему
- У чому реальні обмеження
- Пулери та проксі між шарами
- PostgreSQL: PgBouncer
- MySQL / MariaDB: ProxySQL
- Керовані проксі в хмарі
- Інші пулери: PgCat, Odyssey, pgpool-II
- Laravel
- Що пулер не вилікує
- Чекліст
Чому PHP посилює проблему
Класичний PHP-FPM обробляє запит, звертається до сервісів, відповідає й звільняє ресурси запиту. Без persistent connections кожен запит до БД зазвичай відкриває TCP, автентифікується, за потреби TLS, потім виконує запити.
Під навантаженням:
pm.max_children— скільки процесів PHP одночасно на ноді; якщо більшість запитів б’є в БД, може знадобитися стільки ж одночасних сесій на воркер-машину.- Воркери черг (
queue:work, Horizon) — довгоживучі; кожен паралельний воркер часто тримає відкрите з’єднання під час джоба. - Горизонтальне масштабування множить усе: кілька нод із великим
max_childrenдають сотні потенційних сесій до БД.
Під час деплою та стрибків трафіку база бачить шторм коннектів. Навіть якщо max_connections формально достатньо, вдаряють пам’ять на backend (особливо Postgres) і вартість auth.
У чому реальні обмеження
- Глобальний
max_connections; зарезервовані слоти зменшують доступне застосунку. - RAM на кожне з’єднання — нескінченно піднімати ліміт небезпечно (OOM).
- Затримка handshake — TLS і перевірка пароля на кожен запит дорого в сумі.
- Thundering herd після рестарту — усі процеси PHP підключаються одночасно.
Рахуйте усі програми з SQL: веб, воркери, cron, CLI, адмінки.
Пулери та проксі між шарами
Пулер стоїть між PHP і СУБД. PHP підключається до пулера; пулер тримає менший пул справжніх з’єднань до Postgres/MySQL і перевикористовує їх між клієнтами.
Плюси: менше backend’ів на сервері БД, мультиплексування, рівніша поведінка при сплесках.
Мінуси: додатковий хоп, зміна семантики сесії залежно від режиму, ризик, що сам пулер стане новим вузьким місцем, якщо його не розмірити.
PostgreSQL: PgBouncer
| Режим | Ідея | Застосунок з Laravel |
|---|---|---|
| Session | Один серверний коннект на всю клієнтську сесію | Максимум сумісності: SET, LISTEN, temp tables, prepared statements як при прямому коннекті. Менший виграш за мультиплексування, якщо клієнт довгоживучий. |
| Transaction | Повернення серверного коннекту в пул після COMMIT/ROLLBACK | Сильне мультиплексування для коротких HTTP-запитів. Ламає сесію між транзакціями; іменовані prepared statements часто треба вимикати або емулювати на клієнті. |
| Statement | Після кожного оператора | Для ORM зазвичай не підходить. |
Prepared statements: при зміні фізичного коннекту іменовані prepare ламаються — типові обхідні шляхи: безіменні prepare, simple protocol, PDO::ATTR_EMULATE_PREPARES (за рекомендацією драйвера).
Передайте application_name для трасування в pg_stat_activity.
MySQL / MariaDB: ProxySQL
ProxySQL — проксі протоколу MySQL: маршрутизація, правила, read/write split, пул і налаштовуване мультиплексування. Допомагає обмежити backend-коннекти, поки до проксі підключаються сотні FPM-процесів.
MySQL Router, балансувальники в хмарі можуть не давати той самий рівень мультиплексування — перевіряйте документацію. MariaDB MaxScale — залежить від редакції та ліцензії.
Керовані проксі в хмарі
RDS Proxy, аналоги перед Aurora / Cloud SQL зазвичай дають пулинг і зручніший failover. Обмеження по сесії та prepared statements залишаються — читайте матрицю сумісності для вашого драйвера.
Інші пулери: PgCat, Odyssey, pgpool-II
- PgCat та Odyssey — пулери для Postgres; порівняйте режими пула, метрики та кварки драйверів із PgBouncer.
- pgpool-II — часто для реплікації та маршрутизації; важчий в експлуатації, якщо потрібен лише мультиплексинг.
Laravel
config/database.php— опції PDO під пулер (емуляція prepare тощо).- Read/write +
sticky— узгодьте з лагом реплік. - Octane / Swoole — довгоживучі воркери: обережно з стікістю з’єднання та транзакціями між запитами.
- Horizon —
concurrency × workersдодає стійке навантаження на пул. - У проді не залишайте інструменти, що розтягують транзакції.
У .env DB_HOST часто вказує на хост пулера:
DB_HOST=pgbouncer.internal
DB_PORT=6432
Розмір пулу на пулері зв’язуйте з реальною паралельністю запитів, а не лише з pm.max_children.
Що пулер не вилікує
- N+1 і відсутність індексів.
- Довгі транзакції (зовнішні HTTP всередині транзакції).
- Складні DDL / міграції — інколи потрібен прямий коннект до БД.
Чекліст
- Перелічити всі типи процесів із SQL.
- Порівняти з
max_connectionsі пам’яттю на з’єднання. - Обрати режим пула / правила під ORM і драйвер.
- Навантажувальний тест із prepared statements і потрібними сесійними фічами.
- Моніторити чергу на пулер і активні коннекти на СУБД.
Пулер «посередині» перетворює сотні коротких клієнтських сесій PHP на десятки реальних backend’ів на базі — саме ту форму, з якою OLTP зазвичай працює стабільніше.