Каталог на 200 тысяч товаров в Битрикс: что ломается первым
Размер каталога сам по себе редко кладёт Битрикс. Первыми обычно подводят кеш, умный фильтр, сортировка и расчёт цен. Разбираем механику каждого узла и даём способ найти слабое место замерами.

Каталог на 200 тысяч товаров редко падает от размера таблиц. Чаще картина такая: в рабочее время идёт обмен с 1С, кеш разделов сбрасывается, посетители приходят за страницами, которых в кеше уже нет, и каждый запускает одну и ту же тяжёлую сборку. Процессы php-fpm заканчиваются, MySQL тонет в одинаковых запросах, nginx отдаёт 502 и 504. Ниже порядок, в котором на таком каталоге обычно выходят из строя узлы Битрикс, и способ проверить, что из этого у вас. Речь об актуальных редакциях «1С-Битрикс: Управление сайтом» (ядро 22.x и новее) с модулями iblock и catalog и о MySQL 8.0. Порядок выведен из механики ядра, а не из замеров на вашем сервере.
Кеш: ломается первым, и не из-за размера
При включённом тегированном кешировании компоненты каталога регистрируют тег инфоблока. Любое изменение элемента этого инфоблока, будь то правка в админке или строка из выгрузки 1С, сбрасывает кеш всех компонентов, которые его показывают. На маленьком каталоге это незаметно. На 200 тысячах товаров обмен идёт долго, и кеш листингов обнуляется всё это время.
Дальше работает эффект толпы. Пока один запрос пересобирает раздел, соседние тоже не находят кеша и запускают ту же сборку параллельно.
Пояснение: эффект толпы (cache stampede) — ситуация, когда после сброса кеша множество параллельных запросов одновременно пересобирают одно и то же значение и перегружают источник данных.
Три вещи делают это хуже:
CACHE_FILTERв компонентах каталога. При включённом кешировании с фильтром каждая комбинация значений умного фильтра получает свой файл кеша. Ключей становится очень много, а попаданий в кеш почти нет.CACHE_GROUPS. Если цены или доступ зависят от групп пользователей, кеш одной страницы хранится отдельно для каждого набора групп.- Файловый кеш в
/bitrix/cache. Мелких файлов становится так много, что inodes могут кончиться раньше места на диске, проверьтеdf -i. Memcached или Redis снимают эту проблему, но формат настройки хранилища сверьте с документацией своей версии. После их перезапуска вы получите холодный кеш и ту же толпу.
Каталог падает не тогда, когда в нём много товаров, а тогда, когда кеш сбрасывается быстрее, чем успевает наполняться.
Умный фильтр и хранение свойств
В инфоблоках версии 1 значения всех свойств лежат в одной общей таблице, и фильтр по нескольким свойствам превращается в серию соединений с ней. Версия 2 хранит единичные свойства в отдельной таблице на инфоблок, множественные тоже отдельно, и листинг с десятками свойств обходится дешевле. Конвертация меняет структуру базы, поэтому сначала репетиция на копии и бэкап.
Для умного фильтра нужен фасетный индекс. Без него подсчёт доступных значений идёт тяжёлыми запросами на каждый показ. С индексом читать дешевле, но его надо поддерживать: каждое изменение товара обновляет индекс, и на крупном обмене это заметная часть времени.
Пояснение: фасетный индекс — служебная таблица, где для каждого товара заранее записаны значения свойств и цен в виде, удобном для быстрого подсчёта вариантов фильтра.
Сортировка, пагинация и цены
Сортировка по полю элемента, например по ID или дате, обычно идёт по индексу. Сортировка по свойству или по цене требует соединения с другой таблицей, и в EXPLAIN вы увидите Using filesort или временную таблицу. Глубокие страницы пагинации читают и отбрасывают все предыдущие строки, а подсчёт общего числа для навигации обходится отдельным запросом.
Следом идут цены. В листинге для каждого товара считается оптимальная цена с учётом типов цен, скидок и правил каталога. Это нагрузка на PHP, а не на базу, поэтому в журнале медленных запросов её не видно. Ищите её профилировщиком PHP: Xdebug, XHProf или Blackfire.
| Узел | Как проявляется | Как увидеть |
|---|---|---|
| Сброс кеша при обмене | Всплеск нагрузки и 502 после старта выгрузки | Время создания файлов в /bitrix/cache, занятые процессы php-fpm |
| Комбинации фильтра в кеше | Кеш растёт, попаданий мало | Число файлов и inodes, доля попаданий в Memcached или Redis |
| Свойства и умный фильтр | Долгие запросы к таблицам значений свойств | Журнал медленных запросов, EXPLAIN |
| Сортировка и глубокие страницы | Время растёт с номером страницы | EXPLAIN: Using filesort |
| Цены и скидки в листинге | Высокий CPU у PHP при спокойной базе | Профилировщик PHP |
Замеры: что мерить, если цифр нет
Чужие цифры здесь бесполезны: результат зависит от железа, числа свойств и схемы скидок. Снимите свои, в таком порядке.
- Включите журнал медленных запросов на сутки. Нужны права на
SET GLOBAL. Если их нет, как на части хостингов, используйте раздел «Производительность» (модуль perfmon) в админке. - Запустите обмен с 1С и отметьте время начала и конца. Смотрите, как часто в
/bitrix/cacheпоявляются новые файлы и сколько процессов php-fpm заняты. Для этого включите страницу статуса черезpm.status_path: строкаmax children reachedпокажет, что пул упирался в лимит. - Возьмите запросы с наибольшим суммарным временем, выполните для них EXPLAIN и сопоставьте с таблицей выше.
- Снимите время генерации раздела при тёплом и холодном кеше. Разница показывает, сколько стоит каждый сброс.
Какие таблицы тяжелее всего (MySQL 8.0, выполняйте в базе сайта; число строк для InnoDB приблизительное):
SELECT table_name,
table_rows AS approx_rows,
ROUND(data_length / 1024 / 1024) AS data_mb,
ROUND(index_length / 1024 / 1024) AS index_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND (table_name LIKE 'b\\_iblock%'
OR table_name LIKE 'b\\_catalog%'
OR table_name LIKE 'b\\_search%')
ORDER BY data_length + index_length DESC
LIMIT 15;Журнал медленных запросов на время диагностики. Настройки действуют до перезапуска MySQL; после замеров верните прежние значения (по умолчанию long_query_time равен 10):
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 0.5;
-- Путь к файлу журнала:
SHOW VARIABLES LIKE 'slow_query_log_file';Где подход ломается
- Торговые предложения. У них отдельный инфоблок со своим тегом и своими таблицами, а предложений обычно больше, чем товаров. Считайте нагрузку по обоим инфоблокам.
- Отключение тегированного кеша. Оно снимает толпу, но изменения цен и остатков появятся на сайте только по истечении
CACHE_TIME. Для магазина с живыми остатками это не вариант. - Нагрузка в PHP. Внешний кеш и фасетный индекс не вылечат тяжёлый код шаблонов и расчёта цен. Если упирается CPU, база ни при чём.
- Агенты на хитах. По умолчанию агенты выполняются на запросах посетителей, и тяжёлый агент замедляет случайную страницу. Перенос на cron описан в документации.
Композитный кеш и полностраничный кеш на nginx — отдельная тема, здесь не разбираем.
Что делать дальше
Поставьте на график три показателя: время генерации страницы раздела при тёплом кеше, долю запросов, попавших в кеш, и число занятых процессов php-fpm в моменты обмена. Если обмен совпадает с пиками нагрузки, сначала перенесите его на ночь и разбейте на части, и только потом правьте индексы и шаблоны. Это дешевле остальных шагов и сразу покажет, был ли кеш причиной. Затем идите по таблице сверху, начиная со строки, на которую в профиле приходится больше всего времени.
Автоэксперт / механик: Расшифровка OBD2-кодов, выбор б/у авто, диагностика по симптомам.
Спросить за 12 ₽Источники: Документация 1С-Битрикс для разработчиков, MySQL 8.0 Reference Manual: EXPLAIN Output Format
Частые вопросы
А если у меня другая версия Битрикс или MySQL?
Поможет ли просто добавить ядер и памяти?
Сайт уже падает во время обмена. Что сделать сразу?
Безопасно ли переводить кеш на Memcached или Redis в проде?
Нужен ли отдельный поисковый движок для 200 тысяч товаров?
Достаточно ли перевести инфоблоки на версию 2?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.