Что было. Веб-приложение на Django в Docker, база данных MariaDB в соседнем контейнере. Пока приложение под нагрузкой, всё хорошо. После нескольких минут простоя первый же запрос падает с OperationalError (2006, 'Server has gone away'). Перезапуск контейнера помогает до следующей паузы.
Почему. Сервер базы данных закрывает соединения, которые молчат дольше wait_timeout. Приложение об этом не знает: оно держит соединение открытым в пуле и при первом запросе пишет в уже мёртвый сокет. Ответ: 2006. Разработчики закрыли обращение как «не ошибка приложения»: это вопрос согласования настроек. Отдельный случай той же ошибки: запрос или результат больше max_allowed_packet, тогда сервер обрывает соединение сразу, без простоя.
Как выяснить, какой из двух вариантов.
- Ошибка появляется только после паузы: таймаут простоя.
- Ошибка на конкретном большом запросе или импорте дампа: размер пакета. В журнале сервера будет упоминание
packet. - Значения на сервере:
SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'max_allowed_packet';. Образы контейнеров часто идут с агрессивными таймаутами.
Что настроить.
- Со стороны приложения (предпочтительно): время жизни соединения в пуле меньше серверного таймаута. В Django это
CONN_MAX_AGE, в SQLAlchemypool_recycleплюсpool_pre_ping=True, в Node-драйверахidleTimeoutи проверка при выдаче из пула. Соединение старше порога переоткрывается заранее, а не по ошибке. - Со стороны сервера: увеличить
wait_timeoutдля длительных фоновых задач (например, до 3600) иmax_allowed_packetпод размер данных. Менять в конфиге сервера, а не только в сессии. - Автопереподключение в драйвере как страховка, но не как единственная мера: первый запрос после разрыва всё равно потеряется, если драйвер не повторяет его сам.
Про Docker. Отдельная тема из обсуждений: сетевые таймауты между контейнерами при неактивности (например, если между ними стоит прокси или включён IPv6 без маршрута). Проверить docker network inspect и попробовать отключить IPv6 в сети compose, если ошибка возникает даже при коротких простоях.
Причины ошибки перечислены в документации MySQL.