WordPress 504 Gateway Timeout: база даних чи PHP? Як це довести

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

504 — це найгірший тип простою: не чистий збій і не приємна сторінка з помилкою, а проксі, який знизав плечима перед вашими клієнтами, поки в Slack з’являються повідомлення «сайт крутиться». WordPress додає сюди ще більше складності, бо збій може бути водночас у трьох місцях: веб‑проксі, PHP‑FPM і базі даних, і всі звинувачують одне одного.

Це підхід, орієнтований на продакшн, щоб довести, чи ваш 504 Gateway Timeout походить від шару бази даних (MySQL/MariaDB) або шару PHP (PHP-FPM / mod_php). Не здогадки. Не «я перезапустив(ла) і пішло». Докази, які можна вставити в канал інциденту й прийняти рішення.

Єдина модель мислення для 504

504 Gateway Timeout майже ніколи не є реальною причиною. Це посланець. Проксі (Nginx, Apache як зворотний проксі, Cloudflare, балансувальник) дочекався відповіді від апстріму й втратило терпіння.

У типовому хостингу WordPress шлях запиту виглядає так:

  • Клієнт → CDN/WAF (опціонально) → Nginx/Apache (зворотний проксі)
  • Проксі → обробник PHP (PHP-FPM або mod_php)
  • PHP → база даних (MySQL/MariaDB) та інші залежності (Redis, зовнішні API, SMTP, платіжні шлюзи)
  • Відповіді йдуть тим же шляхом назад

Отже, коли ви отримуєте 504, питання: хто не відповів вчасно? Цим «хто» може бути PHP (завис, повільний, насичений) або база даних (повільні запити, блокування, проблеми з IO), або обидва в ланцюжку причин (DB повільна → PHP‑воркери накопичуються → проксі таймаутиться).

Ось правило для продакшн‑роботи, яке економить години: 504 — це проблема чергування, доки не доведено інше. Щось ставить у чергу: запити чекають PHP‑воркерів, PHP‑воркери чекають на DB, DB чекає диска або все чекає на блокування.

Короткий жарт: 504 — це як зустріч, що «закінчилася по часу» — ніхто не визнає, що не зробив роботу, але всі погоджуються перенести.

Що означає «проблема бази даних» (операційно)

«База даних повільна» — це не діагноз. Операційно це означає одне з:

  • Запити повільні через сканування занадто великого обсягу (відсутні індекси, погані шаблони запитів).
  • Запити повільні через блокування в БД (locks, metadata locks, довгі транзакції).
  • Запити повільні через неможливість читати/писати досить швидко (IO насичення, fsync‑стали).
  • Запити повільні через обмеження CPU в БД (складні сортування, regex, важкі join).
  • Запити повільні через вузьке місце підключень (max_connections, thread pool, connection storms).

Що означає «проблема PHP» (операційно)

«PHP повільний» зазвичай означає одне з:

  • Пул PHP-FPM насичений (усі воркери зайняті; запити ставляться в чергу).
  • Воркери зависають (заглухання в коді, зовнішні API з довгими таймаутами, проблеми з DNS).
  • Воркери вмирають / перезапускаються (OOM kills, max_requests занадто малий, витік пам’яті).
  • Відсутній/неправильно налаштований opcode cache (кожен запит занадто багато компілює коду).
  • Файлові операції повільні (NFS‑зависання, EBS burst credits, перевантажене сховище).

Трюк не в дебаті цих теорій. Трюк — зібрати достатньо сигналів, щоб звинуватити один шар.

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

Це послідовність «у мене 10 хвилин до того, як менеджмент дізнається, що статус‑сторінка теж на WordPress». Ви не оптимізуєте; ви визначаєте вузьке місце і зупиняєте кровотечу.

1) Почніть на краю: підтвердіть, що це таймаут оригіну, а не каприз CDN

  • Якщо Cloudflare/ALB повертає 504, перевірте, чи досяжний origin і чи проксі‑логи показують upstream timeouts.
  • Якщо 504 лише на деяких сторінках, підозрюйте застосунок/базу даних. Якщо всі сторінки 504 включно зі статичними ресурсами — підозрюйте веб/проксі або мережу.

2) Перевірте лог помилок проксі на деталі upstream таймауту

  • Nginx буквально скаже: «upstream timed out» (PHP не відповів) або «connect() failed» (PHP впав).
  • Це ще не доводить DB vs PHP; це доводить «PHP не відповів проксі». Далі запитайте: чи PHP чекав на DB?

3) Перевірте PHP-FPM: довжина черги та насичення max_children

  • Якщо у PHP-FPM накопичується listen queue і процеси завантажені до max_children, PHP насичений (причина може бути все ще у БД).
  • Якщо у PHP є вільні воркери, але запити все одно таймаутяться, шукайте завислі виклики (DB‑блокування, зовнішні API, файлові зависання).

