Нагрузочное тестирование интернет-магазина 1С-Битрикс
Магазин на Битрикс работает стабильно при 30 посетителях онлайн, а при 300 — страницы каталога открываются по 8 секунд, оформление заказа падает с таймаутом, а в логах nginx — 502 Bad Gateway. Владелец магазина узнаёт об этом в первый день распродажи. Нагрузочное тестирование нужно, чтобы узнать потолок до того, как по нему ударишься лбом.
Инструменты генерации нагрузки
k6 — выбор номер один для большинства проектов. Сценарии на JavaScript, минимальное потребление ресурсов (одна машина выдаёт 5000+ RPS), нативная интеграция с Grafana для визуализации в реальном времени. Хранится в репозитории, запускается в CI/CD. Пример сценария для каталога Битрикс:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // ramp-up
{ duration: '5m', target: 100 }, // plateau
{ duration: '2m', target: 300 }, // stress
{ duration: '5m', target: 300 }, // hold
{ duration: '2m', target: 0 }, // ramp-down
],
};
export default function () {
// Главная → раздел → фильтрация → карточка товара
let res = http.get('https://shop.example.com/catalog/electronics/');
check(res, { 'catalog 200': (r) => r.status === 200 });
res = http.get('https://shop.example.com/catalog/electronics/?filter_brand=samsung&filter_price_from=10000');
check(res, { 'filter 200': (r) => r.status === 200 });
res = http.get('https://shop.example.com/catalog/electronics/samsung-galaxy-s24/');
check(res, { 'product 200': (r) => r.status === 200 });
sleep(Math.random() * 3 + 1); // пауза 1-4 сек, имитация реального пользователя
}
Apache JMeter — проверенный стандарт. GUI для создания сценариев, запись через прокси, поддержка cookies и авторизации. Минусы — Java, высокое потребление RAM, один инстанс редко выдаёт больше 1500 RPS. Подходит для команд, привыкших к визуальным инструментам.
Gatling — Scala DSL, неблокирующий I/O, детальные HTML-отчёты с перцентилями. Эффективнее JMeter по ресурсам, но порог входа выше.
| Инструмент | Язык | RAM на 1000 VU | Отчёты | CI/CD |
|---|---|---|---|---|
| k6 | JavaScript | ~200 МБ | Grafana / JSON | Нативная |
| JMeter | GUI / XML | ~2 ГБ | Плагины (JTL) | Через CLI |
| Gatling | Scala DSL | ~500 МБ | HTML встроенные | Нативная |
Четыре сценария, которые нужно тестировать
Нагрузочный тест без реалистичных сценариев — бессмысленная генерация трафика на главную страницу. Для Битрикс-магазина критичны:
Просмотр каталога (60-70% трафика). Главная → раздел → фильтрация → карточка товара. Фильтрация — самое тяжёлое место. Компонент bitrix:catalog.smart.filter при каждом запросе обращается к b_iblock_element_property с серией JOIN по таблицам свойств. На разделе с 1000 товарами и 15 фильтруемыми свойствами — это 30-50 SQL-запросов на один хит. Если компонентный кеш выключен (а он часто выключен «на время отладки» и забыт) — каждый посетитель генерирует полную нагрузку на БД.
Поиск (10-15% трафика). Модуль search использует таблицу b_search_content с FULLTEXT-индексом. На 100 000+ товаров MATCH AGAINST в MySQL деградирует нелинейно. PostgreSQL с tsvector держится лучше, но при конкурентных запросах тоже просаживается. Если используется Elasticsearch — тестируйте отдельно, у него свои лимиты.
Корзина (5-10% трафика). Добавление, изменение количества, применение купона. Каждое действие вызывает пересчёт скидок через \Bitrix\Sale\Discount\RuntimeCache. При 50+ правилах корзины (накопительные скидки, комбо-акции, купоны) пересчёт занимает 200-500 мс. Под нагрузкой это складывается.
Оформление заказа (2-5% трафика, но самый критичный). Создание записей в b_sale_order, b_sale_basket, b_sale_order_props_value, b_sale_payment, b_sale_shipment. Плюс обработчики событий: OnSaleOrderSaved, отправка email, запуск бизнес-процессов. Плюс внешний запрос к API транспортной компании для расчёта доставки. Один checkout — это 50-100 SQL-запросов и 1-3 HTTP-запроса к внешним сервисам.
Deep-dive: профилирование и поиск узких мест
Нагрузочный тест показывает что тормозит. Профилирование — почему.
Xhprof / Tideways — расширения PHP для профилирования с минимальным оверхедом (5-15%). В отличие от Xdebug, можно включать на продакшене под нагрузкой. Xhprof генерирует callgraph — дерево вызовов с временем и потреблением памяти каждой функции. Типичная находка: CIBlockElement::GetList() вызывается 47 раз на одной странице каталога, потому что шаблон компонента дёргает данные повторно.
Подключение xhprof в Битрикс:
// /local/php_interface/init.php
if ($_GET['__xhprof'] === 'on' && CUser::IsAdmin()) {
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function() {
$data = xhprof_disable();
$xhprofLib = '/path/to/xhprof_lib/utils/xhprof_lib.php';
$xhprofRuns = '/path/to/xhprof_lib/utils/xhprof_runs.php';
include_once $xhprofLib;
include_once $xhprofRuns;
$runs = new XHProfRuns_Default();
$runs->save_run($data, 'bitrix');
});
}
Slow query log — обязателен во время нагрузочного теста. Для MySQL: slow_query_log = 1, long_query_time = 0.3 (300 мс — порог для e-commerce). Для PostgreSQL: log_min_duration_statement = 300.
Типичные находки в slow query log Битрикс-магазина:
-- Фильтрация каталога без фасетного индекса
SELECT DISTINCT BE.ID FROM b_iblock_element BE
INNER JOIN b_iblock_element_property BEP1 ON BEP1.IBLOCK_ELEMENT_ID = BE.ID AND BEP1.IBLOCK_PROPERTY_ID = 45
INNER JOIN b_iblock_element_property BEP2 ON BEP2.IBLOCK_ELEMENT_ID = BE.ID AND BEP2.IBLOCK_PROPERTY_ID = 52
INNER JOIN b_iblock_element_prop_m67 BEPM ON BEPM.IBLOCK_ELEMENT_ID = BE.ID AND BEPM.IBLOCK_PROPERTY_ID = 67
INNER JOIN b_catalog_price CP ON CP.PRODUCT_ID = BE.ID AND CP.CATALOG_GROUP_ID = 1
WHERE BE.IBLOCK_ID = 15 AND BE.ACTIVE = 'Y'
AND BEP1.VALUE_NUM BETWEEN 10000 AND 50000
AND BEP2.VALUE = '1456'
AND BEPM.VALUE = '2891'
ORDER BY CP.PRICE ASC
LIMIT 20;
-- Time: 4.2s, Rows examined: 2,340,000
Пять JOIN по таблицам свойств, сортировка по цене, 2.3 миллиона просмотренных строк. На 50 000 товаров это типичная картина.
Blackfire — коммерческий профайлер от SensioLabs. Минимальный оверхед, сравнение между версиями кода, интеграция с CI/CD. Можно задать assertion: «время страницы каталога не превышает 400 мс» — и получать алерт при деградации после деплоя.
Типичные узкие места Битрикс
Компоненты без кеша. bitrix:catalog.section с CACHE_TIME = 0 — каждый хит генерирует запросы к b_iblock_element, b_iblock_element_property, b_catalog_price. Решение очевидно: CACHE_TIME = 3600, тегированный кеш через \Bitrix\Iblock\TaggedCache. При обновлении товара в админке кеш сбрасывается автоматически.
Множественные свойства инфоблоков. Каждое множественное свойство хранится отдельной строкой в b_iblock_element_prop_m{IBLOCK_ID}. Фильтрация по трём множественным свойствам — три дополнительных JOIN. На 50 000 товаров с 20 свойствами таблица свойств содержит миллионы строк. Фасетный индекс (b_catalog_smart_filter) решает проблему, но его нужно перестроить после массовых обновлений каталога.
OPcache. Без OPcache Битрикс компилирует PHP при каждом запросе. Ядро — тысячи файлов. Минимальные настройки:
opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0 # на продакшене
opcache.revalidate_freq = 0
Проверка: opcache_get_status(). Если cache_full = true — увеличивайте memory_consumption.
Сессии в файлах. При 500+ одновременных пользователях каталог /tmp/sess_* содержит тысячи файлов. ext4 справляется, но с задержками. Решение — Redis: session.save_handler = redis, session.save_path = "tcp://127.0.0.1:6379".
Агенты Битрикс (b_agent). По умолчанию агенты выполняются на хитах — при загрузке страницы Битрикс проверяет, какие агенты нужно запустить, и выполняет их в контексте пользовательского запроса. Тяжёлый агент (переиндексация поиска, пересчёт скидок) добавляет секунды к времени ответа. Решение: define('BX_CRONTAB_SUPPORT', true) в dbconn.php и перенос агентов на cron.
Ключевые метрики
- RPS — запросов в секунду без деградации. Для среднего магазина на Битрикс — 100-300 RPS (с кешированием).
- TTFB — до 200 мс для закешированных страниц, до 500 мс для динамических. Больше 1 секунды — проблема.
- P95 response time — время, в которое укладываются 95% запросов. Именно P95, не среднее. Если среднее 200 мс, а P95 — 4 секунды, каждый двадцатый посетитель ждёт неприемлемо долго.
- Error rate — процент 5xx и таймаутов. При превышении пропускной способности error rate растёт резко, не плавно.
Что делать с результатами
| Проблема | Метрика | Решение |
|---|---|---|
| TTFB > 1 с на каталоге | P95 response time | Компонентный кеш + композитный кеш |
| 502 при 200+ RPS | Error rate | Увеличение pm.max_children в PHP-FPM, тюнинг max_connections в PostgreSQL |
| Slow query > 2 с на фильтрации | Slow query log | Фасетный индекс, составные индексы, переход на Elasticsearch |
| OOM при 300 пользователях | Memory usage | OPcache, memory_limit per-worker, отключение неиспользуемых модулей |
| Таймаут при checkout | TTFB checkout | Асинхронная обработка событий заказа, кеш расчёта доставки |
Когда тестировать
Нагрузочное тестирование — не разовая процедура. Запускайте перед распродажами (Чёрная пятница, 11.11, сезонные акции), после миграции сервера, после обновления ядра Битрикс, после массового импорта товаров. k6 в CI/CD позволяет запускать базовый smoke-тест при каждом деплое — 50 VU на 2 минуты, проверка что P95 не ушёл за пределы нормы. Деградация ловится на ранней стадии, а не в день акции.







