Ubuntu 24.04: MySQL “server has gone away” — правильно виправляємо таймаути та обмеження пакетів (випадок №36)

Було корисно?

«MySQL server has gone away» — це еквівалент того, коли колега зник під час наради: у кімнаті стає тихо, всі дивляться в ноутбуки, і раптом це вже ваша проблема.

На Ubuntu 24.04 зазвичай це не «баг MySQL». Це невідповідність таймаутів, обмеження розміру пакета, проксі, який «допомагає», або рестарт mysqld, якого ви не помітили, бо додаток логував лише «SQLSTATE[HY000]». Ми виправимо це правильно: швидко знайдемо вузьке місце, доведемо корінну причину командами та налаштуємо потрібний шар, не перетворюючи вашу базу на невгамовну пам’ять-піньяту.

Що насправді означає «server has gone away» (і чого це не означає)

Фраза вводить в оману своєю дружелюбністю. Вона ніби натякає, що сервер вийшов за кавою й скоро повернеться. Насправді це означає: клієнт спробував використати з’єднання з MySQL, а інша сторона вже не була на місці, або потік протоколу зламався так, що клієнт не може відновитися.

Поширені варіанти помилки

  • Помилка 2006: MySQL server has gone away (клієнт помічає, що сокет мертвий або не може надіслати).
  • Помилка 2013: Lost connection to MySQL server during query (запит почався, з’єднання обірвалося «по дорозі»).
  • SQLSTATE[HY000] оболонки від драйверів додатків (PDO, mysqli, JDBC), які приховують номер помилки та контекст.

Чого це не є

Це не обов’язково «перевантаження бази даних», і це не вирішується «просто збільшіть усі таймаути». Бездумне збільшення таймаутів приховує витоки (витоки з’єднань, транзакцій), поки одного дня не скінчиться пам’ять або потоки.

Існує лише кілька основних категорій причин:

  1. Мертве просте з’єднання, яке вбив MySQL (wait_timeout) або проксі/ЛБ/NAT посередині.
  2. Занадто великий пакет: MySQL відхиляє його (max_allowed_packet) або проксі відкидає, або клієнт досягає власного ліміту.
  3. Рестарт/крах сервера: mysqld помер, був убитий OOM або systemd його перезапустив.
  4. Мережа: TCP reset-и, дивності MTU, таймаути conntrack, несумісність keepalive.
  5. Запит перевищив таймаути (серверні net_read_timeout/net_write_timeout, таймаути проксі, клієнтські таймаути).

Цитата, яку корисно пам’ятати: Надія — не стратегія — часто цитують в операційних колах; це ідея, яку варто застосувати при виправленні баз даних.

Жарт №1: Якщо ваше рішення — «встановити таймаути на 24 години», ви проблему не вирішили, ви просто перенесли її на зміну наступної доби.

Цікаві факти та історія, якими можна скористатися

  • Факт 1: «MySQL server has gone away» існує з часів ранніх клієнтських бібліотек MySQL; це повідомлення на боці клієнта, а не рядок у лозі сервера.
  • Факт 2: Класичний дефолт wait_timeout історично був 8 годин на багатьох інсталяціях; сучасні середовища з проксі часто потребують значно коротших таймаутів плюс правильного пулінгу.
  • Факт 3: Протокол пакетовий; великі заяви й великі рядки навантажують як max_allowed_packet, так і шляхи виділення пам’яті.
  • Факт 4: NAT-шлюзи й stateful фаєрволи часто мають таймаути простих TCP значно менші за дефолти MySQL; вони вб’ють «здорові» ідл-сокети, не сказавши ні одному кінцю.
  • Факт 5: За замовчуванням Linux TCP keepalive консервативні (години). У хмарних мережах це фактично «ніколи», отже мертві з’єднання можуть висіти поки наступна операція не впаде.
  • Факт 6: Багато драйверів (особливо старі) не роблять безпечного автоматичного перепідключення, бо перепідключення всередині транзакції — це мінне поле по коректності.
  • Факт 7: max_allowed_packet існує і на сервері, і на клієнті; підвищення тільки однієї сторони може не вирішити проблему.
  • Факт 8: Частий сучасний винуватець — не MySQL взагалі, а шар L7: пул підключень додатку з «вічним» життям тримає сокети, які проксі обривають.
  • Факт 9: Помилка часто з’являється після деплоїв, бо деплої змінюють поведінку підключень: більше паралелізму, більші payload-и, інша логіка повторних спроб.

Швидкий план діагностики (перший/другий/третій)

Перший: визначте, чи mysqld перезапускався або впав

Якщо mysqld перезапустився близько часу помилок — не жіться за обмеження пакетів. Ваші клієнти втратили з’єднання, бо сервер зник. Дізнайтеся, чому він перезапустився.

  1. Перевірте systemd журнал на предмет перезапусків mysqld та кодів виходу.
  2. Перегляньте лог помилок MySQL на маркери краху та відновлення InnoDB.
  3. Перегляньте лог OOM killer та тиск пам’яті.

Другий: вирішіть, чи це таймаути простою, чи обрив під час запиту