4) Перевірте MySQL/MariaDB: активні запити, очікування блокувань і спайки slow query

  • Якщо бачите багато потоків «Waiting for table metadata lock» або довго виконувані SELECT/UPDATE — у вас конкуренція в БД.
  • Якщо бачите високе InnoDB fsync/IO wait і зростання часу запитів — підозрюйте сховище.

5) Зробіть стабілізаційний крок, а не випадковий крок

  • Якщо БД заблокована: вбийте блокуючу транзакцію, а не всю БД (якщо не хочете збільшувати простій).
  • Якщо PHP насичений: тимчасово масштабуйтесь по PHP‑воркерам лише якщо БД може це витримати; інакше ви просто DDoS‑ите власну базу даних.
  • Якщо один плагін ендпоінт нагріває систему: обмежте частоту або тимчасово вимкніть його.

Цікаві факти та коротка історія (чому 504 виглядають так)

  1. 504 визначений в HTTP‑специфікації як «Gateway Timeout» — він явно про проміжні ланки (проксі/шлюзи), що таймаутяться, а не про застосунок, який вирішив таймаутитись.
  2. Nginx популяризував мову «upstream» в логах і документації, і це формувало спосіб дебагу: «який upstream?» стало першим питанням.
  3. PHP‑FPM став дефолтним для багатьох стеків WordPress, бо ізолює процеси PHP, дає керування пулами (max_children) і уникає пам’ятного надування Apache prefork на навантажених сайтах.
  4. MySQL з InnoDB витіснив MyISAM для більшості інсталяцій WordPress через блокування на рівні рядка і відновлення після збоїв при паралельних записах (коментарі, кошики, сесії).
  5. Схема WordPress навмисно загальна (postmeta, usermeta таблиці ключ/значення). Гнучка, так. Але це й «вогнепальне» місце для продуктивності, якщо робити запити по meta без індексів.
  6. Повільні запити не завжди помітні як повільні сторінки, поки не зросте конкурентність. Один 1‑секундний запит дратує. А 200 одночасних 1‑секундних запитів стають DoS, за який ви платите.
  7. За замовчуванням таймаути рідко узгоджені: CDN timeout, proxy timeout, fastcgi timeout, PHP max_execution_time і MySQL net_read_timeout можуть не збігатися. Несумісні налаштування дають «містичні» 504.
  8. Зміни індексів можуть блокувати таблиці довше, ніж ви очікуєте, особливо для великих таблиць і певних ALTER TABLE операцій. Це проявляється як раптові metadata locks і каскадні 504.
  9. Історично «просто додати воркерів» працювало, коли CPU були дешеві і навантаження на БД було малим. На масштабі такий підхід перетворюється на самостворений thundering herd проти бази даних.

Як виглядає «доведення»: встановлення вини без емоцій

«Це база даних» — це твердження. «Це PHP» — теж твердження. Доведення — це ланцюжок відмічених часом подій і корельованих доказів, що показують, де витрачається час.

У чистому звіті про інцидент вам потрібно як мінімум два незалежні сигнали, які вказують на той самий винуватець:

  • Докази з рівня проксі: upstream timeouts, часи відповіді апстріму, співвідношення 499/504, сплески помилок.
  • Докази з рівня PHP: статус PHP‑FPM (active/idle, max_children досягнуто), slowlog зі стек‑трасами, CPU‑витрата воркерів, довжина черги.
  • Докази з рівня БД: slow query log у вікні інциденту, очікування блокувань, довгі транзакції, IO‑waits, висока кількість Threads_running.
  • Докази хоста: CPU steal, load average vs runnable threads, iowait, затримки диска, мережеві ретрансміти.

Також не ігноруйте ланцюг залежностей: PHP може бути тим, що таймаутиться, тоді як БД — тим, хто це спричинив. Ваше завдання — знайти перший обмежений ресурс у ланцюгу.

Другий короткий жарт: Перезапуск сервісів, щоб виправити 504 — наче додаєш гучності радіо, щоб виправити дивний звук двигуна — змінює відчуття, а не фізику.

Мислення надійності (одна цитата)

Сподівання — не стратегія. — часто приписується операційній культурі; сприймайте як перефразовану ідею для надійності.

Переклад: збирайте докази, потім дійте.

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

Це реальні завдання, які ви можете виконати на типовому Linux‑хості з WordPress. Кожне включає: команду, що означає вивід, і яке рішення прийняти далі. Виконуйте їх по порядку, якщо панікуєте; відбирайте, якщо спокійні.

Завдання 1: Підтвердити 504 та виміряти, де витрачається час (з боку клієнта)

cr0x@server:~$ curl -sS -o /dev/null -w 'code=%{http_code} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
code=504 ttfb=60.001 total=60.002

