Ускорение сайта путём выявления проблемных участков кода: xDebug + phpStorm

Kate

Administrator
Команда форума
Статья будет полезна джунам и миддлам кто разрабатывает сайты, кто занимается оптимизацией сайтов и кто хочет посмотреть на работу php кода "с высоты". Для себя из полезного можно узнать как связать вместе OpenServer, PhpStorm и xDebug. Один раз настраиваете и можно потом запросто делать отладку. И так, начнём.

Описание проблемы​

Есть сайт. Работает на WP + wooCommerce. Количество продуктов ~21 000, категорий ~4 000. Куча своих таксономий, пользовательских типов записей. Генерация одной страницы занимает 4-5 секунд, иногда доходит до 10. Пользователи со всей статикой и с задержками сети ждут ещё 2-3 секунды. Задача: надо выявить что так тормозит сайт.

Конфигурация сервера: CentOS 8, ISPmanager 6 Lite. RAM 16 Гб, 4 процессора (именно какие не смог узнать, там VDS не показал с теми доступами что у меня были, а я сильно не интересовался, ну тут это не особо важной роли играет).

Для такой конфигурации и трафика который не превышает в сутки 500 уников такая долгая генерация однозначно патология. Взялся изучать...

Первичный анализ​

Для начала сделал бекапы. Установил плагин Query Monitor который абы как показывает запросы в БД. Зацепка есть: около ~15000 запросов.

У WooCommerce есть такая проблема - по умолчанию при выводе категорий он показывает сколько там находится продуктов рядом с названием категории в скобках. Если кому интересно, issue. Так вот, Query Monitor сможет показать свойственный этой особенности запросы и одной строчкой можно исправить ситуацию. Но на этот раз проблема в другом.

На первый взгялд не понять в чем проблема. Тяжелых плагинов нет, тема не на каком-то Elementor или WP Bakery, создан с нуля и расширена плагином Advanced Custom Fields, все файлы разбиты по папкам и названы адекватно. Копаем глубже.

Разворачиваем среду отладки​

С помощью плагина Duplicator быстро взял копию сайта. Исключил все медиа, изображения, архивы и папку wp-content/uploads целиком. Они нам не нужны для анализа, но могут здорово раздуть архив что сервер упрётся в лимит, либо браузер покажет 504 Gateway time out.

У меня установлен OpenServer версии 5.3.8, создаю директорию, закидываю файл с архивом и с файлом installer.php что выдаёт duplicator. Перехожу по адресу mydevsite.domain/installer.php и заполняю все данные что просит dup-installer. Не буду тут долго останавливаться, поднять копию сайта проще простого.

Открываю проект в phpStorm. IDE довольно удобный, но для текущего кейса можно было и без него обойтись. Поэтому если у вас другой IDE не торопитесь уходить, нам он нужен только для просмотра логов xDebug.

Включить xDebug в OpenServer​

Если у вас как и у меня установлен OpenServer то отдельно устанавливать расширение xDebug не надо, он уже установлен, достаточно в конфигурации php.ini раскомментировать нужные строчки и немного настроить. Если что-то другое документация к xdebug тут.

Чтобы открыть php.ini надо проделать следующее:

Клик по иконке OpenServer в нижнем меню -> Дополнительно -> Конфигурация -> php7.4.

У меня php версии 7.4, если у вас другая версия там будет соответствующее название.

В открывшемся окне с конфигами находим строчку ;zend_extension = xdebug и убираем с начала строки точку с запятой (раскомментируем). Чуть ниже в окне будут уже сами конфиги xdebug. Опускаемся или находим поиском строку [xdebug] и вносим правки ниже:

xdebug.mode = profile,trace
xdebug.start_with_request = "trigger"
Тут xdebug.mode указывает что мы хотим профилировать и трассировать код. По умолчанию там стоит off. Есть ещё другие режимы, но на них не буду останавливаться, этих двух нам достаточно. xdebug.start_with_request указывает что включать режим отладки надо не всегда, а только если передать специальный параметр (через POST, GET или COOKIE, удобнее всего GET параметр). И да, этот параметр по умолчанию закомментирован, не забудьте раскомментировать.

