Разработка модуля фильтрации каталога 1С-Битрикс
Стандартный компонент catalog.section.list с компонентом фильтра catalog.section.list.filter работает из коробки, но имеет известные ограничения. При большом количестве свойств (b_iblock_element_prop_s*, b_iblock_element_prop_m*) запросы становятся медленными из-за множественных JOIN. Чекбоксы показывают все возможные значения без учёта того, сколько товаров за ними стоит — пользователь кликает на фильтр и получает пустой результат. Динамическая перестройка фильтра при каждом изменении убивает производительность на каталогах от 10 000 позиций.
Проблема N+1 и денормализация
Корень проблемы производительности стандартного фильтра — архитектура хранения свойств инфоблока. Значения хранятся в нескольких таблицах в зависимости от типа (b_iblock_element_prop_s* для строк, b_iblock_element_prop_m* для множественных), и для каждого свойства нужен отдельный JOIN или подзапрос.
Решение — денормализованный индекс фильтра. Модуль создаёт и поддерживает таблицу myvendor_filter_index, где для каждого товара хранится плоская структура значений всех фильтруемых свойств в JSONB:
CREATE TABLE myvendor_filter_index (
element_id INT PRIMARY KEY,
section_id INT NOT NULL,
price_min DECIMAL(12,2),
in_stock BOOLEAN,
props JSONB NOT NULL -- {"brand": "Samsung", "color": ["black", "white"]}
);
CREATE INDEX idx_filter_props ON myvendor_filter_index USING gin(props);
CREATE INDEX idx_filter_section ON myvendor_filter_index(section_id);
CREATE INDEX idx_filter_price ON myvendor_filter_index(price_min);
Индекс обновляется через событие OnAfterIBlockElementUpdate для конкретного товара и через агент для массового пересчёта.
Детально: умные чекбоксы с счётчиками
Это ключевая фича — показывать рядом с каждым значением фильтра количество товаров, которые за ним стоят с учётом уже выбранных фильтров. Такое поведение называется faceted search.
-- Подсчёт вариантов для фильтра "Бренд"
-- с учётом уже выбранного фильтра "Цвет: черный"
SELECT
props->>'brand' AS brand,
COUNT(*) AS cnt
FROM myvendor_filter_index
WHERE
section_id = :section_id
AND in_stock = true
AND props @> '{"color": "black"}'::jsonb
GROUP BY props->>'brand'
ORDER BY cnt DESC;
Этот запрос возвращает все бренды с количеством чёрных товаров в наличии. Для каждого свойства выполняется отдельный такой запрос — но это быстро, потому что GIN-индекс по JSONB делает поиск эффективным.
Кеширование фасетов. Результаты подсчётов кешируются с тегами по разделу и набору активных фильтров. При изменении любого товара в разделе тег сбрасывается. TTL кеша — 30 минут.
URL-схема фильтра
Фильтр строит «красивые» URL, дружелюбные к SEO:
-
/catalog/smartphones/brand-samsung/color-black/— постраничный список с фильтрами -
/catalog/smartphones/brand-samsung/— категориальный фильтр с собственным H1 и описанием
Ключевые страницы фильтра (бренд, популярная комбинация свойств) могут иметь уникальные мета-теги, задаваемые через административный интерфейс модуля. Остальные формируются автоматически по шаблону.
AJAX-обновление без перезагрузки
При изменении фильтра страница не перезагружается: AJAX-запрос уходит на /api/catalog/filter/, сервер возвращает JSON с ID отфильтрованных товаров и обновлёнными счётчиками фасетов, фронтенд обновляет список товаров и чекбоксы. История браузера обновляется через history.pushState.
Ранжирование результатов
Помимо фильтрации, модуль управляет сортировкой: по цене, популярности (количество заказов из b_sale_basket), новизне, рейтингу. «Популярность» пересчитывается агентом раз в сутки и хранится в myvendor_filter_index.popularity_score.
Сроки разработки
| Масштаб | Состав | Срок |
|---|---|---|
| Базовый | Денормализованный индекс + чекбоксы + диапазон цен | 3–4 недели |
| Средний | + умные счётчики (фасеты) + AJAX + URL-схема | 5–7 недель |
| Расширенный | + SEO-страницы фильтра + персонализация сортировки | 8–11 недель |
Количество свойств инфоблока и объём каталога — главные факторы выбора архитектуры. При 200+ свойствах JSONB-подход требует тщательного проектирования схемы индекса.







