Якщо ваш сайт WordPress час від часу викидає «MySQL server has gone away» або «Too many connections», це не проблема філософії. Це проблема обліку ресурсів. Хтось закриває з’єднання, виснажує їх або тримає занадто довго — зазвичай під час сплеску трафіку, повільного запиту або поганого плагіна, який робить з базою даних те саме, що залишена відкрита кран.
Цей посібник написаний для людей, які експлуатують продакшн-системи та потребують спокійного, повторюваного шляху від «все впало» до «стабільно» без гадань. Ви отримаєте швидку триажну процедуру, конкретні команди, пояснення результатів і подальші рішення.
Що насправді означають ці помилки (і чого вони не означають)
«MySQL server has gone away»
Повідомлення — це спосіб додатку сказати: «Я намагався використати з’єднання з MySQL, але сервер на іншому кінці більше не відповідає.» Таке може статися через:
- Сервер закрив з’єднання через простій (
wait_timeout). - Сервер аварійно перезапустився або рестартувався (OOM killer, ручний рестарт, оновлення, паніка).
- Мережевий пристрій перервав з’єднання (NAT, брандмауер, таймаут балансувальника).
- Клієнт надіслав пакет більший за дозволений (
max_allowed_packet). - Сервер досяг обмежень ресурсів і почав відмовляти або переривати роботу (диск заповнений, пошкодження таблиці, неконтрольований витік пам’яті, вичерпання потоків).
Це не означає «WordPress зламався». WordPress лише кур’єр. Не стріляйте в нього; опитайте його.
«Too many connections»
Це MySQL каже прямо: кількість одночасних клієнтських з’єднань перевищила max_connections. У WordPress-стеках це зазвичай відбувається тому, що PHP відкриває з’єднання швидше, ніж MySQL встигає завершити запити.
Два тонкі моменти мають значення:
- Підвищення
max_connectionsне є самостійним вирішенням. Воно може купити час або перетворити «відмовлені з’єднання» на «шторм свопу» та вивести весь хост з ладу. - Кількість з’єднань — це симптом. Корінь проблеми майже завжди — затримки: повільні запити, блокування таблиць, насичена I/O підсистема, завантажений CPU або надмірне створення воркерів PHP-FPM.
Одна перефразована ідея, часто приписувана лідерам інженерії: «Надійність — це функція, яку будують, а не бажання, яке загадують.»
Короткий жарт #1: MySQL не «йде геть». Скоріше він різко виходить із гри, бо хтось попросив його зробити п’ять речей одночасно на ноутбуковому диску.
Швидкий план діагностики (перша/друга/третя перевірки)
Коли сторінки повертають помилки, у вас є одне завдання: швидко знайти вузьке місце, зупинити кровотечу, а потім зробити все надійним.
Перше: визначте клас відмови за 2 хвилини
- Чи працює MySQL і приймає з’єднання? Якщо ні — це краш/рестарт/OOM/диск.
- Чи насичені з’єднання? Якщо так — це «too many connections», часто спричинено повільними запитами або вибухом воркерів PHP.
- Чи з’єднання вбивають посеред виконання? Якщо так — думайте про мережеві таймаути, таймаути MySQL, max packet або поведінку проксі.
Друге: вирішіть, де формується черга
- Черга на боці PHP: PHP-FPM має довгий listen queue, багато активних воркерів, повільні запити.
- Черга на боці MySQL: MySQL показує багато виконуваних потоків, довгі запити, очікування блокувань, пропуски в буфері, очікування I/O.
- Черга в мережі/проксі: періодичні скиди, таймаути NAT, «gone away» після періодів простою.
Третє: застосуйте найбезпечнішу негайну міру
- Якщо MySQL впав: виправте диск/OOM, перезапустіть і обмежте PHP-конкурентність, перш ніж знову підливати трафік.
- Якщо з’єднання насичені: тимчасово зменшіть конкуренцію PHP-FPM; за потреби підніміть
max_connectionsтрохи, якщо дозволяє пам’ять; знайдіть шаблон повільних запитів. - Якщо «gone away» відбувається після простою: вирівняйте таймаути між MySQL, PHP і мережевими пристроями; розгляньте увімкнення keepalives або уникайте постійних з’єднань.
Цікаві факти та історичний контекст (щоб поведінка була зрозуміла)
- За замовчуванням таймаути MySQL були розраховані на довгоживучі сервери додатків, а не на сучасні шари проксі, NAT і безсерверні краєві вузли, які часто обривають прості TCP-з’єднання.
- У ранню епоху LAMP постійні з’єднання були модними, бо відкриття TCP + аутентифікація були дорогими. Сьогодні постійні з’єднання часто посилюють режими відмов під час стрибків трафіку.
- «Too many connections» колись вважали ознакою сили в деяких організаціях — допоки не виявили, що кожен потік коштує пам’яті та часу контекстного переключення.
- WordPress історично заохочував екосистему плагінів, що добре для функцій, але погано для дисципліни запитів. Один плагін може додати по 20 запитів на сторінку, і не сказати вам про це.
- InnoDB став механізмом за замовчуванням у MySQL 5.5, і це змінило правильний підхід до налаштування. Поради з епохи MyISAM досі блукають форумами.
- Шторми з’єднань — відома категорія каскадних відмов: коли MySQL сповільнюється, PHP відкриває більше з’єднань, що ще більше гальмує MySQL, що викликає повтори… і тепер усе горить.
- Історично
max_allowed_packetбув малим за замовчуванням, бо пам’ять була цінною і величезні блоби не заохочували. Медія WordPress і сериалізовані опції ігнорують ту історію. - Багато інцидентів «server has gone away» — не баги MySQL; це таймаути інфраструктури (балансувальники навантаження, брандмауери) з дефолтами 60 секунд, про які ніхто не пам’ятає.
- Aborted connections не завжди погані: вони можуть відображати користувачів, які закрили браузер під час запиту. Але сплеск aborted connections разом із помилками зазвичай — явна ознака проблеми.
Основні режими відмов за «gone away» і «too many connections»
1) MySQL перезапускається (часто OOM або диск)
Якщо mysqld перезапускається, кожне активне з’єднання вмирає. WordPress повідомляє «server has gone away», бо сокет, який він мав, тепер — скам’янілость.
Поширені тригери:
- Вбивство через нестачу пам’яті: занадто великий buffer pool, багато буферів на потік, забагато з’єднань або витік пам’яті в суміжних службах.
- Диск заповнений: бінлог, tmpdir, ibtmp або логи повільних запитів ростуть; MySQL починає провалювати записи; далі — каскад помилок.
- Затримки файлової системи: I/O-стали призводять до зависання потоків; watchdog рестартує; клієнти таймаутять.
2) Голодування з’єднань через повільні запити
WordPress розмовливий. Додайте кілька неіндексованих мета-запитів, плагін звітності та наполегливе обходження ботом — і ви можете зайняти всі потоки. Нові з’єднання відхиляються: «too many connections».
3) PHP-FPM або веб-рівень дозволяють занадто велику паралельність
Більшість відмов WordPress перекладають на MySQL, але PHP-FPM часто підпалює сірник. Якщо дозволити 200 PHP-воркерів і кожен може відкрити MySQL-з’єднання, ви ефективно налаштували повінь з’єднань.
4) Таймаути не вирівняні між шарами
Уявіть, що wait_timeout MySQL — 60 секунд. Ваш балансувальник обриває простий TCP через 50 секунд. PHP намагається повторно використати з’єднання через 55 секунд. Результат: «server has gone away», хоча MySQL насправді його не закривав.
5) Надто великі пакети та дивні дані WordPress
WordPress зберігає масиви в wp_options як сериалізовані блоби. Деякі плагіни клацають туди величезні навантаження (кеші конструкторів сторінок, дампи аналітики). Якщо цей блоб перевищує max_allowed_packet, MySQL обриває з’єднання і ви отримуєте «server has gone away».
Практичні завдання: команди, виводи, рішення (12+)
Це кроки, які я очікую від инка під час інциденту. Кожне завдання містить команду, приклад виводу, що це означає і рішення, яке треба прийняти.
Завдання 1: Підтвердіть, що MySQL живий і зауважте час роботи
cr0x@server:~$ systemctl status mysql --no-pager
● mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2025-12-27 10:11:03 UTC; 3min ago
Docs: man:mysqld(8)
Main PID: 1423 (mysqld)
Status: "Server is operational"
Що це означає: Якщо час роботи — кілька хвилин і ви не рестартували його, у вас або цикл крашів, або перезапуск хоста.
Рішення: Якщо він недавно перезапустився, негайно перевірте логи на OOM/диск і обмежте PHP-конкурентність перш ніж трафік знову його наситить.
Завдання 2: Перевірте журнал помилок MySQL на підписи аварій
cr0x@server:~$ journalctl -u mysql --since "30 min ago" --no-pager | tail -n 40
Dec 27 10:10:58 server mysqld[1423]: 2025-12-27T10:10:58.221Z 0 [Warning] Aborted connection 891 to db: 'wpdb' user: 'wpuser' host: '10.0.2.15' (Got an error reading communication packets)
Dec 27 10:11:02 server systemd[1]: mysql.service: Main process exited, code=killed, status=9/KILL
Dec 27 10:11:03 server systemd[1]: mysql.service: Scheduled restart job, restart counter is at 1.
Dec 27 10:11:03 server systemd[1]: Started MySQL Community Server.
Що це означає: Status 9/KILL часто вказує на OOM killer або адміністративне вбивство.
Рішення: Підтвердіть OOM і розміри пам’яті; не «просто перезапускайте» по колу в надії, що база знайде внутрішній спокій.
Завдання 3: Підтвердіть події OOM killer
cr0x@server:~$ dmesg -T | egrep -i "oom|killed process|out of memory" | tail -n 20
[Sat Dec 27 10:11:01 2025] Out of memory: Killed process 1423 (mysqld) total-vm:4123988kB, anon-rss:2450180kB, file-rss:0kB, shmem-rss:0kB, UID:110 pgtables:6420kB oom_score_adj:0
Що це означає: MySQL був вбитий через пам’ять. «Server has gone away» — наслідок.
Рішення: Зменшити споживання пам’яті (buffer pool, буфери на потік, кількість з’єднань), додати RAM або перемістити MySQL з перевантаженого хоста.
Завдання 4: Перевірте вільний диск та іноді іноди
cr0x@server:~$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 80G 76G 2.1G 98% /
tmpfs tmpfs 3.9G 12M 3.9G 1% /run
cr0x@server:~$ df -ihT
Filesystem Type Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 ext4 5M 4.9M 120K 98% /
Що це означає: Майже повний диск або іноди можуть порушити тимчасові таблиці, логи, бінлоги і навіть запис сокета.
Рішення: Звільніть місце негайно (обертання логів, безпечне очищення бінлогів), а потім виправте приріст (ретеншн логів, моніторинг).
Завдання 5: Виміряйте тиск з’єднань і запас по max_connections
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 198 |
+-------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 200 |
+-----------------+-------+
Що це означає: Ви прижаті до стелі. MySQL витрачає час на контекстні переключення, а нові логіни будуть відхилені.
Рішення: Не піднімайте одразу до 1000 з’єднань. Спочатку зменште конкуруючість вгору по стеку (PHP-FPM), потім знайдіть, що повільно.
Завдання 6: Визначте, хто споживає з’єднання (за user/host)
cr0x@server:~$ mysql -e "SELECT user, host, COUNT(*) AS conns FROM information_schema.processlist GROUP BY user, host ORDER BY conns DESC LIMIT 10;"
+--------+------------+-------+
| user | host | conns |
+--------+------------+-------+
| wpuser | 10.0.2.15 | 184 |
| wpuser | 10.0.2.16 | 12 |
| root | localhost | 1 |
+--------+------------+-------+
Що це означає: Одна веб-нода переповнює базу або застрягла в циклі повторних підключень.
Рішення: Обмежте або відключіть цю ноду; перевірте її чергу PHP-FPM і логи помилок; упевніться, що вона не зациклиться на збоях БД.
Завдання 7: Виявіть довготривалі запити та очікування блокувань
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;" | head -n 30
Id User Host db Command Time State Info
812 wpuser 10.0.2.15:54012 wpdb Query 42 statistics SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts ...
901 wpuser 10.0.2.15:54101 wpdb Query 39 Waiting for table metadata lock ALTER TABLE wp_postmeta ADD INDEX meta_key (meta_key)
...
Що це означає: Довгі запити і метадані-блокування можуть затиснути потоки. Це спричиняє накопичення з’єднань.
Рішення: Вбивайте явно погані ad-hoc запити, відкладіть DDL і виправте запит/індекс. Якщо DDL блокує, перенесіть його на не піковий час і використайте інструменти для онлайн-міграцій схем.
Завдання 8: Перевірте стан InnoDB та реальні підказки «чому повільно»
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | egrep -A3 -B2 "LATEST DETECTED DEADLOCK|History list length|buffer pool|I/O|semaphore" | head -n 80
History list length 124873
Pending reads 0
Pending writes: LRU 0, flush list 12, single page 0
Buffer pool size 131072
Free buffers 12
Database pages 130984
Modified db pages 9812
Що це означає: Велика history list length може означати відставання purge; багато модифікованих сторінок — тиск на скидання сторінок; мало вільних буферів вказує на гарячий buffer pool.
Рішення: Якщо чергується скидання на диск, покращте затримку диска, налаштуйте параметри flush і зменшіть пікові записи (кеші, батчування). Якщо purge відстає — шукайте довгі транзакції.
Завдання 9: Перевірте таймаути, що викликають «gone away» після простою
cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('wait_timeout','interactive_timeout','net_read_timeout','net_write_timeout','max_allowed_packet');"
+--------------------+-----------+
| Variable_name | Value |
+--------------------+-----------+
| interactive_timeout| 28800 |
| max_allowed_packet | 67108864 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| wait_timeout | 60 |
+--------------------+-----------+
Що це означає: 60 секунд для wait_timeout — агресивно. Це може бути прийнятно, якщо ваш додаток не повторно використовує прості з’єднання. У WordPress-стеках іноді це трапляється (залежно від бібліотеки клієнта та персистентності).
Рішення: Вирівняйте таймаути: встановіть wait_timeout на розумне значення (наприклад, 300–900) і переконайтесь, що проксі не обривають раніше, ніж MySQL, або навпаки.
Завдання 10: Перевірте лічильники aborted connections
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Aborted_%';"
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Aborted_clients | 1821 |
| Aborted_connects | 94 |
+------------------+-------+
Що це означає: Aborted_clients зростає, коли клієнти відключаються некоректно; Aborted_connects зростає, коли аутентифікація/рукопотискання не вдаються або ресурсні ліміти блокують підключення.
Рішення: Якщо обидва стрибнули під час інциденту, корелюйте з мережевими скидами й насиченням по max_connections. Якщо лише Aborted_clients зростає, не панікуйте — перевірте рівень помилок і їх таймінг.
Завдання 11: Перевірте конкуренцію та черги PHP-FPM
cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Active: active (running) since Sat 2025-12-27 09:41:10 UTC; 33min ago
cr0x@server:~$ sudo ss -lntp | egrep "php-fpm|:9000"
LISTEN 0 511 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm8.2",pid=2201,fd=8))
Що це означає: Listen backlog (тут 511) — це лише місткість для черги запитів. Реальне питання: чи насичені воркери?
Рішення: Перевірте сторінку статусу FPM або логи; якщо воркери насичені, зменшіть pm.max_children, щоб він відповідав можливостям БД, а не «кількості, що влізе в RAM у хороший день».
Завдання 12: Переконайтесь, що Nginx/Apache не роблять надто агресивних повторів до апстріму
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/27 10:12:01 [error] 3112#3112: *9821 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.55, server: example, request: "GET / HTTP/2.0", upstream: "fastcgi://127.0.0.1:9000"
2025/12/27 10:12:03 [error] 3112#3112: *9838 FastCGI sent in stderr: "PHP message: WordPress database error Too many connections for query SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'"
Що це означає: Повільний апстрім спричинює таймаути веба; логи PHP показують помилки БД. Обидва — симптоми тієї самої черги.
Рішення: Зменшіть конкуренцію і виправте затримки БД. Розширення таймаутів Nginx без виправлення БД — шлях до довших відмов і більш розлючених користувачів.
Завдання 13: Знайдіть найгірші запити WordPress (включно повільний лог на вимогу)
cr0x@server:~$ mysql -e "SET GLOBAL slow_query_log=ON; SET GLOBAL long_query_time=0.5; SHOW VARIABLES LIKE 'slow_query_log_file';"
+---------------------+----------------------------------+
| Variable_name | Value |
+---------------------+----------------------------------+
| slow_query_log_file | /var/log/mysql/mysql-slow.log |
+---------------------+----------------------------------+
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-27T10:12:22.114Z
# Query_time: 3.218 Lock_time: 0.004 Rows_sent: 10 Rows_examined: 184221
SET timestamp=1766830342;
SELECT p.ID FROM wp_posts p
JOIN wp_postmeta pm ON pm.post_id=p.ID
WHERE pm.meta_key='some_key' AND pm.meta_value LIKE '%needle%'
ORDER BY p.post_date DESC LIMIT 10;
Що це означає: Rows examined — величезне число. Це вбивця з’єднань: кожен повільний запит займає потік і нарощує чергу.
Рішення: Додайте/відкоригуйте індекси, зменшіть meta-запити або замініть плагін. Потім знизьте поріг логування, щоб він не наповнив диск.
Завдання 14: Швидко перевірте таблиці/індекси на цілісність
cr0x@server:~$ mysql -e "CHECK TABLE wp_options, wp_posts, wp_postmeta QUICK;"
+-------------+-------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+-------------+-------+----------+----------+
| wpdb.wp_options | check | status | OK |
| wpdb.wp_posts | check | status | OK |
| wpdb.wp_postmeta | check | status | OK |
+-------------+-------+----------+----------+
Що це означає: «OK» виключає деякі сценарії корупції. Це не доводить, що продуктивність у порядку.
Рішення: Якщо бачите корупцію, не робіть імпровізованих ремонтів під час піку, якщо ви не знаєте межі впливу. Стабілізуйте, зробіть снапшот/резерв і потім ремонтуйте обдумано.
Завдання 15: Оцініть пам’ять на підключення (груба оцінка)
cr0x@server:~$ mysql -e "SHOW VARIABLES WHERE Variable_name IN ('read_buffer_size','read_rnd_buffer_size','sort_buffer_size','join_buffer_size','tmp_table_size','max_heap_table_size');"
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| join_buffer_size | 262144 |
| max_heap_table_size | 16777216 |
| read_buffer_size | 131072 |
| read_rnd_buffer_size | 262144 |
| sort_buffer_size | 262144 |
| tmp_table_size | 16777216 |
+----------------------+----------+
Що це означає: Буфери на підключення можуть виділятися за потребою. При високій конкуренції «за потребою» стає «відразу для всіх».
Рішення: Тримайте буфери на потік помірними. Не піднімайте їх, щоб «завалити» один запит; виправте запит.
Налаштування, які справді мають значення (і те, на що витрачати час не варто)
Почніть з обмеження: потоки MySQL — не безкоштовні
Кожне з’єднання споживає пам’ять і увагу планувальника. Підвищення max_connections збільшує потенційну лавину.
Коли потрібно його підвищити, робіть це по-діловому:
- Оцініть запас пам’яті спочатку (buffer pool + накладні витрати + OS cache + інші демони).
- Піднімайте по кроках (наприклад, +25% або +50%), не в 10 разів.
- Паркуйте це з upstream-лімітуванням (PHP-FPM max_children, обмеження веб-сервера).
InnoDB buffer pool: єдиний великий регулятор, який зазвичай окупається
Для виділених MySQL-хостів звичайна відправна точка — 60–75% від RAM для innodb_buffer_pool_size. Для шарених хостів це залежить від інших споживачів пам’яті.
Якщо робочий набір не вміщається, ви більше читаєте з диска. Дисковий I/O сповільнює запити. Повільні запити тримають з’єднання довше. Це порочне коло.
Таймаути: виправляйте вирівнювання, а не міф
wait_timeout — це скільки MySQL тримає просте неінтерактивне з’єднання. Якщо він занадто малий і ваш клієнт повторно використовує прості з’єднання, ви отримуєте «gone away». Якщо надто великий і у вас тисячі простих з’єднань, ви марнуєте пам’ять. Виберіть реалістичне значення і погодьте його з вашим середовищем.
Також врахуйте невидимі вбивці: брандмауери і балансувальники. Вони часто мають таймаути простою менші, ніж MySQL. Якщо вони мовчки обривають з’єднання, перша операція читання/запису потім викликає «gone away».
max_allowed_packet: чому завантаження вбиває БД
Файлові завантаження WordPress безпосередньо не йдуть через MySQL, але плагіни зберігають великі сериалізовані блоби, особливо в опціях і post meta. Якщо бачите помилки під час великих оновлень, підніміть max_allowed_packet до розумного (64M або 128M залежно від навантаження) і знайдіть плагін, що зберігає сміття.
Не витрачайте час на це під час інциденту
- Поради про старий query cache: Якщо хтось радить увімкнути старий MySQL query cache, вони подорожують у часі. У сучасному MySQL його немає.
- Різке збільшення буферів на потік: Це шлях до OOM-вбивств із впевненістю.
- Вимикання InnoDB flush safety: Можна пожертвувати надійністю заради швидкості, але робіть це усвідомлено — не тому, що хтось у коментарі так порадив.
Специфіка WordPress: характерні патерни відмов
Автозавантажувані опції як податок на базу даних
WordPress завантажує всі опції з autoload='yes' на початку. Якщо плагін скидає туди великі дані з автозавантаженням, кожний запит платить цю ціну. Це уповільнює запити і збільшує одночасне використання БД.
Виправлення: проведіть аудит wp_options, зменшіть автозавантажувальний мотлох і перемістіть великі кеші в object cache (Redis/Memcached) або файли.
Запити до wp_postmeta без індексів
Meta-запити гнучкі, але дорогі. Багато плагінів будують «пошукоподібні» фічі, використовуючи LIKE '%...%' по meta_value. Це не запит; це крик про допомогу.
Виправлення: обмежте мета-запити, додайте таргетовані індекси (обережно) або перенесіть цю функцію в пошуковий движок.
WP-Cron і шторми admin-ajax
Високий трафік може часто запускати WP-Cron, якщо він прив’язаний до трафіку. Тим часом admin-ajax.php часто використовують теми/плагіни для постійного опитування.
Виправлення: перенесіть cron на системний cron; обмежте частоту admin-ajax; кешуйте активно для анонімних користувачів.
Короткий жарт #2: «Too many connections» — це спосіб MySQL сказати, що йому треба менше нарад у календарі.
Три міні-історії з реального життя
Міні-історія 1: Відмова через неправильне припущення
Команда мала WordPress-структуру за балансувальником: два веб-вузла, один MySQL. Місяцями все було стабільно. Потім з’явився періодичний «server has gone away» — тільки на деяких сторінках, головним чином після того, як користувачі деякий час гортають і потім натискають щось.
Перше припущення було передбачуваним: «MySQL таймаутить.» Хтось запропонував підняти wait_timeout до годин. Інший порадив увімкнути персистентні з’єднання, щоб «не витрачати час на перепідключення». Звучало правдоподібно і було неправильним.
Справжня проблема була в мережевому шарі. Між веб-підмережею і БД стояв брандмауер з таймаутом простих TCP, меншим ніж у MySQL. Він мовчки обривав прості сесії. PHP потім намагався повторно використати ті з’єднання і отримував скиди. Логи MySQL показували «aborted connection», але MySQL не був тим, хто переривав.
Виправлення було нудним: вирівняти таймаути і увімкнути TCP keepalives на веб-вузлах, щоб прості з’єднання не вмирали, або краще — вимкнути персистентне повторне використання там, де воно не потрібне. Урок: коли бачите «gone away», перевірте, хто реально поклав слухавку першим.
Міні-історія 2: Оптимізація, що підвела
Інша компанія гналася за швидкістю. Вони зменшили TTFB сторінки, додавши агресивне масштабування PHP-FPM: велике pm.max_children, щоб сайт міг «витримувати сплески». Він витримував сплески — створюючи їх.
Під час маркетингової кампанії трафік виріс. PHP-FPM породив воркерів. Кожен воркер відкрив MySQL-з’єднання і виконав кілька запитів. MySQL почав відставати, запити тривали довше. Довші запити тримали воркерів зайнятими, тому пул породжував ще більше. З’єднання вдарили у стелю. «Too many connections» вистрілили. Потім додались повтори з боку додатку і деяких проксі.
Вони оптимізували не те: продуктивність веб-рівня замість кінцево-кільцевої пропускної здатності. Правильне виправлення не було «підняти max_connections до 2000». Треба було обмежити конкуренцію PHP до того, що база може обслуговувати з прийнятною затримкою, і кешувати анонімний трафік, щоб більшість запитів не доходили до MySQL.
Після налаштувань система витримала ту саму кампанію з меншим числом воркерів, менше DB-з’єднань і більше попадань в кеш. У синтетичному бенчмарку це виглядало повільніше, але для реальних користувачів було швидше. Це єдиний показник, що має значення.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Підприємство із WordPress працювало на MySQL з репліками. Нічого надзвичайного. Секрет — дисципліна: кожна зміна схеми планувалась, кожне оновлення плагіна проходило тестування на стенді, і лог повільних запитів був увімкнений у робочі години з адекватними порогами.
Одного дня плагін оновився і привніс новий патерн запитів до wp_postmeta. Не катастрофа. Просто повільніше. Але достатньо, щоб під час нормального трафіку кількість з’єднань почала рости. Он-кол помітив це завдяки алерту на «Threads_connected як відсоток від max_connections» і на «95-й перцентиль часу запитів» у метриках БД.
Вони не чекали повного відмови. Відкотили плагін, очистили автозавантажувальний мотлох, що прийшов з ним, і додали таргетований індекс у вікні обслуговування. Користувачі ледь помітили. Керівництво нічого не дізналося. Так виглядає успіх: профілактика настільки нудна, що з неї не роблять слайдів.
Поширені помилки: симптом → корінна причина → виправлення
1) «Server has gone away» після точно N секунд простою
- Симптом: Помилки з’являються, коли користувачі повертаються на сайт після простою; відтворюється зі секундоміром.
- Корінь: Несумістні таймаути (
wait_timeoutvs таймаут брандмауера/LB vs повторне використання клієнтом). - Виправлення: Вирівняйте таймаути; помірно підніміть
wait_timeout; вимкніть персистентні з’єднання, якщо є; налаштуйте TCP keepalives там, де доцільно.
2) «Too many connections» під час сплесків трафіку та високий iowait CPU
- Симптом: БД стає в’ялою, кількість з’єднань росте, iowait підскакує.
- Корінь: Промахи buffer pool + повільне сховище; запити чекають диска.
- Виправлення: Збільшіть InnoDB buffer pool (в межах RAM), покращте сховище, зменшіть вартість запитів, додайте кеш для анонімного трафіку.
3) «Too many connections» одразу після підняття PHP-FPM max_children
- Симптом: Більше воркерів «покращує» пропускну здатність на день, потім починаються інциденти.
- Корінь: Конкурентність upstream перевищує можливості БД; шторми з’єднань.
- Виправлення: Обмежте PHP-FPM до потужностей БД; додайте черги та зворотний тиск; розгляньте окрему репліку для читань, якщо додаток підтримує розподіл читань/записів.
4) «Server has gone away» під час збереження постів або оновлення плагінів
- Симптом: Адмін-дії відпадають; логи показують помилки пакетів або aborted connections.
- Корінь:
max_allowed_packetзанадто малий або довгі записи під час таймауту. - Виправлення: Підніміть
max_allowed_packet; перевірте, які дані зберігаються; зупиніть плагіни, що заштовхують великі блоби в опції.
5) «Error establishing a database connection» періодично
- Симптом: Генерична помилка підключення WordPress, не завжди «too many connections».
- Корінь: MySQL рестарти, проблеми DNS, виснаження сокетів або throttle аутентифікації.
- Виправлення: Перевірте uptime MySQL і логи крашів; зафіксуйте хост БД по IP, якщо DNS ненадійний; перевірте ліміти файлових дескрипторів; аудит аутентифікаційних невдач.
6) Підняття max_connections призводить до падіння хоста
- Симптом: Менше помилок «too many connections», але затримки зростають; потім mysqld вбивають OOM.
- Корінь: Пам’ять на підключення + накладні витрати на потоки; свопінг або OOM.
- Виправлення: Поверніться до безпечнішого max_connections; обмежте PHP; зменшіть буфери на потік; додайте кеш; масштабуйтесь вертикально або перенесіть DB на виділений хост/керований сервіс.
Контрольні списки / покроковий план
Чекліст реагування на інцидент (15–30 хвилин)
- Підтвердіть стан БД: перевірте
systemctl status mysqlі час роботи. - Перевірте OOM/диск: dmesg на OOM-рядки;
df -hіdf -ih. - Виміряйте насичення:
Threads_connectedvsmax_connections. - Знайдіть винуватців: processlist; топ користувачів/хостів; довгі запити.
- Застосуйте зворотний тиск: зменшіть конкуренцію PHP-FPM (або тимчасово вимкніть один веб-вузол) перед зміною налаштувань БД.
- Увімкніть повільний лог на короткий час: зафіксуйте шаблони запитів; не залишайте його з низьким порогом назавжди.
- Стабілізуйте: підтвердіть зменшення числа помилок; слідкуйте за затримками; переконайтесь, що MySQL більше не рестартує.
План стабілізації (на наступний день)
- Виправте повільні запити: індекси, зміни плагінів, переписування запитів.
- Аудит wp_options autoload: приберіть мотлох, відключіть винуватців, перемістіть кеш з БД.
- Вирівняйте таймаути: MySQL, PHP, веб-сервер, балансувальник, брандмауер.
- Підіберіть buffer pool: за RAM і робочим навантаженням, а не за фольклором.
- Додайте моніторинг: використання з’єднань, затримка запитів, метрики InnoDB, ріст диска, події OOM.
План посилення (на наступний спринт)
- Розділення ролей: розмістіть MySQL на виділеному хості або керованому сервісі, якщо можете.
- Кешування анонімного трафіку: повносторінковий кеш + object cache, щоб зменшити навантаження на БД.
- Навмисне обмеження конкуренції: встановіть PHP-FPM
pm.max_children, щоб захистити MySQL. - Операційна гігієна: планові зміни схеми, політика оновлення плагінів і план відкату, що не спирається на молитви.
Питання та відповіді
1) Чи просто підвищити max_connections, щоб вирішити «Too many connections»?
Тільки як тимчасова міра розвантаження і лише після перевірки запасу пам’яті. Стійке рішення — зменшення часу виконання запитів і upstream-конкуренції.
2) Який найшвидший спосіб зупинити відмову прямо зараз?
Обмежте конкуренцію на веб-рівні (PHP-FPM max_children або виведіть з обігу веб-вузол), щоб MySQL встиг наздогнати. Потім шукайте повільні запити.
3) Чому я бачу «server has gone away», але MySQL виглядає живим?
Тому що з’єднання можуть переривати брандмауери, балансувальники, NAT-шлюзи або таймаути клієнта. Перевіряйте таймаути і мережеві скиди, а не тільки uptime mysqld.
4) Це викликано самим WordPress чи плагіном?
Ядро WordPress зазвичай не є прямою причиною. Плагіни часто створюють патологічні запити (особливо мета-запити) або засмічують автозавантажувані опції.
5) Чи допомагають персистентні MySQL-з’єднання продуктивності WordPress?
Іноді, але вони також можуть посилити помилки «gone away» і накопичення з’єднань. У більшості сучасних конфігурацій виправлення вартості запитів і кешування дає більше користі.
6) Які значення таймаутів мені використовувати?
Універсальної відповіді немає. Але wait_timeout=60 часто занадто мало для багатошарових мереж. Почніть з 300–900 секунд і вирівняйте з таймаутами мережевих пристроїв.
7) Чому це відбувається переважно під час адмін-дій?
Адмін-дії викликають важчі запити, записи та іноді зміни схеми. Також вони виявляють обмеження max_allowed_packet, коли оновлюються великі опції.
8) Чи допоможе репліка вирішити «too many connections»?
Лише якщо ваша аплікація справді відправляє читання на репліку. Класичний WordPress без додаткових інструментів не розділяє читання/записи автоматично. Зазвичай першим виграшем є кешування.
9) Як зрозуміти, чи I/O диска — справжнє вузьке місце?
Шукайте високий iowait, повільні запити, що корелюють з дисковою активністю, очікування скидання InnoDB та низькі показники попадання в buffer pool. Якщо сховище повільне, все інше — видовищність.
10) Чи MariaDB відрізняється тут?
Режими відмов подібні: ліміти з’єднань, таймаути, витрати пам’яті/потоків і повільні запити. Точні змінні і дефолти можуть відрізнятися, тож перевірте вашу версію.
Висновок: практичні наступні кроки
Ці помилки MySQL — не містика. Це звіти про ресурси: ви вичерпали з’єднання, час, пам’ять або терпіння десь у стеку.
- Сьогодні: пройдіть швидкий план діагностики, обмежте конкуренцію PHP, підтвердіть, що MySQL не рестартує, і зафіксуйте повільні запити.
- Цього тижня: видаліть або виправте гірші патерни запитів (часто спричинені плагінами), скоротіть автозавантажувальні опції і вирівняйте таймаути між MySQL та мережею.
- У цьому спринті: додайте кешування, встановіть явні обмеження потужності і налаштуйте моніторинг/сповіщення за використанням з’єднань і затримками запитів, щоб ви бачили обрив до того, як злетіти з нього.
Якщо зробити одну культурну зміну: перестаньте трактувати «max_connections» як ручку продуктивності. Це запобіжник. Розрахуйте його уважно і спроектуйте стек так, щоб він рідко був критичним.