Как выглядит конфиг php.ini у меня.
Как выглядит конфиг php.ini у меня.
Всё. xDebug готов, перезапускаем OpenServer. Теперь при переходе на любую страницу нам достаточно прописать ключ get параметра XDEBUG_PROFILE и php будет собирать отладочную информацию и записывать в папку что указан по пути xdebug.output_dir (у меня стоит путь по умолчанию). Про другие особенности можно прочитать тут.

Вид URL будет иметь такой вид mydevsite.domain/?XDEBUG_PROFILE если надо собрать информацию с главной страницы. В моем случае я снял снепшот с главной страницы.

Просмотр логов в красивом виде​

Для просмотра логов я использую phpStorm. Но гугл подсказывает что для vsCode или Atom тоже есть решения. В самом верхнем меню находим раздел Tools и в списке Analyze Xdebug Profiler Snapshot. Нас попросит выбрать файл, переходим по пути где указали запись файлов, у меня по умолчанию это {{ папка openServer где он установлен }}/userdata/temp/xdebug/

Если всё сделано правильно там будет записанный файл с названием cachegrind.out.{{ какие-то цифры }}. Выбираем, ждём когда phpStorm обработает всё и вуаля, у нас есть работа скриптов с высоты.

Выявляем проблемный участок кода​

phpStorm показывает логи в красивом и удобном виде. На первом окне смотрим сколько вообще времени заняло выполнение скрипта. Но нас интересует вкладка Call Tree (ниже выделил куда кликать).

e345ee747bbb50c93fcdead724ddb0e5.png

И так у нас уже есть какие-то цифры. Раскрываем по одному файлы чтобы понять где лежит проблемный участок. Нас интересует ветка с файлом template-loader.php, нам надо ускорить написанную с нуля тему, а не копать в сторону ядра WP/

Ориентируясь цифрами, сколько процентов какая ветка сожрала, опускаемся до нужного файла и участка кода. В моем случае функция get_heder использовала 42% от всего времени. Смотря ещё глубже нахожу что это функция get_catalog_menu который жрал 36%. Кавабанга!

Файл header.php
Файл header.php
Опускаемся ниже и находим функцию тяжеловес
Опускаемся ниже и находим функцию тяжеловес
Находим файл в котором находится функция. Можно кликнуть по ПКМ и сделать jump to source. В нашем случае это оказался файл menu.php.

Вкратце - функция выбирает каждый раз все 4 000 категории и как-то делая проверки строит из них меню. Каждая проверка условии для одной категории это одно обращение к БД. В целом кусок кода довольно тяжелый.

Разработчик учёл что эта функция сильно нагружает код и сделал кеширование через WP Object Cache. Но не учёл что кеш работает только если сервер поддерживает кеширование и на сайте WP установлен плагин для объектного кеширования. А в моем случае на сервере он не работал и кеш не создавался. Про эту особенность можно прочитать тут.

Решение​

Заказчик сказал что сайт мало когда редактируется, категории тем более, поэтому не стали переделывать код и оставили всё как есть для удобства. Только вместо объектного кеширования сделал хранение кеша в транзиентах.

И была такая же функция, только для менюшек в сайдбаре. Их тоже сделал чтобы кешировались. Итог - сайт летит.

Поверх всего провёл ещё ряд других оптимизаций и настройку сервера (сжатие gzip, время жизни кеша для статики, апдейт версии php до 8.0, очистка базы данных от мусора, вынесение стилей и скриптов в отдельные файлы).

Итог​

Результаты ДО. ~9 секунд на выполнение.
Результаты ДО. ~9 секунд на выполнение.
Результат после. Выполение занимает ~6 секунд.
Результат после. Выполение занимает ~6 секунд.
Результаты до/после тут только после исправления всего двух функций. После были выполнены ещё другие мелкие оптимизации, настроен плагин полностраничного кеширования и суммарно для конечного пользователя сайт работает раза 4-5 быстрее. Отклик кешированных страниц занимает ~400мс, а не кешированных в зависимости от типа 1,5-2 секунд.

В целом заказчик доволен. Мы не ставили цель разогнать сайт до максимума на данном этапе. В будущем возможно с ростом трафика будем ещё переделывать кодовую базу. Пока на этом всё. Мне будет интересно прочесть комментарии от других разработчиков что можно было делать лучше, а где были допущены ошибки.

 
Сверху