Скачать CV
Назад к бизнес-кейсам

Соединения

Спящие хендлы PDO, которые забили MySQL

ATTR_PERSISTENT на php-fpm, Horizon и schedule. max_connections 151. Too many connections в полдень.

MySQL около обеда начал отказывать в соединениях. Листинг был в порядке. SHOW PROCESSLIST почти весь Sleep. Сто сорок хендлов от php-fpm с запросом двадцатиминутной давности. Простаивающие воркеры Horizon держали ещё восемь. wait_timeout 28800, дефолт. Persistent PDO делает что написано. Воркер держит TCP всю жизнь.

pm.max_children был 8. Это восемь соединений, если у каждого воркера одно. Перезапуск по pm.max_requests открывает ещё одно, если старый persistent-хендл ещё в пуле процесса. Horizon --max-processes и контейнер schedule ходили в тот же DSN с ATTR_PERSISTENT. Compose один раз масштабировал php под всплеск и не вернул назад. 8 стало 32. Дефолт MySQL 151. Спящие строки остались от прошлого scale-out, пока воркеры не умерли по max_requests.

Проблема была в persistent PDO на php-fpm, Horizon и schedule, который забил max_connections 151 строками Sleep. Редакторы и покупатели ловили too many connections в полдень. Нужны короткие хендлы, wait_timeout 60 и те же 151 как жёсткий потолок, который не прячу.

Три сервиса Compose, один max_connections. Persistent значит слот занят, пока Sleep.
Три сервиса Compose, один max_connections. Persistent значит слот занят, пока Sleep.

Короткая жизнь

ATTR_PERSISTENT выключен. Каждый запрос открывает, читает, закрывает. Восемь воркеров в обед это восемь соединений, не сто сорок. Horizon держит соединение на процесс и закрывает, когда воркер умирает по --memory. wait_timeout 60. Лишний Sleep умирает до вечернего отказа.

Простаивающий TCP не бесплатный. Это слот. wait_timeout помогает, только если PDO не persistent.
Простаивающий TCP не бесплатный. Это слот. wait_timeout помогает, только если PDO не persistent.
  • Memcached и Redis снимают листинг с MySQL. Оставшиеся запросы дешёвые. Новое соединение на запрос дешевле полного processlist.
  • max_connections остался 151. Поднять его значит спрятать Sleep до следующего scale-out.
  • healthcheck контейнера mysql ходит отдельным пользователем. Он не из этих 140.

Threads_connected в полдень сидит около числа занятых воркеров. Too many connections ушло вместе с persistent. Каталогу не нужно было больше MySQL. Нужно было меньше хендлов, которые ничего не делали.

Какой опыт из этого

SHOW PROCESSLIST полный Sleep это не проблема запроса. Это ATTR_PERSISTENT плюс длинный wait_timeout.

Scale-out без scale-in оставляет хендлы до max_requests. Persistent это множит.

Поднятие max_connections прячет график. Держу 151 и закрываю TCP.

Назад к бизнес-кейсам