Значення: Time‑to‑first‑byte (TTFB) майже дорівнює загальному часу, і запит помирає на ~60s. Це пахне проксі‑таймаутом, а не сторінкою з помилкою, згенерованою застосунком.

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

Завдання 2: Перевірте лог помилок Nginx на «upstream timed out» (доказ з проксі)

cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/26 11:18:41 [error] 1842#1842: *991 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "example.com"

Значення: Nginx чекав на PHP‑FPM і не отримав заголовків відповіді вчасно. Це доводить, що проксі не є повільною частиною; він чекає.

Рішення: Перевірте насичення PHP‑FPM і повільні запити. Це ще не повна провина БД; PHP може чекати на БД.

Завдання 3: Підтвердіть налаштування fastcgi timeout в Nginx (щоб не гнатися за примарами)

cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -E 'fastcgi_read_timeout|proxy_read_timeout|send_timeout' | head
fastcgi_read_timeout 60s;
send_timeout 60s;

Значення: Порог у 60 секунд відповідає вашому curl‑таймінгу. Добре. Ви дебажите в правильному часовому вікні, а не через якийсь timeout на клієнті.

Рішення: Поки не підвищуйте таймаути. Спочатку з’ясуйте, чому щось займає >60 секунд. Підвищення таймаутів приховає відмови і збільшить черги.

Завдання 4: Перевірте статус прослуховування PHP-FPM (чи слухає він?)

cr0x@server:~$ sudo ss -lxp | grep php-fpm
u_str LISTEN 0 128 /run/php/php8.2-fpm.sock  44123  * 0 users:(("php-fpm8.2",pid=1640,fd=9))

Значення: PHP‑FPM слухає. Якщо цей рядок відсутній — PHP‑FPM не працює або шлях сокета інший.

Рішення: Якщо відсутній: відновіть сервіс PHP‑FPM. Якщо присутній: перевірте, чи немає насичення і чергування.

Завдання 5: Перевірте налаштування pm.max_children у PHP-FPM (класичне вузьке місце)

cr0x@server:~$ sudo grep -R "pm.max_children" /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:pm.max_children = 20

Значення: У вас максимум 20 одночасних PHP‑запитів у цьому пулі. Це може бути нормально або катастрофічно мало, залежно від часу обробки запиту й трафіку.

Рішення: Далі перевірте, чи ці 20 усі зайняті і чи запити ставляться в чергу.

Завдання 6: Прочитайте логи PHP-FPM на предмет «server reached pm.max_children»

cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[26-Dec-2025 11:18:12] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[26-Dec-2025 11:18:13] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it

Значення: PHP‑FPM насичений. Запити ставляться в чергу. Це міцний доказ того, що є обмеження на боці PHP.

Рішення: Визначте, чи PHP повільний через власну CPU‑роботу, чи чекає на DB/IO. Сліпе підвищення max_children може знищити БД.

Завдання 7: Перевірте кількість процесів PHP-FPM і використання CPU

cr0x@server:~$ ps -o pid,pcpu,pmem,etime,cmd -C php-fpm8.2 --sort=-pcpu | head
  PID %CPU %MEM     ELAPSED CMD
 1721 62.5  2.1       01:12 php-fpm: pool www
 1709 55.2  2.0       01:11 php-fpm: pool www
 1698 48.9  1.9       01:10 php-fpm: pool www

Значення: Якщо воркери показують високий CPU — PHP може виконувати важку роботу (або застряг у tight loops). Якщо CPU низький, але ELAPSED довгий — вони, ймовірно, чекають на IO (БД, диск, мережа).

Рішення: Якщо CPU високий: профілюйте/оптимізуйте PHP або зменшіть роботу (плагіни, кешування). Якщо CPU низький, але час великий: перевірте БД і IO далі.

Завдання 8: Увімкніть або прочитайте PHP-FPM slowlog для стек‑трас повільних запитів

cr0x@server:~$ sudo grep -R "slowlog\|request_slowlog_timeout" /etc/php/8.2/fpm/pool.d/www.conf
request_slowlog_timeout = 10s
slowlog = /var/log/php8.2-fpm.slow.log
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.slow.log
[26-Dec-2025 11:18:39]  [pool www] pid 1721
script_filename = /var/www/html/index.php
[0x00007f2f0c...] mysqli_query() /var/www/html/wp-includes/wp-db.php:2056
[0x00007f2f0c...] query() /var/www/html/wp-includes/wp-db.php:1945
[0x00007f2f0c...] get_results() /var/www/html/wp-includes/wp-db.php:2932

Значення: Це «димлячий пістолет», коли з’являється: PHP повільний, бо він всередині виклику до бази даних. Якщо ви бачите curl_exec(), file_get_contents() або DNS‑функції — винуватець інший.

Рішення: Якщо slowlog показує DB‑виклики: переходьте до діагностики MySQL негайно. Якщо там зовнішні HTTP‑виклики: ізолюйте цей плагін/сервіс і додайте таймаути/циркуїт‑брекери.

