Спящие хендлы 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 как жёсткий потолок, который не прячу.
Короткая жизнь
ATTR_PERSISTENT выключен. Каждый запрос открывает, читает, закрывает. Восемь воркеров в обед это восемь соединений, не сто сорок. Horizon держит соединение на процесс и закрывает, когда воркер умирает по --memory. wait_timeout 60. Лишний Sleep умирает до вечернего отказа.
- 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.
