Профилирование WordPress под нагрузкой: где сайт ломается первым
Профайлер показывает один запрос, а сайт падает от очереди из сотни. Разбираем, как найти узкое место WordPress 6.x до того, как закончатся воркеры PHP-FPM: WP-CLI, slowlog, лог MySQL и autoload.

Расчёт простой. Если на сервере 10 воркеров PHP-FPM, а WordPress строит страницу за 400 мс, потолок составляет 25 запросов в секунду. Двадцать шестой встаёт в очередь, и сайт отдаёт 502 или 504, хотя процессор может простаивать: воркеры ждут базу. Профайлер этого не покажет, он меряет один запрос, а не очередь. Ниже разобрано, что мерить на WordPress 6.x и PHP 8.x, если проблема появляется только под нагрузкой.
Почему профиль одного запроса не отвечает на вопрос
Запрос без страничного кеша проходит три стадии: bootstrap (ядро, плагины, тема), main_query (основной запрос к базе) и template (рендер). Так их делит и пакет WP-CLI profile. В одиночном замере на прогретой машине картина чистая. Под нагрузкой появляется то, чего в нём нет:
- очередь к воркерам: один воркер обслуживает один запрос, остальные ждут;
- повтор одних и тех же SELECT: без постоянного объектного кеша (Redis или Memcached через drop-in
wp-content/object-cache.php) каждый запрос читает базу заново; - автозагружаемые опции в
wp_options: они читаются при каждом запросе, и чем их больше, тем дороже каждый хит; - WP-Cron: по умолчанию он стартует при обращении к сайту, и тяжёлое задание может занять воркер в худший момент;
- admin-ajax.php и ajax-запросы корзины WooCommerce: они не попадают в страничный кеш и всегда доходят до PHP.
Профайлер отвечает, где один запрос тратит время. Нагрузочный тест отвечает, что будет, когда таких запросов много. Нужны оба ответа, и второй обычно неприятнее.
Чем мерить
Один инструмент все вопросы не закрывает. Вот как они делят работу:
| Инструмент | Что показывает | Под нагрузкой на проде |
|---|---|---|
| Query Monitor | запросы, хуки, HTTP-вызовы одного запроса | нет, только на стенде: это один запрос плюс накладные расходы плагина |
| WP-CLI profile | время по стадиям, хукам и колбэкам, число запросов к базе | нет, запускается из CLI на копии сайта |
| Xdebug (профайлер) | полный граф вызовов | нет, сильно замедляет и искажает картину |
| slowlog PHP-FPM | стек вызовов для запросов дольше порога | да, показывает только медленные запросы |
| Лог медленных запросов MySQL | SQL выше заданного порога | да, но слишком низкий порог быстро раздувает файл |
Шаг первый: что внутри одного запроса
Установите пакет в WP-CLI 2.x и снимите профиль на копии сайта, не на проде.
wp package install wp-cli/profile-command:@stable
wp profile stage --url=https://staging.example.com/
wp profile stage bootstrap --spotlight
wp profile hook --all --spotlight --url=https://staging.example.com/
Флаг --spotlight убирает строки с околонулевыми значениями. Смотрите, какой плагин или колбэк съедает больше всего времени и запросов на bootstrap: эту цену сайт платит на каждом хите. CLI запускает PHP без веб-окружения, а OPcache в CLI по умолчанию выключен. Поэтому сравнивайте стадии между собой, а не с цифрами из браузера.
Шаг второй: нагрузка и очередь
Прогоните стенд с данными, близкими к боевым. Пустая база скроет медленные запросы. Для первого прогона хватит ApacheBench, для сценариев с логином и корзиной подойдёт k6:
ab -n 1000 -c 50 https://staging.example.com/
Параллельно включите два лога. В пуле PHP-FPM:
; пул PHP-FPM (путь к файлу зависит от дистрибутива)
pm.status_path = /fpm-status
request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm/slow.log
Страницу статуса закройте по IP на уровне Nginx, иначе она доступна всем. В конфиге MySQL, секция [mysqld]:
slow_query_log = 1
long_query_time = 0.5
slow_query_log_file = /var/log/mysql/slow.log
Во время теста следите за тремя сигналами:
- Сообщение в логе FPM «server reached pm.max_children setting»: воркеры закончились, дальше очередь.
- Поля
listen queueиmax listen queueна странице статуса: чем длиннее очередь, тем ближе 502 и 504. - Повторяющиеся записи в логе MySQL: один и тот же запрос на каждый хит указывает на отсутствие объектного кеша или на кривой плагин.
Автозагрузка опций
Самый частый тихий виновник. Посмотрите, что крупного загружается при каждом запросе:
SELECT option_name, LENGTH(option_value) AS size_bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY size_bytes DESC
LIMIT 20;
Замените префикс wp_ на свой. До WordPress 6.6 флаг принимал в основном значения yes и no, в 6.6 набор расширили, поэтому актуальный список значений сверьте с документацией вашей версии. Опции, оставшиеся от удалённых плагинов, часто весят сотни килобайт и грузятся впустую.
Сноска. Autoload: флаг у строки в wp_options. Такие опции WordPress выбирает одним запросом в начале каждого обращения, нужны они в этом запросе или нет.
Где подход ломается
Страничный кеш прячет проблему
Если тестируете главную с включённым страничным кешем, вы нагружаете Nginx, а не WordPress. Прогоните отдельно URL с обходом кеша: страницы корзины, поиска, личного кабинета и ajax-эндпоинты.
Внешние HTTP-вызовы
Плагин ходит во внешний API через wp_remote_get, таймаут по умолчанию 5 секунд. Если сервис недоступен, воркер занят всё это время. Дюжина таких запросов, и пул пуст при нулевой нагрузке на CPU. В slowlog это видно по стеку, застрявшему на HTTP-вызове.
Сброс кеша под трафиком
После очистки кеша сотни посетителей одновременно строят одну и ту же страницу. Это называется cache stampede, и в тестах на прогретом кеше его не увидеть. Повторите прогон сразу после сброса.
Что фиксировать до и после
Замеры без условий бесполезны. Для каждого прогона записывайте:
- железо, число воркеров PHP-FPM, версии WordPress, PHP и MySQL;
- объём данных: число записей, размер
wp_optionsиwp_postmeta; - параллельность и длительность теста, состояние кеша;
- RPS, долю ошибок 5xx и p95* времени ответа.
*p95 — время, в которое укладываются 95% запросов. Среднее скрывает хвост, а падают сайты именно из-за хвоста.
Что делать дальше
Снимите базовый прогон на стенде и сохраните его условия. Затем меняйте по одному фактору: подключите постоянный объектный кеш, почистите autoload, вынесите WP-Cron в системный cron (DISABLE_WP_CRON в wp-config.php плюс вызов wp cron event run --due-now). После каждого изменения повторяйте тот же прогон. В проде оставьте slowlog и лог MySQL с умеренными порогами и смотрите на длину очереди FPM. Настройка страничного кеша и его инвалидация здесь не разбираются, это отдельная тема.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: wp-cli/profile-command: документация и установка пакета, PHP: конфигурация FPM (директивы пула)
Частые вопросы
Можно ли держать Query Monitor включённым на проде?
У меня другая версия WordPress или PHP. Сработает ли?
Команда wp profile пишет, что такой команды нет. Что делать?
Чем грозит включить slowlog и лог медленных запросов на проде?
Нужно ли отключать встроенный WP-Cron?
Сколько воркеров PHP-FPM ставить?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.