Завдання 9: Перевірте стани потоків MySQL (блокування і довгі запуски)

cr0x@server:~$ sudo mysql -e "SHOW FULL PROCESSLIST\G" | egrep -A2 "State:|Time:|Info:" | head -n 40
Time: 58
State: Waiting for table metadata lock
Info: ALTER TABLE wp_postmeta ADD INDEX meta_key (meta_key)
Time: 55
State: Sending data
Info: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts LEFT JOIN wp_postmeta ...

Значення: «Waiting for table metadata lock» — червоний прапор: зміна схеми або довгий DDL блокує читання/записи. «Sending data» довго вказує на великі скани або повільний IO.

Рішення: Якщо metadata lock: знайдіть і вбийте блокувальну DDL або заплануйте її правильно. Якщо довгі скани: перевірте slow query log, індекси і тиск на buffer pool.

Завдання 10: Перевірте InnoDB status на очікування блокувань і IO‑стали

cr0x@server:~$ sudo mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
=====================================
2025-12-26 11:18:45 0x7f0a4c2
TRANSACTIONS
------------
Trx id counter 12904421
Purge done for trx's n:o < 12904400 undo n:o < 0 state: running
History list length 1987
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 12904388, ACTIVE 62 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 418, OS thread handle 139682..., query id 9812 10.0.0.15 wpuser updating
UPDATE wp_options SET option_value='...' WHERE option_name='woocommerce_sessions'

Значення: Довгі активні транзакції і зростання history list length вказують на відставання purge і потенційну конкуренцію. Оновлення «гарячих» таблиць (options, sessions) часто викликає накопичення.

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

Завдання 11: Увімкніть і перегляньте MySQL slow query log (корельований доказ)

cr0x@server:~$ sudo mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+------------------------------+
| Variable_name       | Value                        |
+---------------------+------------------------------+
| slow_query_log      | ON                           |
| slow_query_log_file | /var/log/mysql/mysql-slow.log|
+---------------------+------------------------------+
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+
cr0x@server:~$ sudo tail -n 25 /var/log/mysql/mysql-slow.log
# Time: 2025-12-26T11:18:21.123456Z
# Query_time: 12.302  Lock_time: 0.000 Rows_sent: 10  Rows_examined: 2450381
SELECT * FROM wp_postmeta WHERE meta_key = '_price' ORDER BY meta_value+0 DESC LIMIT 10;

Значення: Rows_examined у мільйонах для простого запиту — саме те, що перетворює трафік у таймаути. Це твердий доказ провини БД з відміченим часом.

Рішення: Додайте/підкоригуйте індекси, перепишіть запити (часто викликані плагінами) або введіть кешування/пошук. Також перевірте, чому цей запит сплескнув зараз (новий плагін, функція, кампанія).

Завдання 12: Спостерігайте в реальному часі поточні потоки й запити MySQL (DB тоне?)

cr0x@server:~$ sudo mysqladmin extended-status -ri 2 | egrep "Threads_running|Questions|Slow_queries"
Threads_running            34
Questions                  188420
Slow_queries               912
Threads_running            37
Questions                  191102
Slow_queries               925

Значення: Зростання Threads_running під навантаженням означає накопичення конкурентності всередині MySQL. Якщо Threads_running залишається низьким, а PHP таймаутиться, можливо БД не є вузьким місцем.

Рішення: Якщо Threads_running високий: зменшіть вартість запитів і конкуренцію; розгляньте read replicas для читально‑інтенсивних ендпоінтів. Якщо низький: зосередьтесь знову на PHP/зовнішніх викликах/сховищі.

Завдання 13: Перевірте iowait і затримки диска на хості (сховище часто тихий винуватець)

cr0x@server:~$ iostat -xz 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.34    0.00    4.12   28.90    0.00   54.64

Device            r/s     w/s   rKB/s   wKB/s  await  svctm  %util
nvme0n1         120.0   210.0  6400.0  8200.0  48.20   1.10  98.00

Значення: iowait близько 30% і %util диска ближче до 100% із високим await означає насичення сховища. MySQL сповільниться навіть якщо CPU виглядає нормальним.

Рішення: Якщо сховище — вузьке місце: виправляйте IO (швидший диск, кращі IOPS, зменшення write amplification, налаштування InnoDB, переміщення tmpdir, зменшення обсягу логів). Не просто додавайте PHP‑воркерів.

Завдання 14: Перевірте OOM kills або тиск ядра (тихий PHP‑спіра́ль)

cr0x@server:~$ sudo journalctl -k -n 50 | egrep -i "oom|killed process" | tail
Dec 26 11:17:59 server kernel: Out of memory: Killed process 1721 (php-fpm8.2) total-vm:1234567kB, anon-rss:456789kB, file-rss:0kB, shmem-rss:0kB

