PHP и тесният участък „връзки към базата“: пулери, прокси и практика

В много стекове заявките вече не са „убийци“, а продукшънът все пак удря too many connections, резервирани слотове или зависвания след деплой. Честата причина не е бавен SQL, а аритметиката на връзките: моделът на заявка в PHP създава пикове connect + auth + TLS, а базата има твърд таван на паралелни backend-ове. Пулерите и управляваните прокси държат малък стабилен пул сървърни сесии зад много краткотрайни PHP клиенти.

Свързани материали: Бази под натоварване · Sail: бази и Docker

Съдържание


Защо PHP влошава проблема

Класическият PHP-FPM обработва заявка, вика услуги, връща отговор и освобождава ресурсите на заявката. Без persistent connections всяка заявка към базата обикновено отваря TCP, удостоверява се, при нужда TLS, после изпълнява SQL.

Под натоварване:

  • pm.max_children определя едновременните PHP процеси на машината; ако повечето заявки удрят базата, може да са нужни толкова паралелни сесии на възел.
  • Работници на опашки (queue:work, Horizon) са дълготрайни — всеки паралелен работник често държи отворена връзка по време на задачата.
  • Хоризонтално мащабиране умножава всичко.

След рестарт и при скокове на трафик базата вижда буря от връзки. Дори max_connections да стига, удрят памет на backend (особено Postgres) и разход за удостоверяване.


Какви са реалните лимити

  • Глобален max_connections; резервирани слотове за админ/репликация намаляват капацитета за приложението.
  • RAM на връзка — безкрайно вдигане на лимита води до OOM.
  • Закъснение при свързване — 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 се чупят — клиентска емулация (PDO::ATTR_EMULATE_PREPARES) или безименни/simple протокол според драйвера.

Подавайте application_name за проследяване в pg_stat_activity.


MySQL / MariaDB: ProxySQL

ProxySQL — прокси на MySQL протокол: маршрутизация, правила, read/write split, пул и настройваемо мультиплексиране. Помага да се ограничат backend връзките, докато стотици FPM деца се вързват към проксито.

MySQL Router и облачни балансьори не винаги мультиплексират по същия начин — проверете документацията. MariaDB MaxScale — според издание и лиценз.


Управлявани облачни прокси

RDS Proxy и аналози предлагат пулинг и по-гладък failover. Ограниченията за сесия и prepared statements остават — вижте матрицата за вашия драйвер.

Други пулери: PgCat, Odyssey, pgpool-II

  • PgCat и Odyssey — пулери за Postgres; сравнете режими, метрики и особености на драйвера с PgBouncer.
  • pgpool-II — често за репликация и маршрутизация; по-тежък за експлоатация, ако ви трябва само мультиплексиране.

Laravel

  • config/database.php — PDO опции според пулера.
  • Read/write и sticky — съгласувайте с лаг на репликите.
  • Octane / Swoole — дълготрайни работници: внимавайте с транзакции и състояние между заявки.
  • Horizonconcurrency × workers = постоянна натовареност на пула.
  • Инструменти за дебъг не оставяйте в прод да разтягат транзакции.

Често DB_HOST сочи към пулера:

DB_HOST=pgbouncer.internal
DB_PORT=6432

Оразмерете пула според реалната паралелност на заявки, не само според pm.max_children.


Какво пулерът не лекува

  • N+1 и липсващи индекси.
  • Дълги транзакции (външни HTTP вътре в транзакция).
  • Тежки DDL / миграции — понякога е нужен директен път към базата.

Чеклист

  1. Инвентаризация на всички процеси с SQL.
  2. Сравнение с max_connections и памет на връзка.
  3. Избор на режим / правила според ORM и драйвер.
  4. Натоварване с prepared statements и нужни сесийни функции.
  5. Мониторинг на опашката пред пулера и активните връзки към СУБД.

Пулерът „по средата“ превръща стотици кратки PHP сесии в десетки реални backend-ове на базата — формата, в която повечето OLTP системи работят най-предсказуемо.