«Gone away» відразу після періоду бездіяльності вказує на wait_timeout або на мережеві/проксі таймаути. «Lost connection during query» вказує на довгі запити, таймаути проксі або серверні net-таймаути.

  1. Порівняйте часи помилок із «періодами бездіяльності» в логах додатку.
  2. Перевірте тенденції Aborted_clients та Aborted_connects в MySQL.
  3. Перегляньте налаштування таймаутів проксі/ЛБ, якщо вони є.

Третій: протестуйте обмеження пакетів (тільки якщо рестартів немає)

Якщо помилки корелюють з великими вставками/оновленнями або трафіком BLOB, перевірте max_allowed_packet на сервері й клієнті та чи надсилає додаток мегабайтові заяви.

  1. Перевірте поточні значення max_allowed_packet (глобальні та сесійні).
  2. Відтворіть у контрольованому середовищі з великим payload-ом.
  3. Виправте, підвищивши ліміти обґрунтовано або змінивши спосіб відправки даних (чанджинг, стрімінг, уникнення мегазапитів).

Практичні завдання: команди, виводи, рішення (робіть в порядку)

Це не «ідеї». Це кроки, які ви виконуєте на Ubuntu 24.04 і висновки з кожного. Запускайте їх від користувача з правами sudo на хості БД, якщо не зазначено інше.

Завдання 1: Підтвердіть, який MySQL у вас запущено (MySQL vs MariaDB має значення)

cr0x@server:~$ mysql --version
mysql  Ver 8.0.39-0ubuntu0.24.04.1 for Linux on x86_64 ((Ubuntu))

Що це означає: У вас пакети Oracle MySQL 8.0. Змінні та шляхи логів відповідають конвенціям MySQL 8.0.

Рішення: Користуйтеся документацією/змінними MySQL 8.0; не копіюйте MariaDB-специфічні параметри.

Завдання 2: Перевірте стан сервісу та недавні перезапуски (systemd — джерело правди)

cr0x@server:~$ systemctl status mysql --no-pager
● mysql.service - MySQL Community Server
     Loaded: loaded (/usr/lib/systemd/system/mysql.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 08:22:10 UTC; 2h 11min ago
   Main PID: 1234 (mysqld)
     Status: "Server is operational"
      Tasks: 44 (limit: 38316)
     Memory: 1.3G
        CPU: 18min 12.345s
     CGroup: /system.slice/mysql.service
             └─1234 /usr/sbin/mysqld

Що це означає: MySQL зараз працює; ви також бачите час роботи.

Рішення: Якщо час роботи підозріло короткий порівняно з часом появи помилок, переключіться на розслідування крахів/рестартів перед налаштуванням таймаутів.

Завдання 3: Витягніть останню годину логів mysql.service (шукайте виходи, OOM, рестарти)

cr0x@server:~$ journalctl -u mysql --since "1 hour ago" --no-pager
Dec 30 09:54:01 server systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
Dec 30 09:54:01 server systemd[1]: mysql.service: Failed with result 'signal'.
Dec 30 09:54:01 server systemd[1]: mysql.service: Scheduled restart job, restart counter is at 1.
Dec 30 09:54:04 server systemd[1]: Started mysql.service - MySQL Community Server.

Що це означає: mysqld отримав SIGKILL. Це не граціозно. Типові причини: OOM killer, адмінський kill, watchdog або викид контейнера.

Рішення: Не чіпайте max_allowed_packet ще. Спочатку з’ясуйте, хто його вбив.

Завдання 4: Перевірте докази OOM killer (Ubuntu підкаже)

cr0x@server:~$ journalctl -k --since "2 hours ago" | grep -E "Out of memory|Killed process|oom-kill" | tail -n 20
Dec 30 09:53:59 server kernel: Out of memory: Killed process 1234 (mysqld) total-vm:9123456kB, anon-rss:6212340kB, file-rss:0kB, shmem-rss:0kB, UID:112 pgtables:15432kB oom_score_adj:0
Dec 30 09:53:59 server kernel: oom_reaper: reaped process 1234 (mysqld), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Що це означає: MySQL був вбитий через нестачу пам’яті. Ваш додаток бачить «server has gone away», бо сервер дійсно помер.

Рішення: Виправте розподіл пам’яті, паралелізм або витоки; підвищення лімітів пакетів може лише погіршити ситуацію.

Завдання 5: Знайдіть і підвʼяжіть лог помилок MySQL (піймайте відновлення після краху, помилки пакетів)

cr0x@server:~$ sudo ls -l /var/log/mysql/
total 120
-rw-r----- 1 mysql adm 118432 Dec 30 10:32 error.log
cr0x@server:~$ sudo tail -n 60 /var/log/mysql/error.log
2025-12-30T09:54:04.123456Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.39) starting as process 5678
2025-12-30T09:54:05.234567Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
2025-12-30T09:54:06.345678Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.39'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.

Що це означає: Ви бачите маркери рестарту та готовності; якщо були помилки пакетів, частіше за все ви побачите «Got packet bigger than ‘max_allowed_packet’ bytes».