Значення: Якщо PHP‑воркери OOM‑вбиваються, проксі бачить таймаути і ресети. Це може виглядати як «випадкові 504».

Рішення: Зменшіть використання пам’яті PHP (плагінова надмірність), обмежте пам’ять на процес, відрегулюйте розмір пулу, додайте RAM і переконайтеся, що swap не спричиняє падіння продуктивності.

Завдання 15: Підтвердіть, що WordPress debug logging не погіршує ситуацію

cr0x@server:~$ grep -n "WP_DEBUG" /var/www/html/wp-config.php
90:define('WP_DEBUG', false);
91:define('WP_DEBUG_LOG', false);

Значення: Увімкнений WP_DEBUG_LOG на зайнятому сайті може створювати інтенсивні записи на диск, перетворюючи невелику проблему на IO‑контенцію.

Рішення: Тримайте debug logging вимкненим у продакшні за замовчуванням; вмикайте тимчасово з короткими вікнами і ротацією логів, коли потрібно.

Завдання 16: Доведіть, чи PHP чекає на БД за допомогою strace (точково, не для слабкодухих)

cr0x@server:~$ sudo strace -p 1721 -tt -T -e trace=network,read,write,poll,select -s 80
11:18:40.101203 poll([{fd=12, events=POLLIN}], 1, 60000) = 0 (Timeout) <60.000312>

Значення: PHP‑воркер заблокований в poll/select на 60 секунд — він чекає мережевого IO — часто сокета бази даних або зовнішнього HTTP‑сервісу.

Рішення: Якщо це DB‑сокет — фокусуйтеся на MySQL. Якщо це зовнішня IP‑адреса — виправте цю інтеграцію (таймаути, повтори, circuit‑breaking, кешування).

Тепер у вас є набір інструментів, щоб довести, де витрачається час. Далі: розпізнавання патернів, бо сигнали групуються передбачувано.

База даних проти PHP: сигнали, що їх розрізняють

Паттерн A: досягнуто PHP-FPM max_children + PHP slowlog показує mysqli‑виклики

Найімовірніший винуватець: затримки або блокування в базі даних, через які PHP‑воркери накопичуються.

Як це виглядає:

  • Nginx: «upstream timed out while reading response header from upstream»
  • PHP‑FPM лог: «server reached pm.max_children»
  • PHP slowlog: стек‑трасування в wp-db.php / mysqli_query()
  • MySQL: підвищений Threads_running; сплески у slow query log; processlist показує довгі запити або очікування блокувань

Що робити: вважайте DB коренем проблеми. Спочатку зменшіть навантаження на DB, потім коригуйте PHP‑конкуренцію.

Паттерн B: досягнуто PHP-FPM max_children + воркери PHP високий CPU + DB виглядає спокійною

Найімовірніший винуватець: робота на рівні PHP (цикли у шаблонах, важка логіка плагінів, обробка зображень, промахи кешування, погане object caching).

Як це виглядає:

  • PHP‑воркери показують високий %CPU і довгий ELAPSED
  • MySQL Threads_running помірні, slow query log не сплескає
  • Таймаути часто на конкретних ендпоінтах (search, admin-ajax, фільтри продуктів)

Що робити: ізолюйте ендпоінт, додайте кешування, увімкніть OPcache і профілюйте вибірково (без повного трасування під час інциденту).

Паттерн C: у PHP є вільні воркери, але запити все одно 504

Найімовірніший винуватець: невідповідність конфігурації проксі, backlog сокета PHP, проблеми з апстрім‑підключенням або щось поза PHP/БД (DNS, зовнішній API, файловий доступ).

Як це виглядає:

  • Nginx помилки можуть показувати connect() failed, recv() failed або випадкові upstream resets
  • Логи PHP‑FPM можуть показувати child exited, segfault або нічого
  • Логи хоста можуть показувати OOM kills, зависання диска або мережеві проблеми

Що робити: перевірити сокети, backlog, kernel limits і зовнішні залежності; не зациклюватися лише на MySQL.

Паттерн D: MySQL Threads_running високі + iowait високий + disk await високий

Найімовірніший винуватець: сховище обмежує БД, що обмежує PHP і призводить до 504.

Що робити: виправляти IO. Іноді «проблема БД» означає «ми купили найдешевший диск».

Паттерн E: раптові очікування блокувань, особливо metadata locks

Найімовірніший винуватець: DDL у піковий час, міграції плагінів або «швидка зміна індексу» у продакшні без урахування блокувань.

Що робити: зупинити DDL, перенести на нічне вікно з online schema change інструментами і запровадити запобіжники.

Три міні‑історії з практики

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

У них був маркетинговий сайт на WordPress плюс магазин WooCommerce, розміщені на «доволі потужній» віртуалці. Під час запуску кампанії раптово пішли 504. Інженер on‑call глянув у лог Nginx і побачив upstream timeouts до PHP‑FPM. Він упевнено вирішив, що PHP‑FPM потрібно більше воркерів.

