Каталог на 200 000 товаров в Битрикс: что ловить на ревью
Раздел с фильтром и сортировкой по цене на 200 тысяч товаров держит MySQL секундами, а импорт цен сбрасывает кеш быстрее, чем он прогревается. Разбираем, что искать в коде, как формулировать замечания и проверять рефакторинг.
Карточка товара открывается быстро, а страница раздела с фильтром по двум свойствам и сортировкой по цене держит соединение с MySQL секундами. На 20 тысячах товаров это терпели. На 200 тысячах кеш уже не спасает: фильтр даёт тысячи комбинаций, а импорт цен сбрасывает кеш быстрее, чем он успевает прогреться. Ниже: что искать в коде при ревью, как формулировать замечания и как проверить, что рефакторинг помог. Ориентир: «1С-Битрикс: Управление сайтом» 23.x и новее, инфоблоки 2.0, MySQL 8.0 или совместимая MariaDB. Названия параметров и поведение в вашей редакции сверяйте с документацией.
Сначала измерьте, потом правьте
Ревью без замеров превращается в спор о вкусах. Соберите до правок три вещи:
- Медленные запросы. В MySQL включите
slow_query_log, задайте низкийlong_query_timeи смотрите топ по суммарному времени, а не по одиночным выбросам. - План выполнения. Для каждого запроса из топа сделайте
EXPLAIN. Тревожные признаки в колонке Extra: Using temporary и Using filesort на больших выборках. - Картину страницы. Панель «Отладка» и модуль «Монитор производительности» (perfmon) показывают, какой компонент породил запрос.
Сразу записывайте условия: сколько торговых предложений (200 тысяч товаров и 200 тысяч товаров по пять предложений - разные каталоги), холодный или тёплый кеш, какая параллельность.
Что искать в коде
Свойства в фильтре и сортировке
В инфоблоках 2.0 одиночные свойства лежат в таблице b_iblock_element_prop_s с номером инфоблока, множественные - в b_iblock_element_prop_m. Фильтр или сортировка по PROPERTY_* добавляют джойн, а множественное свойство ещё и размножает строки. Сортировка по такому свойству на всём разделе заставляет базу собрать временную таблицу и отсортировать её. Замечание для ревью: «Сортировка по свойству в списке на 200 тысяч строк. Нужна сортировка по полю элемента (SORT, ID, дата) или по заранее посчитанному значению». Отдельно проверьте выборки, которые тянут все свойства, когда шаблону нужны три.
Цены и остатки в шаблоне
Цены лежат в b_catalog_price, остатки по складам в b_catalog_store_product. Если шаблон на каждой итерации вызывает CCatalogProduct::GetOptimalPrice или делает свой запрос за остатком, это N+1: страница на 36 товаров получает десятки лишних запросов, и каждый считает скидки. Часто это дублирует работу компонента, который уже положил цены в $arResult. Замечание: «Цены и остатки страницы получаем одним запросом по списку ID, а не по одному в цикле».
Счётчики и глубокая пагинация
Два дорогих места: подсчёт общего числа элементов для постраничной навигации и счётчики товаров в разделах (параметр COUNT_ELEMENTS у catalog.section.list). Подсчёт по дереву разделов с фильтром по активности и правам на каждом показе - отдельный тяжёлый запрос. Глубокие страницы тоже платят: чем дальше страница, тем больше строк база пропускает. Выводите счётчики только там, где они нужны, кешируйте их отдельно с длинным TTL, а глубину пагинации ограничьте или переходите на выборку по условию на ID.
Кеш и его сброс
Включённый кеш компонента ещё не работающий кеш. Проверьте CACHE_TYPE и CACHE_TIME, затем CACHE_FILTER: при значении N страницы с фильтром не кешируются вовсе. Затем CACHE_GROUPS: при Y кеш размножается по сочетаниям групп пользователей.
Главная ловушка - тегированный кеш. Изменение элемента инфоблока сбрасывает кеш компонентов, привязанных к этому инфоблоку. Если импорт обновляет элементы построчно, а остатки приходят каждые несколько минут, каталог живёт с холодным кешем. Сбрасывает ли тег ваш способ записи цен и остатков, проверяйте на своей версии: обновите одну запись и посмотрите, пересоздался ли кеш раздела.
Если каталог меняется чаще, чем прогревается кеш, оптимизировать надо источник сбросов, а не время жизни кеша.
Умный фильтр и фасетный индекс
Фасетный индекс (пояснение в конце) нужен фильтру на каталоге такого размера: без него варианты значений считаются прямыми запросами по таблицам свойств. Проверьте, что индекс включён у инфоблока и перестроен после последнего изменения набора свойств. Устаревший индекс даёт неполные значения или медленный путь, конкретное поведение зависит от версии.
Как формулировать замечания
| Что видно в коде | Замечание в ревью | Чем заменить |
|---|---|---|
| Сортировка или фильтр по множественному свойству | Джойн с размножением строк на всём разделе | Поле элемента, фасетный индекс, предрасчёт значения |
| Цена или остаток в цикле шаблона | N+1, скидки считаются на каждый товар | Пакетная выборка по списку ID, данные из $arResult |
COUNT_ELEMENTS на всём дереве | Тяжёлый подсчёт на каждый показ | Отдельный кеш с длинным TTL, счётчики не везде |
| Обновление по одной записи в цикле импорта | Каждый апдейт сбрасывает кеш каталога | Обновлять только изменившееся, группировать пачками |
CACHE_FILTER = N или CACHE_GROUPS = Y без причины | Кеш не работает или размножается | Явно обосновать значение или поменять |
Замеры до и после
Готовых цифр здесь не будет: они зависят от железа, конфигурации MySQL и числа свойств. Мерьте так:
- Зафиксируйте эталонные URL: раздел, раздел с фильтром, глубокая страница, поиск, карточка.
- Для каждого записывайте время ответа при холодном и тёплом кеше, число SQL-запросов и суммарное время в базе.
- Дайте нагрузку, близкую к пиковой, судя по логам веб-сервера. Подойдут ab, wrk или k6.
- Меняйте по одному замечанию за раз и повторяйте замер.
- Сравнивайте p95, а не среднее: средние скрывают медленный хвост.
Где подход ломается
- Переписать
CIBlockElement::GetListна D7 (ElementTable::getList) - не оптимизация, если SQL остался тем же. Смотрите на запрос, а не на API. - Обработчики событий вроде
OnAfterIBlockElementUpdateв кастомных модулях умножают цену каждого апдейта. Импорт может упираться в них, а не в базу. - Не отключайте проверку прав ради скорости, если на неё опирается видимость товаров. Расширенные права добавляют джойны, но это цена безопасности.
- Если данные и индексы таблиц каталога не помещаются в буферный пул (
innodb_buffer_pool_size), любая правка запросов упрётся в диск. - Индексы, добавленные вручную в таблицы ядра, перепроверяйте после обновлений платформы.
Что делать дальше
Возьмите пять запросов из топа медленного лога, прогоните EXPLAIN и оформите по одному замечанию в той форме, что в таблице. Заведите мониторинг p95 для трёх эталонных URL, чтобы регрессии были видны после каждого релиза. Если фасетный индекс и кеш не вытягивают подбор и поиск, следующий шаг - внешний поисковый движок. Это отдельная тема, здесь её не разбираем.
Фасетный индекс: служебные таблицы инфоблока, в которых значения свойств заранее разложены для быстрого подбора по фильтру. Его нужно включать и перестраивать при изменении набора свойств.
Таможенный брокер (ВЭД): Коды ТН ВЭД, маркировка «Честный ЗНАК», сертификация.
Спросить за 20 ₽Источники: Документация 1С-Битрикс: CIBlockElement::GetList, MySQL 8.0 Reference Manual: EXPLAIN Output Format
Частые вопросы
У меня инфоблок в формате 1.0. Что делать?
Ускорит ли переход на D7 ORM выборки каталога?
Я всё поправил, а запрос остался медленным. Что проверить?
Можно ли добавлять индексы в таблицы Битрикса на проде?
Сколько секунд держать кеш каталога?
Сколько SQL-запросов на странице считать нормой?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.