15k+ заказов в месяц — не рекорд, но уже тот уровень, на котором стандартная конфигурация 1С-Битрикс начинает сыпаться на пиках: распродажи, рассылки, боты индексации, набежавшие одновременно. Две вещи снимают основную нагрузку с PHP и MySQL: правильно настроенный композитный кеш и индексы под реальные запросы каталога. Настройка PHP-FPM, OPcache и Redis под сессии или объектный кеш — отдельная тема, здесь не про неё. Дальше — почему именно кеш и индексы, а не что-то экзотическое. Остальные механизмы кэша Битрикс — тегированный кэш D7, highload-блоки, хранилище на memcached/redis — разобраны отдельно, в гайде по кэшированию.
Композитный кеш кеширует не всю нагрузку
Что кешируется, а что нет
Композит делит страницу на две части: статичную "оболочку" — HTML, который на первом запросе к URL сохраняется на диск и дальше отдаётся сразу, — и динамические зоны (корзина, данные пользователя, персональные блоки), которые разработчик помечает отдельно в компонентах. Динамические зоны подгружаются отдельным AJAX-запросом уже после того, как статика показана в браузере.
Это работает не только для анонимных посетителей — композит можно включить и для авторизованных групп пользователей, если правильно настроить список групп в настройках (важно не включить туда группы с доступом к административной панели — частая ошибка конфигурации). Но чем персонализированнее страница, тем больше в ней динамических зон и тем меньше реальный выигрыш от статичного кеша — часть работы всё равно уходит в отдельный запрос при каждом показе.
Момент инвалидации
Второе больное место — момент инвалидации. Сбрасывается кеш не по конкретному URL, а по тегам: у каждой закешированной
страницы есть привязанные теги (например, iblock_id_N для инфоблока), и при изменении данных Битрикс сбрасывает
все страницы с этим тегом разом. Если тег расставлен грубо — на весь инфоблок целиком — смена цены одного товара
сбрасывает кеш всего каталога, а не одной карточки. Файл не пересоздаётся сам по себе — он генерируется заново
на первый же запрос к сброшенной странице: этот запрос идёт в PHP и MySQL напрямую, дальше страница снова отдаётся из файла.
PHP может не запускаться вообще
Главный выигрыш композита — не в том, что PHP отдаёт готовый HTML быстрее, а в том, что PHP может не запускаться вообще.
Готовая страница физически лежит файлом в bitrix/html_pages/, и при настроенной отдаче этого пути веб-сервером напрямую
(например, try_files в nginx, аналогичное правило в Apache или готовая конфигурация Bitrix VM) файл отдаётся, минуя
PHP-FPM целиком. Без этой настройки композит просто ускоряет PHP-путь, а не убирает его.
Запрос к странице
↓
Веб-сервер: есть готовый HTML-файл в bitrix/html_pages/?
↓ да ↓ нет
Отдать файл напрямую, Передать запрос в PHP-FPM
PHP не запускается ↓
Собрать страницу (PHP + MySQL)
↓
Сохранить HTML-файл на диск
↓
Отдать собранную страницу
Что с этим реально делать:
- проверить, настроена ли у веб-сервера отдача из
bitrix/html_pages/напрямую — без этого композит работает, но не даёт главного эффекта; - не считать композит защитой от нагрузки "по умолчанию для всех" — то, для каких групп он реально включён, нужно проверять в настройках, а не предполагать;
- на страницах с большим количеством персонального контента (личный кабинет, оформление заказа, сравнение товаров, персональные рекомендации) выигрыш от композита ограничен — там динамических зон больше, чем статики;
- расставлять теги кеша по смыслу (на элемент, не на весь инфоблок) — это доработка компонента/шаблона, не галочка в настройках, но именно она не даёт сбросу одной цены чистить весь каталог разом.
Если кратко: композит убирает обращение к PHP только пока на диске лежит готовый HTML-файл и веб-сервер отдаёт его напрямую — как только кеш сброшен или страница персональная, часть работы возвращается в PHP и MySQL.
Индексы под каталог — то, что реально экономит время БД
Почему EAV дорого при фильтре по нескольким свойствам
1С-Битрикс хранит товары и их свойства по модели "элемент + значения свойств" (EAV) — гибко для админки, но каждое дополнительное свойство в фильтре превращается в лишний JOIN. На каталоге в несколько тысяч позиций это не заметно. На 100k+ позиций и фильтре по 3-4 свойствам разница между "есть индекс под нужную комбинацию колонок" и "индекса нет" — это разница между запросом на десятки миллисекунд и запросом на секунды под нагрузкой.
Фасетный индекс — сначала проверить, потом писать вручную
Для фильтра по свойствам (умный фильтр в каталоге) в Битриксе есть штатный механизм — фасетный индекс (Контент → Инфоблоки → Фасетные индексы). Он один раз собирает свойства элементов в отдельную плоскую таблицу и дальше фильтрует по ней, а не по цепочке JOIN'ов через EAV-таблицы. Для стандартного фильтра каталога это первое, что стоит проверить — построен ли уже фасетный индекс, — а не сразу писать вручную индексы под таблицы свойств: MySQL такой ручной индекс использует как любой другой, и сама пересборка фасетного индекса эти таблицы не трогает — переиндексация работает со своей отдельной служебной таблицей. Риск для ручного индекса в другом месте — при изменении настроек самого свойства (например, при конвертации свойства из одиночного в множественное) Битрикс меняет структуру таблицы значений, и вручную поставленный на неё индекс может быть потерян.
Запрос каталога с фильтром по свойствам
↓
Фасетный индекс включён и построен?
↓ да ↓ нет
Фильтр по плоской служебной таблице Фильтр через JOIN
фасета, без EAV-присоединений по EAV-таблицам свойств
↓ ↓
Быстро Медленно на 100k+ строк —
смотреть EXPLAIN, добавлять
индекс под конкретный запрос
Ручные индексы — под конкретный запрос, не заранее
Ручные индексы MySQL нужны там, где штатный фасетный индекс не участвует — под конкретный медленный запрос, который
не покрывается умным фильтром (нестандартная сортировка, кастомная выборка в компоненте и т.п.). Готового универсального
примера тут нет и не должно быть — какой индекс нужен, зависит от конкретного запроса и от того, какие индексы на
таблице уже есть. Смотреть по факту: включить perfmon, найти в логе медленных запросов конкретный SQL, прогнать через
EXPLAIN и добавлять индекс под то, что реально показал план выполнения — не заранее и не по аналогии с чужим примером. Разбор конкретных случаев — фильтр по нескольким множественным свойствам, диапазон цены вместе с наличием, сортировка с глубокой пагинацией и перенос части данных в HL-блоки — в отдельном посте про индексы под каталог 100k+ товаров.
Практическое правило: индекс должен соответствовать не структуре таблицы, а конкретному запросу, который реально выполняется в фильтре и сортировке каталога.
Если кратко: сначала проверяем, включён и построен ли фасетный индекс, затем по логу медленных запросов и EXPLAIN находим,
что реально им не покрыто, и только тогда добавляем свой индекс — под конкретный запрос, а не по аналогии.
Итог
Ни композитный кеш, ни индексы сами по себе не "решают" нагрузку целиком — они снимают конкретную её часть: композит — повторный полный рендер статичной части страницы, индексы — полное сканирование таблиц под фильтрами каталога. Остальное (личный контент на странице, момент инвалидации кеша, редкие тяжёлые запросы) остаётся и требует отдельного внимания — про это дальше отдельными постами.