Вони підняли pm.max_children. 504 стали гірші. CPU бази даних підскочив, потім затримки диска виросли, і сайт почав падати в ще дивніших способах. Тепер проблеми були не лише на сторінці оформлення — таймаути почалися й на головній.

Найсправжнішим винуватцем був один шаблон запиту, доданий віджетом «фільтрувати продукти за ціною». Він використав postmeta так, що сканував величезні діапазони, і цей запит запускався на кожному перегляді категорії. База даних якось витримувала, коли конкурентність була обмежена. Збільшення PHP‑воркерів призвело до більшої кількості одночасних дорогих запитів. БД досягла IO‑насичення. Черги вибухнули по всьому стеку.

Їх стабілізували відкатом віджету, очищенням кешів і поверненням PHP‑конкуренції до розумних рівнів. Постмортем не був про «не масштабувати PHP». Він був про те, щоб не робити припущень «PHP‑таймаут = проблема PHP». PHP був жертвою. База даних — місце злочину.

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

Команда вирішила «спростити деплоймент» і перемістила завантаження WordPress та частину кодової бази на мережеву файлову систему для двох веб‑нод. У тестах все працювало. Потім у продакшні при підвищеному трафіку почалися 504 — спочатку спорадично, потім в кореляції із сплесками (email‑кампанії, фічи на головній).

Усе здавалося нормальним: MySQL Threads_running не був шалено високим, CPU не був завантажений, PHP‑FPM мав резерви. Але запити все одно зависали довше, ніж Nginx давав шанс. Хтось наполягав, що «це точно база даних», бо WordPress завжди про базу. Це припущення протрималося півдня.

Переломний момент — лог PHP‑FPM slowlog: стек‑трасування вказували на файлові операції та шляхи автозавантаження, а не на mysqli. Одночасно метрики хоста показали сплески iowait. Мережевий файловий сервіс мав періодичні латентності та інколи ретрансміти. PHP‑воркери були майже без CPU, заблоковані на читанні файлів.

«Оптимізація» знизила боль при деплойменті, але додала нову латентну залежність у кожному запиті. Вирішення було буденним: локальна файловa система для коду, об’єктне сховище для uploads з агресивним кешуванням, і механізм деплойменту без спільної POSIX‑семантики. Продуктивність повернулася миттєво, а база даних — на диво — була в порядку.

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

Інша компанія розміщувала WordPress у більшій платформі. Вони не були ідеальними, але мали одну звичку, яка виглядала майже давньою: у кожного шару були стандартні дашборди і логи, і таймаути були узгоджені. Proxy timeout, fastcgi timeout, PHP max_execution_time, DB timeouts — все записане. Все узгоджено.

Одного дня вони побачили підвищення 504. Перший відповідальний перевірив Nginx error logs: upstream timeouts. Далі перевірили PHP‑FPM: max_children не був вичерпаний, але slowlog показав wp‑db виклики. Вони перейшли до MySQL і відразу побачили очікування блокувань навколо плагіна міграції, який почав ALTER TABLE на гарячій таблиці.

Тому що у них була нудна практика — увімкнений slow query log з адекватними порогами і календар змін — вони знали, що і коли змінилося. Вони зупинили міграцію, перенесли її на позачас з інструментами мінімальних блокувань, і 504 зникли. Жодних випадкових перезапусків, жодного «масштабуйте все», жодного тижневого свята звинувачень.

Їхній найбільший плюс не в спостережуваності високого класу. Він у послідовності: узгоджені таймаути і завжди доступні базові сигнали. У реакції на інциденти нудність — це фіча.

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

Це шаблони, що марнують найбільше часу, бо вони інтуїтивні і неправі у продакшні.

1) Симптом: «Nginx показує upstream timed out, отже PHP зламався.»

Корінь: PHP в порядку; він чекає на MySQL‑блокування чи повільні запити.

Виправлення: Використайте PHP‑FPM slowlog, щоб підтвердити, де воно застрягає. Якщо це в wp-db.php — йдіть прямо в MySQL processlist і InnoDB status; розв’яжіть блокування і вартість запитів.

2) Симптом: «Давайте піднімемо fastcgi_read_timeout, щоб клієнти не бачили 504.»

Корінь: Ви маскуєте латентність; черги ростуть; зрештою все колапсує і ви отримаєте таймаути, просто повільніше.

Виправлення: Тримайте таймаути жорсткими, щоб швидко виокремлювати відмови. Зменшуйте хвостову латентність, фіксуйте вузьке місце і додавайте кешування/лімітування запитів там, де потрібно.

3) Симптом: «Підняття pm.max_children зробило гірше.»

Корінь: База даних була обмеженням; більше PHP‑конкуренції збільшило контенцію й IO у DB.