Рішення: Якщо логи показують відновлення після краху або часті рестарти — виправляйте стабільність першочергово. Якщо логи показують помилки розміру пакета — переходьте до розділу про пакети.

Завдання 6: Перевірте поточні змінні таймаутів і пакетів (глобальна істина)

cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL VARIABLES WHERE Variable_name IN ('wait_timeout','interactive_timeout','net_read_timeout','net_write_timeout','max_allowed_packet','max_connections','connect_timeout');"
+---------------------+-----------+
| Variable_name       | Value     |
+---------------------+-----------+
| connect_timeout     | 10        |
| interactive_timeout | 28800     |
| max_allowed_packet  | 67108864  |
| max_connections     | 500       |
| net_read_timeout    | 30        |
| net_write_timeout   | 60        |
| wait_timeout        | 28800     |
+---------------------+-----------+

Що це означає: 64MB пакети, 8-годинний таймаут проста та стандартні net-таймаути.

Рішення: Якщо помилки трапляються після ~8 годин простою — це, ймовірно, wait_timeout. Якщо помилки на ~60 секундах під час великих наборів результатів — підозрюйте net_write_timeout або таймаути проксі.

Завдання 7: Перевірте сесійні змінні з перспективи додатку (пул може задавати інше)

cr0x@server:~$ mysql -uroot -p -e "SHOW SESSION VARIABLES WHERE Variable_name IN ('wait_timeout','max_allowed_packet');"
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 67108864 |
| wait_timeout       | 28800    |
+--------------------+----------+

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

Рішення: Якщо додаток використовує інший акаунт з init-командами, перевірте конкретний акаунт або конфіг пулу підключень.

Завдання 8: Подивіться лічильники aborted connections (виявлення таймаутів і обривів мережі)

cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Aborted_%';"
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 1249  |
| Aborted_connects | 12    |
+------------------+-------+

Що це означає: Aborted_clients рахує підключення, які були перервані (клієнт зник, мережа, таймаут). Це не ідеальна діагностика, але важливі тренди.

Рішення: Якщо Aborted_clients швидко зростає під час інцидентів — перевіряйте обриви через проксі/NAT або навантаження на сервер, що призводить до завислих записів.

Завдання 9: Перевірте поточні підключення та їхній стан (idle vs завислі)

cr0x@server:~$ mysql -uroot -p -e "SHOW PROCESSLIST;"
+-----+------+-----------------+------+---------+------+------------------------+------------------+
| Id  | User | Host            | db   | Command | Time | State                  | Info             |
+-----+------+-----------------+------+---------+------+------------------------+------------------+
| 101 | app  | 10.10.0.21:5332 | prod | Sleep   | 7200 |                        | NULL             |
| 102 | app  | 10.10.0.21:5333 | prod | Sleep   | 7199 |                        | NULL             |
| 201 | app  | 10.10.0.22:6121 | prod | Query   |   55 | Sending data           | SELECT ...       |
+-----+------+-----------------+------+---------+------+------------------------+------------------+

Що це означає: У вас довго сплячі pooled-з’єднання (2 години). Це нормально, якщо мережа підтримує таке. Це проблема, якщо проксі вбиває idle-сокети через 60 хвилин.

Рішення: Якщо ви бачите багато дуже старих sleeping-з’єднань і помилки після простою — вирівняйте життєвий час пулу та таймаути сервера/проксі.

Завдання 10: Перевірте TCP-level reset-и та повторні передачі (або мережа «бреше»?)

cr0x@server:~$ ss -tan sport = :3306 | head -n 20
State  Recv-Q Send-Q Local Address:Port Peer Address:Port  Process
ESTAB  0      0      10.0.0.10:3306   10.10.0.21:5332
ESTAB  0      0      10.0.0.10:3306   10.10.0.21:5333
ESTAB  0      0      10.0.0.10:3306   10.10.0.22:6121

Що це означає: З’єднання існують і не в очевидних «забитих» станах. Для глибшого аналізу мережі використовуйте nstat.

cr0x@server:~$ nstat -az | egrep "TcpRetransSegs|TcpExtTCPRcvCoalesce|TcpExtListenOverflows|TcpExtListenDrops" || true
TcpRetransSegs                124
TcpExtListenOverflows         0
TcpExtListenDrops             0

Рішення: Якщо retransmits різко зростають під час помилок — це не «налаштування MySQL». Досліджуйте шлях мережі, MTU, затори або завантаження CPU хоста.

Завдання 11: Перевірте Linux TCP keepalive (часто занадто «сонні»)

cr0x@server:~$ sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9

Що це означає: Keepalive починається через 2 години. Якщо ваш фаєрвол вбиває idle через 15 хвилин — це не допоможе.

Рішення: Якщо потрібно підтримувати довгі з’єднання, зменшіть час keepalive (або краще: встановіть max lifetime пулу нижче за мережевий idle таймаут).

Завдання 12: Шукайте помилки розміру пакета в логах

cr0x@server:~$ sudo grep -E "max_allowed_packet|packet bigger than" -n /var/log/mysql/error.log | tail -n 20
2025-12-30T10:01:12.123456Z 45 [Warning] [MY-000000] [Server] Got packet bigger than 'max_allowed_packet' bytes

