Содержание
Профилирование корзины магазина на Битрикс
Магазин на Бириксе с 510 товарами в корзине тормозит при ее пересчете, наблюдалась задержка от 10 секунд до 2 минут (в моменты сильной нагрузки сервера).
Результаты профилирования PHP с помощью XHProf
- Самая тормозная функция
mysql_query
(запрос к БД) - 27% общего времени выполнения скрипта, 32% - загрузка CPU, причем эта функция вызывается только в 0.5% случаев от общего количества вызовов - Следущие «долгоиграющие» функции
CAllDBResult::GetNext
,CIBElement::GetProperties
- не являются критичными, занимая порядка 4% общего времени каждая. По сути они занимаются «разбором» полученных от БД данных. - Остальные функции выполняются 2% времени каждая, в сумме составляя остальную часть времени выполнения.
- Самые «популярные» функции:
is_array
(534283 вызовов),array_key_exists
(338574),preg_match
(233013) являются «нативными» для PHP, выполняются быстро
Таким образом, самое узкое место - выполнение запросов к БД.
Результаты профилирования запросов к БД с помощью Percona
- Практически все запросы кешируются (это хорошо)
- Время выполнения медленных запросов: 128 мс в 95% случаев (это довольно медленно), 560 мс - общее время выполнения медленных запросов (это хорошо), т.е. медленные запросы есть, но это не критично
- Общее количество запросов для перестроения страницы в корзине: 20956 (это очень плохо)
Таким образом, запросы к БД в целом выполняются быстро, но они берут «количеством».
«Количественные» выборки отлично кешируются в memcached, что гораздо быстрее кеша запросов MySQL.
Анализ кода
50% времени уходит на получение товаров из корзины:
CSaleBasket::DoGetUserShoppingCart(SITE_ID, intval($USER->GetID()), intval(CSaleBasket::GetBasketUserID()), $arErrors, $arCupon);
Это - нативная функция Битрикс. Пути ее оптимизации:
- переписать - нереально
- закешировать вывод - нельзя, так как она оперирует актуальными данными
- ускорить выполнение - только за счет ускорения выполнения запросов к БД
Выявлены грубые ошибки:
CSaleBasket::GetList()
вызывается 515 раз, занимая 7% времени. Причем возвращаемое значение всегда одно и тоже. Следует вызвать этот метод один раз и дальше уже использовать переменную.- Аналогично:
CAllSaleBasket::GetBasketUserID()
(1025 раз, 0,3% времени).
Предложения
- использовать более производительный MySQL сервер:
- задействовать SSD-диски, так как именно дисковая подсистема является главным тормозом
- установить Percona Server (он является клоном MySQL), и использовать его вместо MySQL - производительность выше в 2-3 раза
- не производить «динамический» пересчет при количестве позиций более 50, выводить вместо этого внизу кнопку «пересчитать» - тогда пересчет, хоть и долгий, будет произведен один раз
- переписать систему скидок (реализовано)