Ваш сайт WooCommerce «працює» для покупців, але щойно хтось натискає Orders у wp-admin,
екран перетворюється на вправу з медитації. Крутилки. Таймаути. Відчуття «ви ще тут?».
Тим часом служба підтримки намагається повернути кошти, а фінанси питають, чому вчорашня партія не відійшла.
Рішення рідко — «додайте більше CPU». Більшість повільності у адмінці WooCommerce — це історія бази даних: пара важких запитів,
погані індекси, патологічні meta‑joinи або плагін, що перетворив список на сховище даних.
Цей посібник показує, як знайти винуватців і приборкати їх — без здогадок і містики.
Швидкий план діагностики
Коли екран замовлень в адмінці повільний, потрібний щільний цикл: відтворити, виміряти, виявити найповільніший запит
і вирішити, чи виправляємо форму даних, індекси, кеш чи поведінку плагіна. Ось порядок дій, що швидко знаходить вузьке місце.
Перше: підтвердьте, куди йде час
- Waterfall браузера / TTFB: якщо Time To First Byte величезний — це серверна частина. Якщо TTFB нормальний, але відтворення тягне — це фронтенд JS або великий HTML.
- PHP-FPM slowlog: якщо PHP‑воркери зависають у викликах БД — ви це побачите. Якщо вони зависають у HTTP‑викликах — теж побачите.
- База даних: шукайте довгі запити, блокування або масові examined rows.
Друге: зафіксуйте найгірший запит, а не всі запити
- MySQL slow query log (тимчасово, таргетовано) для фіксації адмінних ендпойнтів.
- Performance Schema для ранжування запитів за сумарним часом.
- Query Monitor (якщо можна встановити) щоб побачити точний запит і стек викликів.
Третє: вирішіть клас виправлення
- Відсутній/неефективний індекс: додайте композитний індекс або припиніть незіндексовані скани meta.
- Вибух meta‑joinів: зменшіть фільтри, перепишіть з кращими ключами або перейдіть на HPOS.
- Надутий плагін: відключіть фічі, що додають JOIN/стовпці до списку; виправте N+1 патерни.
- Контенція: блокування через імпорти/webhook/cron; плануйте й батчте роботу.
- Відсутність/неправильне використання кешу: додайте object cache, але не намагайтеся кешем витягнути повний table scan.
Мета не просто «зробити швидше» абстрактно. Мета — перетворити сторінку Orders з фабрики запитів у передбачуване, індексоване звернення.
Це означає менше examined rows, менше JOINів і менше сюрпризів.
Цікаві факти та контекст (чому це повторюється)
Невеликий контекст допоможе припинити битву з минулою проблемою. Проблеми продуктивності адмінки WooCommerce не нові й не випадкові.
Ось конкретні факти, які пояснюють шаблони, що ви будете бачити.
- Початкова модель WordPress «усе — пост» змусила продукти і замовлення вміщуватися у
wp_posts, але також перемістила структуровані дані уwp_postmeta, таблицю, яка погано зростає зі збільшенням обсягу. - Таблиці meta схожі на EAV (entity‑attribute‑value). Добре для гнучкості, посередньо для фільтрації та сортування, і справді жахливо, коли комбінують кілька meta‑ключів в одному запиті.
- Історично WooCommerce зберігав дані замовлень у posts/postmeta, тому фільтрація за статусом, датою, сумою, методом оплати чи полями клієнта часто означає JOINи проти
wp_postmeta. - Second‑level індекси InnoDB не зберігають весь рядок. Вони зберігають індексовані колонки плюс первинний ключ. Якщо запиту потрібні додаткові поля — це може означати більше page reads.
- «Rows examined» часто справжній злочинець. Запит 200 мс, що переглядає 20 мільйонів рядків — це потенційна відмова при зростанні трафіку.
- HPOS (High‑Performance Order Storage) у WooCommerce з’явився в основному тому, що wp_posts/wp_postmeta не могли масштабуватись для магазинів із великою кількістю замовлень без болючих шаблонів запитів.
- MySQL не завжди може ефективно використовувати дві окремі індекси для деяких багатоумових шаблонів так, як цього хочуть люди. Композитні індекси часто кращі за «я додав індекс для кожної колонки».
- Адмін‑сторінки — привілейовані і важкі — вони раху акт агрегати, завантажують пов’язані об’єкти і виконують перевірки прав. Ви бенчмаркуєте автобус, не велосипед.
- COUNT(*) може бути дорогим в InnoDB, бо він не зберігає дешевий підрахунок рядків як деякі движки. Списки замовлень, що обчислюють підсумки по статусах, можуть бути затратними.
Якщо винести одне з історії: повільність адмінки WooCommerce зазвичай — це проблема розмітки даних, яка ховається в костюмі PHP.
Як виглядає «повільні замовлення» в проді
«Orders повільні» може означати щонайменше п’ять різних режимів відмови. Їх треба розрізняти.
Режим A: довгий TTFB, потім сторінка завантажується
Сервер виконує роботу перед відправкою HTML. Зазвичай MySQL‑запити, іноді віддалені виклики (ставки доставки, статус оплати),
і іноді величезні PHP‑графи об’єктів.
Режим B: сторінка завантажується, але таблиця Orders порожня деякий час
Часто це AJAX‑компоненти, REST‑ендпойнти або фонові фетчі для таблиці списку.
Перевіряйте admin‑ajax і REST‑часи відповіді, а не тільки основний запит.
Режим C: періодичні таймаути
Класична контенція. Cron, імпорт, шторм webhook‑ів або бекап накладаються на адмін‑використання.
Або ви насичуєте PHP‑FPM воркери, і адмінні запити стають у чергу, поки не помруть.
Режим D: лише деякі користувачі бачать повільність
Ролі мають значення. Деякі плагіни додають додаткові стовпці/дії лише для адміністраторів або підвантажують інтеграції
для певних привілеїв користувача. Також індивідуальні опції екрану можуть змінювати розмір списку.
Режим E: на стенді швидко, у проді повільно
На стенді 2 000 замовлень. У проді 2 000 000 рядків postmeta. Вітаю — ви відкрили проблему масштабу.
Жарт #1: якщо ваша сторінка Orders сповільнюється тільки під час великих розпродажів, це не «таємниця» — це ваша база даних, що просить перерви.
Інструментація, що каже правду (не відчуття)
Ви не можете оптимізувати те, чого не спостерігаєте. Але й спостерігати все не можна без створення нових проблем.
Хитрість — додати таргетовану видимість для адмін‑ендпойнтів і вимкнути її, щойно зберете докази.
Виберіть інструмент залежно від обмежень
- Безпечно для проду, низьке навантаження: MySQL slow query log (з розумними порогами), Performance Schema digest‑зведення, PHP‑FPM slowlog.
- Більше деталей, більший ризик: плагін Query Monitor у проді (іноді ок, часто ні), повний general query log (майже ніколи).
- Найкраще, якщо є: APM‑трейсинг (New Relic, Datadog тощо). Але вам все одно треба розуміти SQL.
Одна цитата, щоб тримати чесно
Надія — не стратегія.
— генерал Гордон Р. Салліван
У цьому контексті: не «сподівайтесь», що Redis вирішить запит, що сканує всю таблицю meta.
Спочатку виміряйте. Потім виправляйте конкретну повільну річ.
Практичні задачі: команди, виводи, рішення
Це реальні задачі, які можна виконати на типовому Linux + Nginx/Apache + PHP-FPM + MySQL/MariaDB стеці.
Кожна задача включає: команду, приклад виводу, що це означає і яке рішення прийняти.
Виконуйте їх у цьому порядку, якщо вирішуєте пожежу.
Задача 1: Підтвердити повільний ендпойнт і таймінг з access логів
cr0x@server:~$ sudo awk '$7 ~ /wp-admin\/edit.php/ && $0 ~ /post_type=shop_order/ {print $4,$7,$9,$10,$NF}' /var/log/nginx/access.log | tail -n 5
[27/Dec/2025:11:04:11 /wp-admin/edit.php?post_type=shop_order 200 81234 6.982
[27/Dec/2025:11:04:29 /wp-admin/edit.php?post_type=shop_order 200 80991 7.104
[27/Dec/2025:11:05:02 /wp-admin/edit.php?post_type=shop_order 504 0 30.001
[27/Dec/2025:11:05:45 /wp-admin/edit.php?post_type=shop_order 200 81012 8.331
[27/Dec/2025:11:06:13 /wp-admin/edit.php?post_type=shop_order 200 81120 7.955
Що це означає: запити списку Orders займають 7–8 секунд, іноді 504 при 30 сек (таймаут проксі).
Рішення: розглядайте як серверну латентність. Перейдіть до інспекції PHP‑FPM і БД, а не фронтенд‑правок.
Задача 2: Перевірити насичення PHP-FPM (черги виглядають як «випадкова повільність»)
cr0x@server:~$ sudo ss -s
Total: 2181
TCP: 1612 (estab 204, closed 1298, orphaned 0, timewait 120)
Transport Total IP IPv6
RAW 0 0 0
UDP 9 7 2
TCP 314 247 67
INET 323 254 69
FRAG 0 0 0
Що це означає: не остаточно, але купа з’єднань може натякати на backlog. Потрібен статус PHP‑FPM.
Рішення: якщо статус PHP‑FPM не увімкнений — тимчасово увімкніть його (з доступом). Потім інспектуйте активні/idle воркери.
Задача 3: Прочитати статус пулу PHP-FPM (active vs max children)
cr0x@server:~$ curl -s http://127.0.0.1/status?full | egrep -i 'active processes|max active processes|idle processes|max children reached|slow requests'
active processes: 24
max active processes: 24
idle processes: 0
max children reached: 93
slow requests: 117
Що це означає: ви на межі max children із нульовими idle воркерами; запити стають у чергу. Система перевантажена або заблокована.
Рішення: не просто підвищуйте pm.max_children. Знайдіть, що блокує воркери (часто виклики БД). Перейдіть до slowlog і БД.
Задача 4: Включити PHP-FPM slowlog на короткий період
cr0x@server:~$ sudo grep -nE 'slowlog|request_slowlog_timeout' /etc/php/8.2/fpm/pool.d/www.conf
255:;slowlog = /var/log/php8.2-fpm/slow.log
256:;request_slowlog_timeout = 5s
cr0x@server:~$ sudo sed -i 's#^;slowlog =.*#slowlog = /var/log/php8.2-fpm/slow.log#; s#^;request_slowlog_timeout =.*#request_slowlog_timeout = 5s#' /etc/php/8.2/fpm/pool.d/www.conf
cr0x@server:~$ sudo systemctl reload php8.2-fpm
Що це означає: запити довші за 5 секунд зіллють стек викликів.
Рішення: відтворіть повільне завантаження Orders один‑два рази, потім проаналізуйте slow log і вимкніть, якщо шумно.
Задача 5: Проінспектувати PHP-FPM slowlog, де витрачається час
cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm/slow.log
[27-Dec-2025 11:07:44] [pool www] pid 22144
script_filename = /var/www/html/wp-admin/edit.php
[0x00007f1c2b9d0a80] mysqli_query() /var/www/html/wp-includes/wp-db.php:2351
[0x00007f1c2b9d0a10] _do_query() /var/www/html/wp-includes/wp-db.php:2265
[0x00007f1c2b9d08b0] query() /var/www/html/wp-includes/wp-db.php:3132
[0x00007f1c2b9d06e0] get_results() /var/www/html/wp-includes/wp-db.php:3645
[0x00007f1c2b9d05d0] query() /var/www/html/wp-content/plugins/woocommerce/includes/data-stores/class-wc-order-data-store-cpt.php:...
Що це означає: воркери блокуються в MySQL. Тепер потрібні точні SQL і його план.
Рішення: увімкніть MySQL slow query log або використайте Performance Schema, щоб зняти конкретний запит.
Задача 6: Увімкнути MySQL slow query log безпечно (тимчасово, з порогом)
cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time'; SHOW VARIABLES LIKE 'slow_query_log_file';"
Enter password:
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | OFF |
+----------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 10.000|
+-----------------+-------+
+---------------------+-------------------------------+
| Variable_name | Value |
+---------------------+-------------------------------+
| slow_query_log_file | /var/log/mysql/mysql-slow.log |
+---------------------+-------------------------------+
cr0x@server:~$ mysql -uroot -p -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = 'ON';"
Enter password:
Що це означає: у лог будуть писатися запити довші за 1 секунду (і ті, що не використовують індекси). Достатньо, щоб впіймати адмінну повільність без перевантаження.
Рішення: відтворіть сторінку Orders. Потім негайно проаналізуйте slow log і вимкніть налаштування, якщо об’єм логів занадто великий.
Задача 7: Знайти найгірші адмінні WooCommerce запити у slow логах
cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log | sed -n '1,120p'
# 1.2s user time, 40ms system time, 27.43M rss, 1.18G vsz
# Current date: Fri Dec 27 11:10:01 2025
# Overall: 193 total, 19 unique, 0.03 QPS, 0.21x concurrency _____________
# Time range: 2025-12-27T11:06:11 to 2025-12-27T11:09:55
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Query time 301.2s 0.002s 28.991s 1.561s 6.821s 3.102s 0.912s
# Rows examined 1.62G 10 38.12M 8.39M 22.01M 9.11M 6.33M
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ============================= ============= ===== ======= ===== ============
# 1 0x7A2F1F8E3C2B4A1D 172.2330 41 4.2008 0.80 SELECT wp_posts ...
# 2 0x9B1A0E21FF77C0B4 68.1192 22 3.0963 0.62 SELECT wp_postmeta ...
Що це означає: масові examined rows. Це не «PHP повільний» — це доступ до даних.
Рішення: витягніть топ‑фінгерпринт запиту і запустіть EXPLAIN/EXPLAIN ANALYZE. Шукайте повні скани, поганий порядок JOINів і temp tables/filesort.
Задача 8: Витягнути точний приклад запиту і запустити EXPLAIN
cr0x@server:~$ sudo grep -n "SELECT .*wp_posts" -m 1 -A 20 /var/log/mysql/mysql-slow.log | sed -n '1,25p'
# Time: 2025-12-27T11:08:12.123456Z
# Query_time: 6.821 Lock_time: 0.002 Rows_sent: 25 Rows_examined: 22010431
SET timestamp=1766833692;
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
LEFT JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1
AND ( wp_postmeta.meta_key = '_billing_email' AND wp_postmeta.meta_value LIKE '%@example.com%' )
AND wp_posts.post_type = 'shop_order'
ORDER BY wp_posts.post_date DESC
LIMIT 0, 25;
cr0x@server:~$ mysql -uroot -p wordpress -e "EXPLAIN SELECT wp_posts.ID FROM wp_posts LEFT JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) WHERE ( wp_postmeta.meta_key = '_billing_email' AND wp_postmeta.meta_value LIKE '%@example.com%' ) AND wp_posts.post_type = 'shop_order' ORDER BY wp_posts.post_date DESC LIMIT 0,25\G"
Enter password:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: wp_postmeta
partitions: NULL
type: ref
possible_keys: post_id,meta_key
key: meta_key
key_len: 191
ref: const
rows: 4821031
filtered: 10.00
Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: wp_posts
type: eq_ref
possible_keys: PRIMARY,type_status_date
key: PRIMARY
key_len: 8
ref: wordpress.wp_postmeta.post_id
rows: 1
Extra: Using where
Що це означає: MySQL починає з wp_postmeta використовуючи meta_key, потім сканує мільйони рядків, щоб застосувати LIKE‑фільтр.
«Using temporary; Using filesort» вказує на додаткові операції для сортування.
Рішення: припиніть використання wildcard LIKE на meta_value в масштабі, або застосуйте стратегію: точні пошуки, нормалізовані таблиці пошуку або HPOS. Якщо потрібно шукати email — використовуйте пошук зі сталим префіксом або відведену індексовану колонку/таблицю.
Задача 9: Перевірити існуючі індекси на wp_postmeta (і чи корисні вони)
cr0x@server:~$ mysql -uroot -p wordpress -e "SHOW INDEX FROM wp_postmeta;"
Enter password:
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| wp_postmeta| 0 | PRIMARY | 1 | meta_id | A | 98122312| NULL | NULL | | BTREE | | | YES | NULL |
| wp_postmeta| 1 | post_id | 1 | post_id | A | 33511291| NULL | NULL | | BTREE | | | YES | NULL |
| wp_postmeta| 1 | meta_key | 1 | meta_key | A | 2412 | 191 | NULL | YES | BTREE | | | YES | NULL |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
Що це означає: існують стандартні індекси, але немає композитного індексу на кшталт (meta_key, meta_value) або (meta_key, post_id), що допомагав би конкретним шаблонам.
Рішення: не додавайте сліпо (meta_key, meta_value), бо meta_value — довгий текст і індексування вимагає префіксу і ретельної думки. Краще виправити шаблон запиту (уникати wildcard LIKE) або перейти на HPOS. Якщо додаєте індекси — робіть найменший, найбільш селективний композитний індекс, що відповідає WHERE і JOIN.
Задача 10: Перевірити обсяг замовлень і надлишок postmeta (масштаб має значення)
cr0x@server:~$ mysql -uroot -p wordpress -e "SELECT COUNT(*) AS orders FROM wp_posts WHERE post_type='shop_order'; SELECT COUNT(*) AS order_meta_rows FROM wp_postmeta pm JOIN wp_posts p ON p.ID=pm.post_id WHERE p.post_type='shop_order';"
Enter password:
+--------+
| orders |
+--------+
| 218942 |
+--------+
+-----------------+
| order_meta_rows |
+-----------------+
| 18320177 |
+-----------------+
Що це означає: ~219k замовлень, але ~18M meta‑рядків для замовлень. Це не дивно, але пояснює, чому фільтрація по meta дорого обходиться.
Рішення: якщо ви ще на legacy storage для замовлень — серйозно розгляньте HPOS. Якщо не можете — обмежте поведінку адмінного пошуку і виправте топ‑запити.
Задача 11: Подивитися топ SQL digest‑и за сумарним часом (Performance Schema)
cr0x@server:~$ mysql -uroot -p -e "SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1000000000000 AS total_sec, AVG_TIMER_WAIT/1000000000000 AS avg_sec, SUM_ROWS_EXAMINED FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
Enter password:
*************************** 1. row ***************************
DIGEST_TEXT: SELECT SQL_CALC_FOUND_ROWS `wp_posts`.`ID` FROM `wp_posts` LEFT JOIN `wp_postmeta` ON ( `wp_posts`.`ID` = `wp_postmeta`.`post_id` ) WHERE ...
COUNT_STAR: 97
total_sec: 214.731
avg_sec: 2.214
SUM_ROWS_EXAMINED: 913200112
*************************** 2. row ***************************
DIGEST_TEXT: SELECT `meta_id`, `post_id`, `meta_key`, `meta_value` FROM `wp_postmeta` WHERE `post_id` IN ( ... )
COUNT_STAR: 412
total_sec: 71.334
avg_sec: 0.173
SUM_ROWS_EXAMINED: 18445022
Що це означає: найважчий запит — патерн списку Orders. Другий — патерн вибірки meta, можливо N+1.
Рішення: оптимізуйте топ‑digest спочатку. Якщо ви виправите #1 патерн, зазвичай покращиться сприйняття «адмін повільний».
Задача 12: Перевірити блокування навколо таблиць замовлень
cr0x@server:~$ mysql -uroot -p -e "SHOW ENGINE INNODB STATUS\G" | egrep -n 'LATEST DETECTED DEADLOCK|TRANSACTIONS|LOCK WAIT|wait' | head -n 40
Enter password:
72:TRANSACTIONS
85:---TRANSACTION 93488322, ACTIVE 18 sec starting index read
86:mysql tables in use 1, locked 1
87:LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
88:MySQL thread id 221, OS thread handle 140067524396800, query id 9123219 10.0.2.14 wordpress updating
Що це означає: є транзакції, що чекають на блокування. Це може перетворити «інколи повільно» у «випадково не працездатно».
Рішення: ідентифікуйте блокувальний запит (PROCESSLIST), тоді вирішіть: скоротити область транзакції, батчити оновлення, перемістити імпорти у непіковий час або налаштувати ізоляцію/блокування.
Задача 13: Виявити живий блокувальний запит
cr0x@server:~$ mysql -uroot -p -e "SHOW FULL PROCESSLIST;" | head -n 15
Enter password:
+-----+---------+-----------+-----------+---------+------+------------------------+------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+---------+-----------+-----------+---------+------+------------------------+------------------------------+
| 221 | wp_user | 10.0.2.14 | wordpress | Query | 18 | updating | UPDATE wp_postmeta SET ... |
| 227 | wp_user | 10.0.2.19 | wordpress | Query | 7 | Sending data | SELECT SQL_CALC_FOUND_ROWS...|
| 229 | wp_user | 10.0.2.19 | wordpress | Query | 6 | Waiting for table lock | SELECT ... wp_postmeta ... |
+-----+---------+-----------+-----------+---------+------+------------------------+------------------------------+
Що це означає: UPDATE працює довго і викликає очікування. SELECT для Orders страждає від побічного ефекту.
Рішення: якщо UPDATE від плагіна, що робить масові meta‑зміни — обмежте швидкість, батчте або перемістіть у чергу. Не дозволяйте «фонова» робота перекривати пік‑часи адмінки.
Задача 14: Перевірити innodb buffer pool і чи ви пов’язані з диском
cr0x@server:~$ mysql -uroot -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
Enter password:
+-------------------------+------------+
| Variable_name | Value |
+-------------------------+------------+
| innodb_buffer_pool_size | 2147483648 |
+-------------------------+------------+
+--------------------------+--------+
| Variable_name | Value |
+--------------------------+--------+
| Innodb_buffer_pool_reads | 918221 |
+--------------------------+--------+
+--------------------------------+-----------+
| Variable_name | Value |
+--------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 918221223 |
+--------------------------------+-----------+
Що це означає: 2 ГБ buffer pool замало для завантаженого WooCommerce зі мільйонами meta‑рядків. Читання з диска немалі.
Рішення: якщо на хості є RAM — збільште buffer pool (обережно). Але робіть це після виправлення найгірших запитів; інакше ви просто кешуватимете погану поведінку.
Задача 15: Перевірити наявність object cache та hit rate (приклад Redis)
cr0x@server:~$ redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses'
keyspace_hits:18382122
keyspace_misses:8821331
Що це означає: hit rate приблизно 67%. Не жахливо, але не відмінно. Адмінні сторінки все одно можуть багато промахуватись, якщо ключі нестабільні або TTL малі.
Рішення: переконайтеся, що persistent object cache активний і не вимикається для адмінки. Але не робіть з 99% hit‑rate релігію; спочатку виправте найгірший SQL.
Задача 16: Профілювати WordPress‑хукi та повільні виклики через WP‑CLI (підозрілі плагіни)
cr0x@server:~$ wp --path=/var/www/html --allow-root plugin list --status=active
+-------------------------------+--------+-----------+---------+
| name | status | update | version |
+-------------------------------+--------+-----------+---------+
| woocommerce | active | available | 8.8.3 |
| woocommerce-subscriptions | active | none | 6.6.0 |
| some-order-exporter | active | none | 2.1.4 |
| query-monitor | inactive | none | 3.15.0 |
+-------------------------------+--------+-----------+---------+
Що це означає: у вас є кілька ймовірних «модифікаторів» списку Orders (експортери, підписки, CRM), які часто додають JOINи/стовпці.
Рішення: тестуйте відключення підозрюваних у стенді або в контролюваному вікні в проді й вимірюйте час завантаження Orders та slow query знову.
Задача 17: Виміряти час відповіді з боку сервера (curl як реальність)
cr0x@server:~$ curl -s -o /dev/null -w "ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n" -H "Cookie: wordpress_logged_in_x=REDACTED" "https://shop.example.com/wp-admin/edit.php?post_type=shop_order"
ttfb=6.742 total=7.118 code=200
Що це означає: TTFB — більша частина загального часу, підтверджуючи серверний вузький горлечко.
Рішення: продовжуйте фокусуватися на БД/PHP, а не мініфікації адмін‑ресурсів.
Задача 18: Спостерігати латентність диска (бо бази даних її ненавидять)
cr0x@server:~$ iostat -x 1 5
avg-cpu: %user %nice %system %iowait %steal %idle
9.02 0.00 3.11 8.44 0.00 79.43
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 210.0 95.0 8420.0 6120.0 68.0 4.20 14.80 18.90 5.10 0.55 16.8
Що це означає: 8% iowait і ~19 ms read await можуть посилювати погані плани запитів. Не катастрофа, але коштує уваги.
Рішення: якщо після виправлення запитів ви все ще бачите високий await під навантаженням адмінки — розгляньте апгрейд сховища або ізоляцію I/O від шумних сусідів.
Задача 19: Відмінити тимчасову інструментацію (будь хорошим сусідом)
cr0x@server:~$ mysql -uroot -p -e "SET GLOBAL slow_query_log='OFF'; SET GLOBAL long_query_time=10; SET GLOBAL log_queries_not_using_indexes='OFF';"
Enter password:
Що це означає: ви зібрали докази і зменшили постійне навантаження.
Рішення: збережіть письмовий запис топ‑digestів і планів. Оптимізація без нотаток — шлях до регресій за шість місяців.
Типові важкі шаблони запитів в адмінці WooCommerce
Коли у вас є приклади повільних запитів і вивід EXPLAIN, ви почнете бачити повтори. Повільність адмінки WooCommerce часто — це
три‑чотири шаблони з різними параметрами.
Шаблон 1: Фільтрація по meta з LIKE на meta_value
Пошук замовлень за email, ім’ям, адресою чи будь‑чим, що зберігається в postmeta, зазвичай робить:
фільтр за meta_key, потім сканує багато рядків, щоб застосувати LIKE '%...%'. Ведучий wildcard убиває корисність індексу.
Підказка діагностики: EXPLAIN починається з wp_postmeta, великий «rows», «Using temporary; Using filesort».
Куди рухатись: змініть поведінку пошуку (точний або початковий), використайте HPOS або додайте окремі індексовані таблиці пошуку. Префіксний індекс longtext — часткове, ризикове тимчасове рішення.
Шаблон 2: ORDER BY post_date з широким WHERE, що викликає filesort
Список замовлень використовує сортування (дата, сума, статус) і пагінацію. Якщо MySQL не може задовольнити ORDER BY із індексу,
він сортує багато кандидатів, а потім повертає 25.
Підказка діагностики: «Using filesort» і величезні examined rows.
Куди рухатись: підвищити селективність, додати композитні індекси, що відповідають WHERE + ORDER BY, або перейти на HPOS, де схема підтримує адекватне індексування.
Шаблон 3: SQL_CALC_FOUND_ROWS / підрахунок для пагінації
Таблиці списків люблять рахувати загальну кількість результатів. На великих наборах підрахунок може коштувати дорожче, ніж вибірка 25 рядків.
Підказка діагностики: схожий запит з’являється двічі: один для ID, інший для FOUND_ROWS або COUNT.
Куди рухатись: зменшити дорогі фільтри; в деяких випадках помʼякшити UX адміністрування (не обчислювати точні totals для кожного фільтру).
Шаблон 4: N+1 вибірки meta для кожного рядка замовлення
Сторінка Orders показує стовпці: billing name, total, status, payment method, shipping method, notes і кастомні колонки від плагінів.
Кожна колонка може викликати додаткові вибірки, якщо дані не prefetched.
Підказка діагностики: багато схожих SELECT ... FROM wp_postmeta WHERE post_id IN (...) або навіть по одному запиту на замовлення.
Куди рухатись: зменшіть кастомні колонки, забезпечте кешування в data store WooCommerce, користуйтеся object cache або оновіться до версії, яка_prefetch_ить більше.
Шаблон 5: JOINи і підзапити, додані плагінами
CRM, експортери, інструменти боротьби з шахрайством і «менеджмент замовлень» обожнюють хукати списки адмінки і додавати WHERE.
Кожна з них виглядає маленькою. Разом вони створюють план запиту, схожий на розлиту тарілку спагетті.
Підказка діагностики: повільний запит містить несподівані meta‑ключі, JOINи таксономій або кастомні таблиці.
Куди рухатись: відключіть і виміряйте. Якщо бізнес потребує плагін — попросіть вендора поради щодо тюнінгу запитів або ізолюйте фічу на окремому екрані.
Жарт #2: SQL як кіт — якщо ігнорувати його, він все одно сяде на вашу клавіатуру і зробить усе гірше.
Прискорення, що працюють (і що вони ламають)
Будемо відвертими: більшість «гайдів з продуктивності» радять кеш і молитви. Ми ж робимо менше examined rows, кращі індекси
і менше зайвих фіч. Ось виправлення, що реально дають ефект.
1) Увімкніть HPOS (High‑Performance Order Storage), якщо це можливо
HPOS переміщує дані замовлень з posts/postmeta в спеціальні таблиці. Перевага неочевидна:
менше JOINів, краще індексування і передбачуваніші форми запитів.
Що ламає: деякі плагіни все ще припускають, що замовлення в posts/postmeta. Потрібно перевірити сумісність,
особливо з fulfillment, підписками та звітністю. Міграція потребує планування.
Порада: якщо у вас шестизначна кількість замовлень і ви ще на legacy storage — HPOS зазвичай варто витрачених зусиль.
Чим довше ви зволікаєте, тим складнішим стає вікно міграції.
2) Зменшіть область адмінного пошуку (припиніть «contains anywhere» пошук)
Найгірший шаблон — введення користувачем термінів, які мапляться на wildcard у meta_value.
«Пошук email містить ‘gmail’» звучить корисно. На великих обсягах це — функція відмови‑навантаження.
Варіанти виправлення:
- Вимагати точного збігу email для пошуку billing email.
- Використовувати префіксний пошук (
LIKE 'abc%') замість contains (LIKE '%abc%') де можливо. - Додати окремий інтерфейс пошуку, що шукає в індексованій таблиці або використовує зовнішній пошук, замість сирого сканування meta_value.
3) Додайте таргетовані композитні індекси (обережно)
Індексування meta‑таблиць WordPress — делікатна справа: ви можете прискорити читання, але сповільнити записи і збільшити розмір.
Проте деякі індекси окуповують себе миттєво.
Поширені корисні форми індексів:
(post_id, meta_key)для ефективного вибору конкретних ключів по замовленню (якщо це часто робиться).(meta_key, post_id)для запитів, що фільтрують за meta_key, а потім JOINять назад до posts по post_id.
Чого уникати: випадкове індексування meta_value, поки ви не зрозуміли розподіл даних і шаблони запитів.
Префіксні індекси можуть допомогти для точних/префіксних збігів, але не врятують leading wildcard LIKE.
4) Убити дорогі колонки і фільтри в адмінці, додані плагінами
Кожна кастомна колонка, що показує «ще один корисний кусок інформації», може викликати додаткові meta‑вибірки або JOINи.
Помножте на 25 рядків на сторінці і на кожного адміністратора, що відкрив екран.
Виправлення: видаліть або відкладіть колонки. Якщо плагін наполягає — налаштуйте його, щоб не змінював запит Orders,
або перемістіть дані на окремий екран, який підвантажується за потреби.
5) Використовуйте персистентний object cache, але розглядайте його як множник
Redis/Memcached можуть зменшити повторні вибірки: options, usermeta, term cache і кеш об’єктів WooCommerce.
Це допомагає адмінці, бо адміністратори повторюють дії. Але це не вирішить один великий запит, що виконує повний скан.
Виправлення: переконайтеся, що object cache персистентний і налаштований по розміру. Також стежте, щоб його не скидали під час деплою або плагіни‑бустери.
6) Приручіть фонові задачі (Action Scheduler, імпорти, webhookи)
WooCommerce використовує Action Scheduler для фонового виконання. Якщо у вас накопичення — він може бити по БД і задушити адмін‑запити.
Імпорти і синки роблять те саме.
Виправлення: обмежте швидкість, батчте, розкладіть на пікові години. Не запускайте масові оновлення опівдні, а потім дивуйтесь, що Orders повільні.
7) Підійміть пам’ять MySQL і зменшіть дискову біль
Якщо buffer pool маленький — кожен адмін‑запит стає диск‑шукачем. Збільште buffer pool, стежте за swap і не компактнуйте БД з шумними навантаженнями.
Виправлення: поступово збільшуйте innodb_buffer_pool_size, рестарт у вікні обслуговування при потребі, потім виміряйте знову.
Якщо латентність сховища висока — виправте і це; бази даних нетерплячі.
8) Оновлюйте WooCommerce і WordPress стратегічно
Поліпшення продуктивності часто приходять у нудних маленьких релізах: менше запитів, кращий кеш, краща поведінка list table.
Якщо ви відстаєте — ви страждаєте від багів, що вже виправлені.
Три корисні історії з практики
Історія 1: Інцидент через неправильне припущення
Середній ритейлер довго працював на WooCommerce без проблем. Замовлення сезонно зростали, але сайт тримався.
Потім служба підтримки почала скаржитись: натискаєш «Orders» — таймаут. Інженери подивилися CPU і побачили, що він не завантажений.
Вони оголосили «мережа» і пішли далі.
Неправильне припущення: якщо CPU нормальний, то БД теж. Тим часом БД переглядала 20+ млн рядків на пошуковий запит,
згенерований макросом підтримки: «знайти замовлення по email клієнта містить ‘@gmail.com’». Це працювало при 30k замовлень.
При 300k — це стало машиною для table scan.
Вони «виправили» це, збільшивши таймаути проксі. Сторінка Orders перестала таймаутитися… і почала займати 40 секунд.
Підтримка перестала користуватись екраном і попросила інженерів виконувати ручні запити.
Це не тимчасове рішення; це повільна відмова.
Справжнє виправлення було неефектне: прибрали contains‑пошук по billing email в адмін‑інтерфейсі, замінили його на точний збіг і окремий
lookup клієнта, що повертав відомі email. Підтримка скиглила тиждень, потім забула.
Сторінка Orders впала до менше ніж двох секунд, бо БД припинили просити неможливого.
Урок: відмови продуктивності часто — це продуктове рішення. Якщо ваш UI заохочує патологічні запити — ваша БД колись застосує фізику.
Історія 2: Оптимізація, що повернулась бумерангом
Інша компанія мала інженера, що обожнював індекси. Сторінка Orders була повільна, тож він додав купу індексів на wp_postmeta,
включно з префіксним індексом на meta_value для кількох ключів. Також додав кілька композитних «на всякий випадок».
На короткому бенчмарку це виглядало чудово.
Через два тижні створення замовлень сповільнилося. Потім повернення коштів. Потім фонові задачі стали накопичуватись.
Записи платили ціну за утримання лісу вторинних індексів. Магазин не впав, але все відчувалося липким.
Гірше: нові індекси змінили вибір планувальника запитів. Деякі запити стали швидшими, інші — драматично гіршими, бо планувальник почав використовувати індекс, який здавався селективним, але насправді ні. Раніше прийнятний запит став довгим, викликаючи контенцію.
Відкат був болючим, бо видалення індексів на великих таблицях не миттєве. Вони планували windows обслуговування і видаляли по одному,
одночасно виправляючи реальну проблему: плагін, що генерував meta‑запити з низькою селективністю і wildcard‑ами.
Урок: індекси не безкоштовні. Додавайте їх так, ніби платите за них з власного бюджету — бо так воно і є, але через латентність.
Історія 3: Скучне, але правильне правило, що врятувало
Магазин, тяжкий на підписки, мав стабільну повільність адмінки щоранку в понеділок. Це не було катастрофою, але робило операції некомфортними.
Інженер, що відповідав, нарешті зробив нудну річ: він збирав slow query логи 15 хвилин кожного понеділка протягом місяця,
потім порівнював digest‑и і плани.
Шаблон був ясний: Action Scheduler обробляв накопичення оновлень підписок, генерував хвилі записів у postmeta і notes,
що перекривалося із запитами персоналу на повернення коштів і підтримку. БД була в порядку в непіковий час; проблема — контенція під змішаним read/write навантаженням.
Виправлення не було героїчним. Вони обмежили швидкість scheduler‑а, перенесли обробку оновлень на ранні години і додали спостережуваність:
якщо глибина черги перевищує поріг — scheduler сповільнюється. Також вони виділили невеликий пул PHP‑FPM тільки для адмінки,
щоб клієнтський трафік не забирав ресурси у співробітників.
Понеділки стали нудними знову. Бизнес святкував тим, що не помічав цього — найвищий комплімент для операцій.
Урок: регулярні виміри перемагають одноразову паніку. Нудна практика — збирати порівнювані докази з часом і діяти по патернах.
Поширені помилки: симптом → корінь → виправлення
Ось речі, що марнують тижні. Визнайте симптом, зіставте з ймовірною причиною і застосуйте конкретне виправлення.
1) Симптом: сторінка Orders повільна тільки при використанні адмін‑пошуку
Корінь: пошук по meta з LIKE '%term%' у wp_postmeta.meta_value.
Виправлення: обмежити пошук точним або префіксним збігом; додати відведений customer lookup; розглянути HPOS. Уникайте contains‑пошуку по довгому тексту meta.
2) Симптом: сторінка Orders таймаутиться під час імпортів/синків
Корінь: масові оновлення тримають блокування і насичують I/O; довгі транзакції; фонові задачі не обмежені.
Виправлення: батчте оновлення, зменшіть розмір транзакцій, розкладьте на непікові години, обмежте Action Scheduler і моніторте lock waits.
3) Симптом: сторінка Orders повільна тільки для адміністраторів
Корінь: плагін додає колонки/дії для адміністраторів, створюючи додаткові запити або JOINи.
Виправлення: відключити функцію плагіна для list table, видалити колонки або перемістити дані на запитуваний екран.
4) Симптом: CPU низький, але латентність висока
Корінь: дисково‑залежні читання БД через малий buffer pool або погані плани запитів.
Виправлення: спочатку виправити плани запитів; потім збільшити buffer pool; перевірити латентність сховища; упевнитися, що БД не на тому ж диску, де шумні роботи.
5) Симптом: стенд швидкий, прод повільний
Корінь: стенд має маленький набір даних; прод має величезні meta‑таблиці і іншу кардинальність.
Виправлення: тестуйте з обсягом, наближеним до проду; використовуйте EXPLAIN ANALYZE на реальних формах запитів; не робіть рішень на тестових даних.
6) Симптом: «Ми поставили Redis і нічого не змінилося»
Корінь: повільна частина — один великий запит, а не повторювані кешовані звернення; або кеш скидається/відключений для адмінки.
Виправлення: перевірте slow query логи; впевніться, що persistent object cache увімкнений; припиніть великий скан; не очікуйте, що кеш виправить незіндексовані фільтри.
7) Симптом: після додавання індексів записи стали повільні
Корінь: занадто багато вторинних індексів на «гарячих» таблицях; вартість підтримки індексів перевищила користь.
Виправлення: видаліть низькоцінні індекси; залиште лише ті, що відповідають топ‑патернам; розгляньте зміну схеми (HPOS) замість індексування EAV.
8) Симптом: випадкові 504, тоді як деякі запити в нормі
Корінь: досягнуто max children PHP‑FPM; черга під навантаженням; повільні виклики БД тримають воркери.
Виправлення: знайти блокуючі виклики через slowlog/APM; виправити БД; за потреби виділити окремий пул для адмінки; в останню чергу змінювати таймаути.
Чеклісти / покроковий план
Чекліст A: Екстрена тріаж (в межах дня)
- Підтвердьте затримку ендпойнта через access логи і серверний curl TTFB.
- Перевірте насичення PHP‑FPM (status): чи досягли max children?
- Увімкніть PHP‑FPM slowlog на 5s на короткий період; відтворіть раз.
- Увімкніть MySQL slow query log з порогом 1s на 10–15 хв; відтворіть раз.
- Запустіть pt-query-digest; оберіть топ‑запит за сумарним часом і examined rows.
- Запустіть EXPLAIN / EXPLAIN ANALYZE на точному запиті; збережіть вивід.
- Якщо бачите wildcard на meta_value: негайно обмежте цей пошук (політично/UX) або тимчасово відключіть фільтр.
- Якщо бачите lock waits: знайдіть блокера в PROCESSLIST і зупиніть/переплануйте завдання, що його викликає.
Чекліст B: Структурні виправлення (1–2 тижні)
- Вирішіть щодо HPOS. Якщо так — тестуйте сумісність і план міграції з відкатом.
- Аудит плагінів, що хукають Orders list: видаліть непотрібні колонки і фільтри.
- Додайте лише ті індекси, що виправдані топ‑digestами (і перевірте вплив на записи).
- Підійміть innodb buffer pool до розміру, що відповідає памʼяті; перевірте відсутність swap.
- Обмежте Action Scheduler і інші фонові задачі; налаштуйте алерти по глибині черги.
- Переконайтеся, що persistent object cache стабільний (без частих flush), і моніторте hit/miss.
Чекліст C: Тримати швидкість (постійнo)
- Щотижня збирайте slow query digest (коротке вікно), порівнюйте тренди.
- Тестуйте продуктивність на даних, схожих на прод, перед будь‑якими апгрейдами плагінів або WooCommerce.
- Визначте «admin SLO» (наприклад, Orders list p95 < 2s) і налаштуйте алерти при порушенні.
- Документуйте додані індекси і патерни запитів, які вони обслуговують.
Поширені питання
1) Чому адмінка WooCommerce повільна, а магазин працює нормально?
Сторінки магазину кешуються і часто потрапляють у page cache/CDN. Адмін‑сторінки персоналізовані, не кешуються
і виконують важчі запити (фільтри, сортування, підрахунки, meta‑вибірки, перевірки прав).
2) Чи головний винуватець — wp_postmeta?
Часто так — особливо для пошуку і фільтрації замовлень. Поєднання великої кількості рядків, гнучкої схеми
і незіндексованих шаблонів робить wp_postmeta повторним підозрюваним на масштабі.
3) Чи просто ввімкнути Redis object cache?
Ввімкніть, якщо можете зробити це правильно (персистентний, моніторинг, розмір). Але це не виправить один повільний запит,
що сканує мільйони meta‑рядків. Розглядайте object cache як множник для вже розумних запитів.
4) Чи вирішить проблему підвищення кількості PHP‑FPM воркерів?
Іноді зменшить черги, але може погіршити проблему БД, дозволяючи більше одночасних повільних запитів.
Спочатку виправте блокуючі DB‑виклики, потім налаштовуйте конкуренцію.
5) Який найшвидший шлях до ідентифікації важкого запиту?
Увімкніть MySQL slow query log з порогом 1s на 10–15 хв під час відтворення, потім запустіть pt-query-digest.
Далі — EXPLAIN/EXPLAIN ANALYZE для топ‑digest.
6) Чи варто індексувати meta_value?
Рідко для «contains» пошуків, бо ведучі wildcard‑и не використовують індекс. Префіксні індекси допомагають для точних/префіксних збігів,
але додають витрати на запис. Додавайте їх, тільки коли топ‑патерн цього вимагає і ви протестували вплив на записи.
7) Як HPOS допомагає на практиці?
HPOS переміщує часто фільтровані/сортовані поля замовлення в присвячені колонки з нормальними індексами, зменшуючи meta‑JOINи і покращуючи
вибір планувальника. Це перетворює «сканувати meta і сподіватися» на «використати індекс і вибрати рядки».
8) Чому в EXPLAIN видно «Using temporary; Using filesort»?
Це означає, що MySQL створює тимчасовий набір результатів і сортує його замість використання порядку індексу.
Це трапляється, коли ORDER BY не відповідає індексу або коли JOINи/фільтри заважають індексному порядку.
Виправлення — краща селективність, композитні індекси або інші форми запитів/схем.
9) Чи варто відокремити сервер бази від веба?
Якщо ви на значущому масштабі — так: це зменшить вплив шумних сусідів і дозволить тонше налаштовувати I/O і пам’ять.
Але не використовуйте архітектуру як заміну виправлення патологічних запитів. Ви просто отримаєте більший рахунок і ті самі скани.
10) Що робити, якщо повільний запит походить від плагіна, який я не можу видалити?
По-перше, відключіть інтеграцію плагіна зі списком Orders, якщо можливо (колонки, фільтри, live‑статуси). По‑друге, зніміть точні запити
і надішліть їх вендору з EXPLAIN. По‑третє, ізолюйте фічу на окрему сторінку або фонова робота, де це можливо.
Висновок: кроки, які можна впровадити цього тижня
Якщо ваша сторінка замовлень WooCommerce повільна, припиніть ставитись до цього як до нерозв’язної таємниці.
Зберіть slow query, прочитайте план і змусьте запит перестати сканувати всесвіт.
- Зберіть докази: slow query log (порог 1s) + pt-query-digest + EXPLAIN для топ‑запиту.
- Приберіть найгірші UX‑шаблони запитів: wildcard‑пошуки по meta, надмірні колонки, дорогі фільтри.
- Вирішіть питання контенції: обмежте імпорти й Action Scheduler, щоб адмін‑робота не конкурувала з батч‑записами.
- Прийміть структурне рішення: HPOS, якщо у вас багато замовлень і ви ще на legacy storage.
- Потім налаштуйте: розмір buffer pool, стабільність object cache і конкуренцію PHP‑FPM — після того, як SQL стане адекватним.
Продуктивність адмінки — це операційна продуктивність. Якщо ваш персонал не може оформити доставку, повернути кошти чи відповісти клієнту швидко,
бізнес фактично не працює, навіть якщо головна сторінка завантажується.