Що це означає: Це ваш «димовий сигнал». Сервер відхилив вхідний пакет.

Рішення: Підвищте max_allowed_packet до обґрунтованого значення і виправте поведінку додатку, що згенерувала гігантські пакети, якщо можливо.

Завдання 13: Безпечно відтворіть обмеження пакетів (контрольний тест, не на піковому проді)

cr0x@server:~$ python3 - << 'PY'
import os
print(len(os.urandom(10*1024*1024)))
PY
10485760

Що це означає: Ви можете згенерувати 10MB blob. Тепер протестуйте вставку з параметричним біндингом (бажано) через ваш стек або локальний mysql-тест, якщо це припустимо.

Завдання 14: Підтвердіть джерела конфігурації на Ubuntu (уникайте редагування не того файлу)

cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'pid_file'; SHOW VARIABLES LIKE 'socket';"
+---------------+------------------------------+
| Variable_name | Value                        |
+---------------+------------------------------+
| pid_file      | /var/run/mysqld/mysqld.pid   |
+---------------+------------------------------+
+---------------+------------------------------+
| Variable_name | Value                        |
+---------------+------------------------------+
| socket        | /var/run/mysqld/mysqld.sock  |
+---------------+------------------------------+

Рішення: В упаковці Ubuntu зазвичай змінюють налаштування MySQL у /etc/mysql/mysql.conf.d/mysqld.cnf (або drop-in файлах). Перевірте за допомогою mysqld --verbose --help, якщо сумніваєтесь.

Завдання 15: Протестуйте навантаження на максимальні підключення (вичерпання thread-ів схоже на «gone away»)

cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections';"
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 312   |
+-------------------+-------+
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| Max_used_connections| 498   |
+---------------------+-------+

Що це означає: Ви близькі до max_connections (500). Коли досягається ліміт, додатки часто не можуть підключитися і некоректно повідомляють помилку.

Рішення: Якщо Max_used_connections підходить до ліміту — виправте пулінг і витоки підключень перед підвищенням max_connections. Підвищення може просто перетворити «помилки підключення» на «OOM kill».

Таймаути, що важливі: wait_timeout, net_*_timeout, проксі та TCP keepalive

Почніть з таймлайну, а не з назв змінних

Найшвидший спосіб вирішити «gone away» — накреслити час. Не дашборд з сорока кольорів; простий таймлайн:

  • Коли додаток відкрив підключення?
  • Як довго воно було в стані простою?
  • Коли сталася помилка: на першому запиті після простою чи під час запиту?
  • Чи є проксі між додатком і MySQL?

Якщо помилки відбуваються на першому запиті після тривалого простою — це невідповідність idle-таймаутів. Якщо помилки під час довгого запиту — це таймаути читання/запису запиту, або рестарт/крах сервера.

wait_timeout і interactive_timeout: гільйотина простих MySQL

wait_timeout — скільки сервер дозволяє неінтерактивній сесії сидіти в стані простою, перш ніж вбити її. Більшість підключень додатків — неінтерактивні. interactive_timeout для інтерактивних клієнтів (наприклад, людини в терміналі), коли клієнт встановлює прапорець CLIENT_INTERACTIVE.

У продакшні рідко буває, що «wait_timeout занадто малий». Частіше:

  • Пул додатку тримає підключення годинами.
  • Фаєрвол/NAT/проксі посередині обрізає idle TCP за 5–60 хвилин.
  • Додаток зберігає обʼєкт підключення й намагається його повторно використати пізніше.
  • Наступний запит потрапляє на мертвий сокет і ви отримуєте «server has gone away».

Отже, виправлення не завжди «підняти wait_timeout». У багатьох середовищах ви хочете скоротити серверний idle-таймаут і забезпечити, щоб пул не тримав зомбі.

Практичні поради, які працюють

  • Встановіть max lifetime пулу нижче за найкоротший idle-таймаут у шляху (NAT/проксі/фаєрвол). Приклад: якщо LB вбиває idle через 15 хвилин — встановіть lifetime пулу 10 хвилин.
  • Віддавайте перевагу перевірці при позичанні (test query) замість періодичних ping-ів, якщо навантаження стрибкоподібне.
  • Не ставте wait_timeout на дні. Довгі idle-з’єднання — це не «стабільність», це застарілий стан з хорошою PR-командою.

net_read_timeout і net_write_timeout: таймаути «під час запиту»

Це серверні таймаути читання з і запису до клієнта. Вони стають релевантними коли:

  • Клієнт повільно надсилає великий запит (повільна мережа, перевантажений клієнт), і сервер вирішує, що чекав достатньо.
  • Сервер відправляє великий набір результатів, а клієнт не читає (CPU клієнта завантажений, backpressure), тож сервер блокується і таймаутиться.

Якщо ви бачите Lost connection during query і маєте великі результати або великі записи — ці змінні підозрілі. Але не просто підвищуйте їх. Запитайте, чому відправник/отримувач повільний. У продакшні «повільний клієнт» часто означає, що пул потоків голодує через GC, або PHP worker застряг, тримаючи сокет відкритим.

