Перейти к содержимому

База отказывает под нагрузкой - слишком много соединений

Сайт то открывается, то отдаёт ошибку базы, а в журнале лежит строка «(1040) Too many connections». База жива и в спокойные часы отвечает мгновенно, но на пике отказывает части посетителей. Разбираем причины по убыванию частоты, начиная с соотношения пределов базы и веб-сервера.

С чего начать

Смотрим предел соединений и пик занятых:

SHOW VARIABLES LIKE 'max_connections'; -- сколько соединений база принимает разом
SHOW STATUS LIKE 'Threads_connected'; -- занято прямо сейчас
SHOW STATUS LIKE 'Max_used_connections'; -- пик с момента последнего запуска базы
-- пик, равный пределу, означает отказы, даже если сейчас соединений немного

Пик, дошедший до предела, подтверждает диагноз без гадания по журналам. Дальше важен другой вопрос: заняты соединения работой или простаивают, и он делит все причины ровно пополам.

Смотрим, чем заняты соединения:

SHOW FULL PROCESSLIST;
-- Query и десятки секунд в поле Time: запросы висят и держат за собой очередь
-- Sleep у большинства строк: слоты заняты процессами, которые ничего не спрашивают

Долгие строки Query называют виновника сами, и дорога дальше идёт в план выполнения запроса. Толпа строк Sleep говорит о другом: соединений открыто больше, чем базе есть чем занять.

Сверяем число процессов PHP с пределом базы:

Окно терминала
grep -E 'pm.max_children|pm.max_requests' /etc/php-fpm.d/*.conf # сколько процессов поднимет PHP
mysql -e "SHOW VARIABLES LIKE 'max_connections'" # сколько соединений примет база
# каждый рабочий процесс держит своё соединение, поэтому предел базы обязан быть выше

Веб-сервер, поднимающий больше процессов, чем принимает база, отказывает сам себе. Запас считают с учётом cron, обмена с учётной системой и консольных скриптов, а не впритык к числу посетителей.

Правим предел так, чтобы правка пережила перезагрузку:

Окно терминала
grep max_connections /etc/mysql/conf.d/bvat.cnf # BitrixVM пишет сюда сам, по объёму памяти
printf '[mysqld]\nmax_connections = 200\n' > /etc/mysql/conf.d/z_bx_custom.cnf
systemctl restart mysqld # свои значения кладут только в z_bx_custom.cnf

Файл окружения перезаписывается по шаблону памяти, и правка в нём пропадает после ближайшей перезагрузки. Каждое соединение стоит оперативной памяти, поэтому предел поднимают вместе с замером её свободного остатка.

Проверяем режим соединения в настройках сайта:

// /bitrix/.settings.php, секция connections
'options' => 2, // 2 - отложенное соединение, оно устанавливается при первом запросе
// 1 - PERSISTENT: соединение не закрывается после запроса и держит слот базы занятым
$db = \Bitrix\Main\Application::getConnection('external'); // второе соединение того же хита

Отложенный режим - значение по умолчанию для базы, и менять его без причины не стоит. Второе подключение внутри одного хита удваивает расход слотов, поэтому запас по пределу считают сразу на все соединения проекта.

Причины

  1. Предел базы ниже, чем число рабочих процессов веб-сервера примерно 30% случаев

    ПризнакОтказы приходят ровно на пике, а в остальные часы сайт работает ровно.

    ПроверкаСравниваем число рабочих процессов PHP с пределом соединений в настройках самой базы.

    Что делатьПоднимаем предел выше числа процессов и оставляем запас на cron и обмен.

  2. Долгие запросы держат соединения, и очередь растёт лавиной примерно 25% случаев

    ПризнакОтказы начинаются не сразу, а через минуту после всплеска посещений.

    ПроверкаСмотрим список соединений: у висящих строк состояние Query и десятки секунд.

    Что делатьЧиним сам запрос индексом или переписыванием, а не поднятием предела соединений.

  3. Фоновые задания и обмен идут одновременно с пиком посещаемости примерно 20% случаев

    ПризнакОтказы повторяются по расписанию: каждый час или в ночь полной выгрузки.

    ПроверкаСверяем время отказов с расписанием cron и журналом обмена с учётной системой.

    Что делатьРазводим тяжёлые задания по времени и уносим их в часы спада нагрузки.

  4. Свой код открывает второе соединение и не закрывает его примерно 15% случаев

    ПризнакЗанятых соединений вдвое больше, чем рабочих процессов у веб-сервера.

    ПроверкаИщем в проекте свои подключения мимо секции connections и включённый режим PERSISTENT.

    Что делатьБерём подключение только через точку входа ядра и оставляем отложенный режим.

  5. Кэш выключен или сброшен целиком, и нагрузка ушла в базу примерно 10% случаев

    ПризнакОтказы начались сразу после чистки кэша или правки настроек кэширования.

    ПроверкаСмотрим, включено ли автокеширование компонентов и где лежит управляемый кэш.

    Что делатьВозвращаем кэш и переносим его в память, чтобы снять с базы повторные чтения.

Частые вопросы

Почему после перезагрузки max_connections снова стал маленьким?

Веб-окружение подставляет значение по шаблону, исходя из объёма памяти сервера, и перезаписывает свой файл настроек. Свои значения кладут в отдельный файл z_bx_custom.cnf.

Поможет ли просто выставить max_connections побольше?

Ненадолго. Каждое соединение занимает память, а очередь из долгих запросов при большем пределе просто станет длиннее и добьёт сервер по памяти.

Нужно ли включать постоянные соединения?

Только осознанно. Значение по умолчанию для базы - отложенное соединение: оно устанавливается при первом запросе, а постоянное держит слот занятым всё время жизни процесса.

Почему сайт открывается через раз, а не падает совсем?

Соединения освобождаются и занимаются заново. Кто успел попасть в свободный слот, увидит страницу, остальные получат ошибку подключения.

Как понять, что виноват обмен с 1С?

По времени: отказы совпадают с началом выгрузки. Обмен идёт длинными транзакциями и держит соединения дольше обычных страниц сайта.

Смежное

Первоисточник