Виправлення: Розглядайте PHP‑конкуренцію як генератор навантаження. Підбирайте її під можливості DB. Зменшуйте дорогі запити, додавайте індекси і кешуйте гарячі дані.

4) Симптом: «Лише wp‑admin 504, фронтенд в порядку.»

Корінь: Адмінські сторінки часто роблять важчі запити (списки постів з фільтрами), перевірки плагінів і cron‑подібну поведінку.

Виправлення: Захопіть slowlog для адмінських ендпоінтів. Перевірте admin‑ajax гарячі цикли і плагіни. Додайте object caching і проведіть аудит плагінів.

5) Симптом: «504 з’являються хвилями після сплесків трафіку.»

Корінь: Колапс черг: пропуски кешу, stampede або connection storms до БД.

Виправлення: Впровадьте кешування зі захистом від stampede, впевніться, що persistent DB‑з’єднання розумні, і лімітуйте шкідливі ендпоінти (xmlrpc, wp-login, admin-ajax).

6) Симптом: «CPU бази даних низький, отже БД не може бути проблемою.»

Корінь: БД може бути IO‑обмеженою або блокованою, а не CPU‑обмеженою.

Виправлення: Дивіться на iowait, disk await, buffer pool hit rate і lock waits. CPU — лише один із шляхів до деградації.

7) Симптом: «Slow query log порожній, отже запити не повільні.»

Корінь: slow_query_log вимкнено, long_query_time занадто велике, або проблема в блокуваннях (Lock_time може бути великим, тоді як Query_time здається помірним залежно від конфігурації логування).

Виправлення: Увімкніть slow query log з реалістичним порогом (часто 0.5–1s для зайнятих сайтів). Корелюйте з lock waits і тривалістю транзакцій.

8) Симптом: «Пройшло після перезапуску, отже це витік пам’яті.»

Корінь: Перезапуск спустошує черги і знімає блокування; тригер не виправлено (трафік, регрес запитів, DDL, зовнішній API stall).

Виправлення: Розглядайте перезапуски як тимчасове пом’якшення. Захоплюйте докази перед перезапуском: slowlog, processlist, iostat, error logs.

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

Поетапно: довести DB vs PHP за менш ніж 30 хвилин

  1. Отримайте відмічену часом пробу: запустіть curl з таймінгом, щоб підтвердити тривалість таймауту і частоту.
  2. Перевірте лог проксі: підтвердіть upstream timed out і ідентифікуйте апстрім (php‑fpm socket, upstream host).
  3. Перевірте насичення PHP‑FPM: шукайте попередження max_children і порахуйте зайняті воркери.
  4. Перегляньте PHP slowlog: підтвердіть, чи повільні запити знаходяться в mysqli‑викликах або в інших місцях.
  5. Перевірте DB processlist: шукайте очікування блокувань, довгі запити і повторювані дорогі запити.
  6. Перевірте InnoDB status: виявіть довгі транзакції і блокуючі локації.
  7. Перегляньте slow query log: корелюйте сплески Query_time/Rows_examined із вікном інциденту.
  8. Перевірте здоров’я сховища/хоста: iowait, затримки диска, OOM kills, CPU steal.
  9. Зробіть одне стабілізаційне зміщення: вбийте блокер, вимкніть проблемний плагін, обмежте частоту або тимчасово масштабуйтесь потрібний шар.
  10. Занотуйте спостереження: вставте ключові рядки логів і виводи команд у хронологію інциденту.

Чеклист стабілізації (що робити під час інциденту)

  • Вимкніть один найгірший ендпоінт, якщо можливо (admin-ajax хендлери, важкий пошук/фільтри).
  • Зменшіть конкуренцію на джерелі навантаження, якщо БД тане (обмежте PHP‑FPM або додайте rate limiting на Nginx).
  • Негайно зупиніть зміни схеми, якщо вони блокують гарячі таблиці.
  • Якщо IO насичено, зупиніть бекграундні джоби, що інтенсивно пишуть (бекапи, індексування, debug‑логи, синхронізацію файлів).
  • Віддавайте перевагу цілеспрямованим вбивствам блокуючих транзакцій, ніж сліпому перезапуску MySQL.

Чеклист жорсткості (що зробити після інциденту)

  • Тримайте PHP‑FPM slowlog налаштованим і протестованим (не обов’язково завжди шумним, але готовим).
  • Тримайте slow query logging доступним (увімкненим або швидко увімкненим), і знайте, де лог лежить і як його ротуєте.
  • Узгодьте таймаути між CDN/proxy/PHP/DB, щоб симптоми були послідовні і зрозумілі.
  • Додайте object cache (Redis) і перевірте, що WordPress дійсно ним користується.
  • Аудитуйте плагіни на предмет шаблонів запитів (meta‑запити, wildcard пошуки, важкі admin‑ajax виклики).
  • Інтелігентно індексуйте: уникати хаотичних індексів; перевіряти EXPLAIN і вимірювати Rows_examined.
  • Плануйте зміни схеми з інструментами низького блокування і в off‑peak вікна.
  • Моніторьте затримку диска і iowait; не чекайте, поки це стане «інцидентом бази даних».