Linux TCP keepalive: останній рубіж

Keepalive — низькорівневий спосіб підтримувати стан NAT/фаєрволу та виявляти мертвих пірів. Вони допомагають, але не замінюють правильний пулінг.

Якщо ви контролюєте сервери і мусите підтримувати довгі з’єднання через ненадійні проміжні вузли, налаштування keepalive може зменшити «сюрпризні» падіння сокетів:

  • Зменшіть tcp_keepalive_time до ~300 секунд (5 хвилин), якщо шлях обриває idle близько 10–15 хвилин.
  • Тримайте інтервали розумними (tcp_keepalive_intvl 30–60 секунд) і кількість перевірок помірною.

Обачно: keepalive створює трафік. На дуже великих кластерах це не безкоштовно. Але дешевше, ніж піднімати людей.

Обмеження пакетів: max_allowed_packet, великі рядки та великі запити

Помилки пакетів прості, коли ви їх зловите. Складність у тому, що багато стеків не логують реальне попередження MySQL, тож ви бачите лише «gone away».

Що вважати «пакетом» у цьому контексті

Протокол MySQL передає дані пакетами, і max_allowed_packet обмежує найбільший пакет, який сервер прийме (та також найбільший, який він відправить). Це взаємодіє з:

  • Гігантськими INSERT ... VALUES (...), (...), ...
  • Великими BLOB/TEXT колонками
  • Великими наборами результатів (сервер відправляє багато назад)
  • Реплікацією та binlog-подіями (так, розміри пакетів важливі і там)

Як обрати значення без «культу»

Поширені значення: 16MB, 64MB, 256MB, 1GB. Спокуса встановити 1GB «на всяк випадок» — велика. Не робіть так.

Чому? Бо більші пакети можуть створювати великі буфери на підключення й виділення пам’яті. Одиничний гігантський запит може створити тиск на пам’ять і спричинити OOM, що проявиться як… «server has gone away».

Розумний підхід:

  1. Виміряйте розміри payload-ів: знайдіть найбільший реалістичний запит/рядок, який надсилає додаток.
  2. Додайте запас: 2–4× зазвичай достатньо, не 100×.
  3. Краще змінити додаток: уникати мегазапитів, розбивати пакетами, стрімінгувати BLOB, зберігати великі об’єкти поза БД або хоча б стискати.

Клієнтські ліміти також важливі

Багато клієнтів мають власний max_allowed_packet або еквівалент. Якщо ви підвищите сервер, але клієнт відмовляє — помилка збережеться, іноді з заплутаними повідомленнями, специфічними для драйвера.

Правильне виправлення на Ubuntu (MySQL 8.0)

Ви хочете постійної зміни в конфігу, а не одноразового SET GLOBAL, що зникне при рестарті.

Приклад змін (ілюстративно): встановіть max_allowed_packet=128M, якщо є докази, що це потрібно.

cr0x@server:~$ sudo grep -n "max_allowed_packet" /etc/mysql/mysql.conf.d/mysqld.cnf || true
cr0x@server:~$ sudo bash -lc 'printf "\n[mysqld]\nmax_allowed_packet=128M\n" >> /etc/mysql/mysql.conf.d/mysqld.cnf'
cr0x@server:~$ sudo systemctl restart mysql

Що це означає: Ви встановили значення для старту сервера. Тепер перевірте.

cr0x@server:~$ mysql -uroot -p -e "SHOW GLOBAL VARIABLES LIKE 'max_allowed_packet';"
+--------------------+-----------+
| Variable_name      | Value     |
+--------------------+-----------+
| max_allowed_packet | 134217728 |
+--------------------+-----------+

Рішення: Якщо помилки зникли і пам’ять стабільна — залишайте так. Якщо пам’ять стрибає або OOM-ів більше — ваше «виправлення» може виявитись ручною гранатою з відтягнутою детонацією.

Жарт №2: max_allowed_packet на 1GB — це як купити більшого смітника замість того, щоб виносити сміття; працює доти, доки не перестає, і тоді виходить масштабний фіаско.

Підступна причина: рестарти, краші, OOM та systemd

У продакшні «gone away» дуже часто є буквальним. mysqld помер, перезавантажився або був замінений під час техобслуговування. Ваші клієнти не були поінформовані.

Чому MySQL рестартує на Ubuntu 24.04

  • OOM killer через тиск пам’яті (часто, коли буфери занадто великі або змінилась навантаженість).
  • Події ядра або гіпервізора (перезавантаження хоста, проблеми live migration, блокування сховища, що спричиняє watchdog).
  • Оновлення пакетів, що перезапускають сервіси.
  • Дії людей (рестарт через зміну конфігу) без координації з пулами та логікою повторних спроб.
  • Краші (баги існують; але спочатку перевіряйте ресурси та конфіг).

Як зробити рестарти менш болючими

