Оптимизация умного фильтра для большого каталога 1С-Битрикс
Умный фильтр начинает деградировать при каталоге от 5 000 позиций. При 50 000+ он может отвечать 5–15 секунд на первый запрос и 1–3 секунды с кешем. Это не проблема алгоритма — это следствие того, что фильтр пересчитывает счётчики для каждого значения каждого свойства при каждом запросе. На реальном проекте с 30 свойствами и 50 000 товарами это тысячи запросов к БД за одно обращение.
Диагностика: где теряется время
Первый шаг — замер, а не угадывание. Включаем SQL-профилировщик:
// В начале скрипта
define('LOG_FILENAME', $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/log_filter.php');
$GLOBALS['DB']->ShowSqlStat = 'Y';
// После отработки компонента фильтра
$sqlLog = $GLOBALS['DB']->GetSqlTrack();
// Смотрим самые медленные запросы
usort($sqlLog, fn($a, $b) => $b['time'] <=> $a['time']);
var_dump(array_slice($sqlLog, 0, 10));
Типичная картина: 80% времени уходит на COUNT-запросы для пересчёта количества товаров по каждому значению свойства при применённом фильтре.
Индексы для ускорения фильтра
Наиболее значимое улучшение без изменения кода — правильные индексы. Умный фильтр активно использует таблицы b_iblock_element_prop_s* (простые свойства) и b_iblock_element_prop_m* (множественные):
-- Проверка существующих индексов
SHOW INDEX FROM b_iblock_element_prop_s15; -- 15 = ID инфоблока
-- Добавление составного индекса для фильтрации
CREATE INDEX idx_filter_prop ON b_iblock_element_prop_s15 (IBLOCK_ELEMENT_ID, PROPERTY_186, PROPERTY_187);
-- Для таблицы множественных свойств
CREATE INDEX idx_mult_prop ON b_iblock_element_property
(IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VALUE_ENUM, VALUE_NUM);
После добавления индексов время ответа на каталоге 30 000 позиций снижается в 2–4 раза без изменения кода.
Параметры кеширования умного фильтра
$APPLICATION->IncludeComponent('bitrix:catalog.smart.filter', 'my_filter', [
'IBLOCK_ID' => 15,
'SMART_FILTER' => 'Y',
'CACHE_TYPE' => 'A', // авто
'CACHE_TIME' => 3600, // 1 час
'CACHE_GROUPS' => 'N', // не разделять кеш по группам пользователей
'LAZY_LOAD' => 'Y', // ленивая загрузка счётчиков
]);
LAZY_LOAD = 'Y' — счётчики загружаются отдельным AJAX-запросом после отрисовки страницы. Страница отвечает быстро, счётчики появляются через 200–400 мс.
Кеш тегами с инвалидацией
Агрессивное кеширование с инвалидацией при изменении каталога:
// Тег кеша привязан к инфоблоку
$taggedCache = \Bitrix\Main\Data\TaggedCache::instance();
$taggedCache->startTagCache('/catalog/filter/');
// Выполняем работу фильтра
// ...
$taggedCache->registerTag('iblock_id_' . CATALOG_IBLOCK_ID);
$taggedCache->endTagCache();
// При изменении элементов инфоблока — автоинвалидация:
// AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', function($id, $fields) {
// \Bitrix\Main\Data\TaggedCache::instance()->clearByTag('iblock_id_' . $fields['IBLOCK_ID']);
// });
Пред-агрегация счётчиков
Для каталогов 100 000+ позиций — предварительная агрегация в отдельную таблицу через агент:
// Таблица для хранения предагрегированных данных
// CREATE TABLE b_custom_filter_counts (
// iblock_id INT, property_code VARCHAR(50),
// property_value VARCHAR(255), cnt INT,
// updated_at DATETIME,
// PRIMARY KEY (iblock_id, property_code, property_value(100))
// );
class FilterCountsAgent
{
public static function recalculate(): string
{
$iblockId = CATALOG_IBLOCK_ID;
$propertyCodes = ['BRAND', 'MATERIAL', 'COUNTRY'];
foreach ($propertyCodes as $code) {
// Подсчёт через прямой SQL
$sql = "
INSERT INTO b_custom_filter_counts
(iblock_id, property_code, property_value, cnt, updated_at)
SELECT
e.IBLOCK_ID,
p.CODE,
pv.VALUE,
COUNT(DISTINCT e.ID),
NOW()
FROM b_iblock_element e
INNER JOIN b_iblock_element_property ep ON ep.IBLOCK_ELEMENT_ID = e.ID
INNER JOIN b_iblock_property p ON p.ID = ep.IBLOCK_PROPERTY_ID
INNER JOIN b_iblock_property_enum pv ON pv.ID = ep.VALUE_ENUM
WHERE e.IBLOCK_ID = {$iblockId}
AND e.ACTIVE = 'Y'
AND p.CODE = '{$code}'
GROUP BY e.IBLOCK_ID, p.CODE, pv.VALUE
ON DUPLICATE KEY UPDATE cnt = VALUES(cnt), updated_at = VALUES(updated_at)
";
\Bitrix\Main\Application::getConnection()->query($sql);
}
return 'FilterCountsAgent::recalculate();';
}
}
Агент запускается каждые 15–30 минут. Счётчики в UI берутся из этой таблицы — не из live-запросов.
Уменьшение количества свойств в фильтре
Часто 30–40% свойств в фильтре никогда не используются. Анализ данных из систем аналитики:
- Подключить события GTM на взаимодействие с каждым блоком фильтра
- За 30 дней определить используемые vs неиспользуемые блоки
- Убрать из фильтра блоки с менее чем 2% взаимодействий
На практике после такого аудита каталог с 28 свойствами в фильтре оставляет 12–15. Время генерации страницы снижается на 35–60% без технических изменений.
Кейс: маркетплейс с 80 000 позиций
Маркетплейс стройматериалов. Фильтр отвечал 8–12 секунд на первый запрос, 2–3 с — с кешем. Индекс кеша сбрасывался при каждой смене цены поставщиком (десятки раз в день).
Решение в три этапа:
- Добавлены составные индексы на таблицы свойств — время 1,8 с без кеша
- Пред-агрегация счётчиков через агент каждые 20 минут — счётчики из таблицы за 5 мс
- Lazy load счётчиков — первый байт страницы через 180 мс, счётчики через 300 мс
Итого: с 8–12 с до 180 мс TTFB, счётчики за 300 мс.
Сроки выполнения
Аудит, добавление индексов и настройка кеширования — 1–2 рабочих дня. Полная оптимизация с пред-агрегацией, lazy load и аудитом используемых свойств — 3–5 рабочих дней.