FAQ

1) Якщо Nginx каже «upstream timed out», чи означає це, що PHP — проблема?

Ні. Це означає, що Nginx не отримав відповідь від апстріму (часто PHP‑FPM) вчасно. PHP може чекати на MySQL, диск або зовнішній API. Використайте PHP‑FPM slowlog, щоб побачити, де код застрягає.

2) Який найшвидший спосіб довести, що база даних спричиняє 504?

Корелюйте три речі в одному часовому вікні: стек‑трасування PHP slowlog у mysqli функціях, processlist MySQL із довгими запитами/очікуваннями блокувань, і сплески у slow query log.

3) Який найшвидший спосіб довести, що PHP‑FPM є вузьким місцем?

Знайдіть попередження «server reached pm.max_children» плюс зростаючу listen queue/backlog, і підтвердіть, що MySQL не перевантажений (Threads_running стабільні, немає lock storms). Якщо PHP‑воркери сильно завантажені CPU — вони роблять надто багато роботи на запит.

4) Чи слід підвищувати fastcgi_read_timeout, щоб зупинити 504?

Тільки як тимчасовий захід і тільки якщо ви розумієте вплив на черги. Довгі таймаути можуть перетворити періодичну повільність у стале насичення. Виправляйте хвостову латентність, не ховайте її.

5) Як відрізнити проблему блокувань від проблеми повільних запитів у MySQL?

Блокування видно як «Waiting for … lock» у processlist і у секціях lock wait в InnoDB status. Повільні запити мають високий Query_time і великі Rows_examined у slow query log, часто зі станом «Sending data».

6) Чому 504 частіше на WooCommerce checkout або в кошику?

Оформлення торгівлі зачіпає write‑інтенсивні таблиці (sessions, orders, order meta) і може викликати зовнішні виклики (платіжні шлюзи, API для податків/доставки). Така комбінація чутлива до контенції БД і зовнішньої латентності.

7) Чи може Redis/object caching виправити 504?

Може, якщо вузьке місце — повторювані запити читання (options, postmeta) і у вас гарний cache hit rate. Не виправить блокування від важких записів або зміни схеми, що блокує все.

8) Чому я бачу 504, але CPU MySQL низький?

Бо БД може бути IO‑обмеженою (високий disk await), блокованою або мережево‑обмеженою. Низький CPU не означає, що все в порядку. Дивіться iowait, затримку диска і lock waits.

9) Чи безпечно вбити MySQL‑запит під час інциденту?

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

10) Що робити, якщо ні DB, ні PHP явно не виглядають поганими?

Тоді підозрюйте залежності: DNS‑латентність, зовнішні HTTP‑виклики, файлові зависання (мережеве сховище), OOM kills ядра або некоректну конфігурацію проксі. PHP‑FPM slowlog — ваш компас: він вкаже функцію, де зникає час.

Висновок: наступні кроки, що реально зменшують 504

Якщо ви винесете один операційний урок з цього матеріалу: не сперечайтесь, чи «база даних чи PHP» абстрактно. Доведіть, де витрачається час за допомогою відмічених часом доказів із проксі, PHP‑FPM і MySQL. Ви будуєте причинний ланцюг, а не інтуїцію.

Практичні наступні кроки:

  1. Тримайте PHP‑FPM slowlog налаштованим (з розумним порогом, наприклад 5–10 секунд), щоб ловити стек‑трасування під час реальних інцидентів.
  2. Тримайте slow query logging доступним (увімкнений або швидко увімкнений), і знайте, де лог лежить і як ротується.
  3. Узгодьте таймаути, щоб запит не помирав містично на різних шарах з різними годинниками.
  4. Розглядайте PHP‑конкуренцію як важіль із наслідками: підвищення max_children збільшує навантаження на базу. Перш ніж піднімати, переконайтесь у запасі DB.
  5. Вимірюйте затримку сховища, коли «база даних повільна». IO — прихована вісь, яку більшість WordPress‑стеків ігнорує, поки не почне горіти.
  6. Після інциденту усуньте тригер: виправте запит, індексуйте правильно, вимкніть поведінку плагіна, кешуйте дорогий шлях або перерахуйте ендпоінт.

504 не містичні. Вони просто інфраструктура, яка ввічливо каже вам, що один із шарів не встигає. Ввічливість закінчується, коли її ігнорують.

← Попередня
База WordPress роздута: безпечне очищення autoload у wp_options без збоїв
Наступна →
Як читати огляди GPU: пастки 1080p, 1440p і 4K

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