Два кути атаки: зменшити частоту і зменшити зону ураження.

  • Зменшити частоту: правильно розрахуйте пам’ять; уникайте патологічних запитів; слідкуйте за здоров’ям сховища; не запускайте сервер на 99% RAM з вимкненим swap і молитвами.
  • Зменшити зону ураження: використовуйте повторні спроби в додатку з ідемпотентністю; застосовуйте пули з валідацією зʼєднань; ставте коротші lifetime пулу; розгляньте MySQL Router/ProxySQL, якщо це доцільно (але тоді керуйте їхніми таймаутами теж).

Швидке виявлення crash-loop

Якщо ви бачите часті рестарти — це інцидент стабільності, а не тонке налаштування.

cr0x@server:~$ systemctl show mysql -p NRestarts -p ExecMainStatus -p ExecMainCode
NRestarts=3
ExecMainStatus=0
ExecMainCode=0

Що це означає: Служба перезапускалась кілька разів з моменту завантаження.

Рішення: Зберіть журнали systemd за період рестартів; перевірте OOM та логи помилок; тимчасово зменшіть паралелізм зі сторони додатку, щоб стабілізувати систему.

Проксі та балансувальники: посередники таймаутів

Якщо між вашим додатком і MySQL стоїть проксі (HAProxy, ProxySQL, хмарний LB, sidecar сервісної сітки), у вас є мінімум три незалежні системи таймаутів:

  • Серверні таймаути MySQL
  • Проксі-таймаути простих сесій
  • Клієнтські/драйверні таймаути і налаштування пулу

Більшість «server has gone away після простою» виникає через невідповідність: проксі закрив з’єднання першим, а пул думає, що може його повторно використати вічно.

Як «правильно» має виглядати

  • Переможе найкоротший idle-таймаут. Max lifetime вашого пулу має бути коротший за нього.
  • Keepalive там, де потрібно. Якщо проксі підтримує TCP keepalive — увімкніть його там; не покладайтеся лише на дефолти ядра.
  • Спостерігайте на проксі. Якщо проксі логують причини дисконекту — це заощадить вам години здогадок.

Коли підвищувати таймаути правильно

Іноді додатку дійсно потрібні довгі запити (аналітика, міграції, backfill). У такому випадку:

  • Підвищуйте таймаути читання/запису на проксі/сервері понад найтриваліший легітимний час запиту плюс запас.
  • Краще винести довгі роботи за межі запитного шляху.
  • Використовуйте обмеження і скасування, щоб уникнути зомбі-запитів, що ніколи не завершуються.

Три корпоративні міні-історії з поля бою

Міні-історія 1: Інцидент через хибну припущення

Вони мали розумне припущення: «Наш БД і додаток в одному VPC, отже мережа стабільна». Розумно — не означає правильно.

Додаток використовував пул з щедрим max lifetime. З’єднання сиділи годинами вночі, потім використовувалися вранці. Рівень помилок різко зростав під першим піком трафіку після 9:00, потім з часом зменшувався.

Команда підняла wait_timeout, бо це виглядало як «idle disconnects». Це не допомогло. Вони підняли ще. Знову нічого. Тим часом логи проксі (які ніхто не читав) показали, що idle-сесії обрізаються через 60 хвилин політикою внутрішнього LB.

Коли вони вирівняли lifetime пулу на 45 хвилин і увімкнули валідаційний запит при позичанні, помилка зникла. Урок був не в «підвищити таймаути». Урок — «найкоротший таймаут у шляху вирішує вашу долю».

Міні-історія 2: Оптимізація, що відкинула назад

Команда хотіла зменшити число мережевих кругів. Вони змінили шлях bulk-import, щоб будувати величезні multi-row INSERT-вирази. CPU впав, пропускна здатність зросла, і всі раділи.

Потім новий клієнт почав імпортувати більші записи з довгими JSON-блоками. Першою помилкою не була граціозна «packet too large». У додатку з’являлась переривчаста «server has gone away», а за кілька хвилин MySQL перезавантажувався. На виклику мережа звинувачувала додаток, мережа — MySQL.

Корінь був грубий: гігантські вирази підвищили пікове використання пам’яті (парсинг на сервері + буфери + накладні витрати на підключення), і при піковій конкуренції MySQL отримував OOM-kill. Початкова оптимізація збільшила розмір найгірших пакетів і загострила пікові сплески пам’яті. Працювало аж поки не перестало.

Виправлення було двоє: обмежити розмір пакетів імпорту, щоб вирази лишалися в межах здорового максимуму, і встановити max_allowed_packet відповідно до реальних потреб. Вони зберегли більшість приросту продуктивності, і БД перестала «вмирати».

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Інша організація мала нудну звичку: після кожної суттєвої зміни вони знімали поточні змінні MySQL, статус systemd і невеликий набір параметрів мережі в коментарі до тікета. Не великий дамп — просто те, що пояснює поведінку.

Одного дня вони почали бачити спорадичні «lost connection during query». Перша підозра була на недавню міграцію схеми. Але їхні «нудні знімки» показали, що net_write_timeout було знижено тижнями раніше під час помилкового прагнення «fail fast», і зміна жила у drop-in файлі, про який ніхто не пам’ятав.

Вони відкотили таймаут, і помилки зникли. Міграція була невинною. Цінність практики не в самому знімку; вона дала швидкий diff припущень і реальності.

Ця нудна правильність зберігає ваші вихідні дні у спокої.

Типові помилки: симптом → корінна причина → виправлення

1) Помилки відбуваються відразу після тривалих періодів простою

Симптом: Перший запит після неактивності падає з «server has gone away», наступні спроби працюють.

Корінна причина: Idle TCP-сесії обриваються (проксі/NAT/фаєрвол) або MySQL вбиває idle-сесії (wait_timeout).

Виправлення: Встановіть max lifetime пулу нижче за найкоротший таймаут шляху; додайте валідацію підключення; опційно налаштуйте TCP keepalive. Якщо сервер вбиває idles і вам дійсно потрібні довгі idles — підвищуйте wait_timeout лише якщо мережа їх підтримує.

2) Помилки відбуваються під час великих вставок/оновлень

Симптом: Збої корелюють з bulk-записами, великим JSON, BLOB-аплоадами або великими multi-row заявами.

Корінна причина: Перевищено ліміт пакета (max_allowed_packet) на сервері або клієнті; або тиск пам’яті від гігантських виразів.

Виправлення: Підтвердіть повідомлення в логах; підвищте max_allowed_packet до обґрунтованого значення; зменшіть розміри батчів; перейдіть на стрімінг/чанкінг.

3) «Lost connection during query» на повільних кінцях

Симптом: Довгий запит виконується, потім падає. Або великі результати падають, коли клієнт зайнятий.

Корінна причина: Таймаут проксі на читання/запис або MySQL net_read_timeout/net_write_timeout занадто малі; клієнт не читає достатньо швидко.

Виправлення: Підвищіть відповідні таймаути до реалістичного часу виконання; виправте додаток, щоб стрімінгувати результати; уникайте завантаження великих наборів у пам’ять; додайте пагінацію.

4) Помилки зростають разом з рестартами MySQL

Симптом: Помилки підключення збігаються з часами перезапусків сервісу; час роботи обнуляється.

Корінна причина: Кrach mysqld, OOM kill, оновлення пакету, перезавантаження хоста.

Виправлення: Діагностуйте причину рестарту через journal + error log; виправте розміри пам’яті; зменшіть паралелізм; розгляньте поведінку swap; обмежте найгірші запити/пакети.

5) Помилки зʼявляються після підвищення max_connections

Симптом: Менше «too many connections», але більше «gone away» і рестартів.

Корінна причина: Більше одночасних підключень збільшило використання пам’яті й контекстні перемикання; сервер став нестабільним.

Виправлення: Зменшіть кількість підключень через правильний пулінг; додайте лімітер підключень; правильно розмірьте буфери; не масштабуйте грубою силою.

6) Помилки тільки в одному середовищі (прод, а не staging)

Симптом: Staging у порядку, прод падає періодично.

Корінна причина: Різні мережеві проміжні вузли/таймаути, різні налаштування пулу, різні розміри payload-ів, різна конкуренція або відмінна конфігурація MySQL.

Виправлення: Порівняйте найкоротший таймаут у шляху; перевірте реальні розміри payload-ів; зробіть diff змінних MySQL; відтворіть із даними, схожими на прод.

Чек-листи / покроковий план

Покроковий план: зупиніть кровотечу, потім виправляйте правильно

  1. Підтвердіть, чи MySQL рестартувався. Використайте systemctl status mysql і journalctl -u mysql. Якщо так — це питання стабільності першочергове.
  2. Перевірте OOM kill-и. Використайте journalctl -k з фільтром повідомлень OOM. Якщо OOM: зменшіть використання пам’яті і конкуренцію перед підвищенням лімітів.
  3. Підтягуйте лог помилок MySQL. Шукайте попередження про пакети і маркери відновлення після краху.
  4. Класифікуйте час виникнення помилки. Після простою чи під час запиту — це змінює весь шлях розслідування.
  5. Зробіть інвентар таймаутів по шарах. MySQL (wait_timeout, net_*), проксі, клієнтський пул, OS keepalive.
  6. Виправте найкоротшу невідповідність таймаутів. Встановіть max lifetime пулу і валідацію; не намагайтесь «перехитрити» фаєрвол оптимізмом.
  7. Лише потім коригуйте змінні MySQL. Піднімайте max_allowed_packet якщо є докази; налаштуйте net_write_timeout/net_read_timeout якщо відбуваються обриви під час запитів.
  8. Перевірте з контрольованим відтворенням. Використайте відомий розмір payload-а, відомий час виконання запиту і стежте за логами та лічильниками.
  9. Зафіксуйте зміни в системі управління конфігом. Уникайте таємних drop-in файлів і «тимчасових» глобальних SET-ів, що зникають при рестарті.
  10. Моніторьте важливе. Uptime, рестарти, OOM події, aborted clients, використані підключення та розподіл латентності.

Чек-лист: правила безпечного налаштування таймаутів

  • Не встановлюйте серверні idle-таймаути довшими, ніж мережа може підтримувати.
  • Max lifetime пулу < найкоротший мережевий/проксі таймаут.
  • Валідація при позичанні краща за періодичний ping при сплесках трафіку.
  • Підвищуйте read/write таймаути тільки після підтвердження легітимних довгих запитів або повільних клієнтів.

Чек-лист: правила безпечного налаштування пакетів

  • Знайдіть найбільший легітимний payload спочатку.
  • Підвищуйте по кроках (наприклад, 64M → 128M), а не стрибками до 1G.
  • Капайте розмір батчів; уникайте мегазапитів.
  • Слідкуйте за пам’яттю після зміни; розмір пакетів і OOM тісно пов’язані.

FAQ

1) Чи завжди «MySQL server has gone away» — це таймаут?

Ні. Це може бути таймаут, обмеження пакета, рестарт сервера або скидання мережі. Ваше перше завдання — визначити, до якої категорії це належить.

2) Чи просто підвищити wait_timeout, щоб зупинити idle disconnects?

Лише якщо MySQL — шар, що вбиває idles, і вам дійсно потрібні довгі idle-з’єднання. У багатьох реальних середовищах проксі/NAT спочатку вбиває TCP-сесію, тож підвищення wait_timeout не допоможе. Спочатку виправте lifetime пулу і валідацію.

3) Яке хороше значення для max_allowed_packet?

Значення має визначатися виміряними payload-ами плюс запас. 64MB або 128MB часто підходять для додатків, що інколи пересилають середні blobs. Якщо потрібні сотні МБ — перегляньте архітектуру (чанкінг, сховище обʼєктів, стрімінг).

4) Чому я бачу проблему лише після деплою?

Деплої змінюють поведінку підключень: більше воркерів, інші дефолти пулу, більші запити, інша логіка повторних спроб або новий проксі-хоп. Також оновлення пакету могло перезапустити mysql.service.

5) У чому різниця між помилками 2006 і 2013?

2006 часто означає, що з’єднання вже було мертвим при спробі його використати. 2013 зазвичай означає, що з’єднання померло під час активного запиту або передачі даних. Вони вказують на різні підозри.

6) Чи може max_connections спричинити «gone away»?

Опосередковано. Досягнення max_connections призводить до помилок підключення; залежно від драйвера додаток може неправильно їх відобразити. Підвищення max_connections також підвищує використання пам’яті і може викликати крахи, що породжують реальні «gone away» помилки.

7) Чи повинен додаток автоматично перепідключатися?

Автоперепідключення небезпечне, якщо ви можете бути в межах транзакції. Безпечніший підхід: пул з валідацією, повтори на вищому рівні з ідемпотентними операціями, і негайний фейл на неідемпотентних записах.

8) Чи вирішують TCP keepalive це надійно?

Вони допомагають з idle NAT/фаєрвол таймаутами і виявленням мертвих пір, але не замінюють правильно налаштовані пули і таймаути. Використовуйте їх як мережеве «трубопровідне» рішення, а не як архітектурне.

9) Що робити, якщо лог помилок MySQL нічого не показує?

Тоді або ви дивитесь не в те місце, або логування неправильно налаштоване, або розрив трапляється поза MySQL (проксі/мережа). Підтвердіть шляхи логів, перевірте systemd journal і логи проксі, якщо вони є.

10) Я підвищив max_allowed_packet, але все одно фейлиться. Що робити далі?

Підтвердіть клієнтський ліміт, підтвердіть, що налаштування застосовано (глобальне vs сесійне), і виміряйте розмір payload-а. Також перевірте обмеження проксі та можливі OOM-події, спричинені більшими алокаціями.

Висновок: наступні кроки, які ви можете зробити сьогодні

Якщо ви візьмете лише одну операційну звичку з цього матеріалу: перестаньте ставитися до «server has gone away» як до однієї баги. Це клас симптомів. Швидко його класифікуйте.

  1. Перевірте рестарти першочергово (systemctl status, journalctl, OOM логи). Якщо mysqld помирав — виправляйте стабільність перед налаштуванням параметрів.
  2. Визначте idle чи mid-query за часом і типом помилки (2006 vs 2013). Це підкаже, чи фокусуватися на wait_timeout/пулінгу, чи на таймаутах читання/запису/проксі.
  3. Доведіть проблеми з пакетами логами і контрольованим відтворенням. Підвищуйте max_allowed_packet обґрунтовано, потім коригуйте батчинг, щоб не створювати пам’яткові обриви.
  4. Вирівняйте таймаути між шарами (MySQL, проксі/ЛБ, пул, TCP keepalive). Перемагає найкоротший; дійте відповідно.

Зробіть це — і «server has gone away» перестане бути моторошним інтермітентним привидом і перетвориться на звичайний інцидент з коренем і коротким постмортемом. Ваш майбутній «я» буде все ще втомленим, але принаймні втомленим через цікаві причини.

← Попередня
Помилки контрольної суми ZFS: що вони означають і що робити насамперед
Наступна →
ZFS acltype: POSIX vs NFSv4 ACL без плутанини

Залишити коментар