Каталог на двести тысяч позиций, умный фильтр из двадцати свойств. Страница раздела отдаётся восемь секунд, причём выборка самих товаров занимает из них меньше десятой доли.
Включили фасетный индекс — стало триста миллисекунд. Победа, которую портит одна деталь: обмен с 1С идёт каждые пятнадцать минут, после каждого индекс частично устаревает, а полный пересчёт занимает сорок минут. Каталог теперь быстрый, но значительную часть суток показывает в фильтре не то, что есть на складе.
Фасетный индекс — хороший инструмент с высокой ценой владения. Разберём, за что именно он платит, и в каких случаях эта сделка невыгодна.
Почему тормозит умный фильтр
Первое заблуждение, которое стоит снять: фильтр тормозит не на выборке товаров.
Выбрать сто товаров из двухсот тысяч по нескольким условиям — задача, с которой база справляется быстро при нормальных индексах. Дорого стоит другое — счётчики.
Чтобы нарисовать фильтр в привычном виде:
Цвет
☐ Синий (42)
☐ Красный (17)
☐ Чёрный (0) ← недоступен при текущих условияхнужно для каждого значения каждого свойства ответить на вопрос: сколько товаров останется, если добавить это условие к уже выбранным. Двадцать свойств по десять значений — двести таких вопросов на одну загрузку страницы.
А теперь вспомним, как свойства хранятся в инфоблоке. Схема EAV, по строке на каждое значение каждого элемента, — разница между этой моделью и плоской таблицей разобрана в статье про выбор между инфоблоками и Highload-блоками. Каждый счётчик — это агрегирующий запрос с соединением по таблице, где на двести тысяч элементов приходятся миллионы строк.
Отсюда неочевидное следствие: на скорость фильтра число свойств влияет сильнее, чем число товаров. Каталог на пять тысяч позиций с сорока свойствами в фильтре тормозит заметнее, чем каталог на пятьдесят тысяч с пятью.
Что такое фасетный индекс
Фасетный индекс — это денормализованная структура, в которой заранее посчитано соответствие «раздел + свойство + значение → элементы». Вместо того чтобы вычислять счётчики соединениями по EAV на каждый запрос, Битрикс читает готовые данные.
Классический размен: место и стоимость поддержания против скорости чтения.
| Без индекса | С индексом | |
|---|---|---|
| Счётчики фильтра | Агрегирующие запросы по EAV | Чтение готовой структуры |
| Стоимость запроса | Растёт с числом свойств | Почти не растёт |
| Место в базе | — | Дополнительная структура |
| Изменение товара | Бесплатно | Требует обновления индекса |
| Массовый импорт | Бесплатно | Требует пересчёта |
Последние две строки и есть цена, о которой обычно не думают при включении.
Сколько он занимает и как это посмотреть
Имя таблицы индекса между версиями Битрикса отличается, поэтому надёжнее искать по шаблону, а не по точному названию:
SELECT
TABLE_NAME,
TABLE_ROWS,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024) AS total_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME LIKE '%facet%'
ORDER BY total_mb DESC;Для сравнения — сколько занимает само хранилище свойств:
SELECT
TABLE_NAME,
TABLE_ROWS,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024) AS total_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME LIKE 'b_iblock_element_prop%'
ORDER BY total_mb DESC;Индекс должен помещаться в буферный пул вместе с остальным рабочим набором. Смысл фасета в том, что чтение готовой структуры дешевле вычисления. Если структура не помещается в память и база читает её с диска, значительная часть выигрыша исчезает. На проектах, где память сервера подбиралась под каталог без фасета, включение индекса иногда даёт куда меньший эффект, чем ожидали, — и причина именно в этом.
Когда индекс не нужен
Три ситуации, в которых включение не окупается.
Маленький каталог. Ориентировочно до пяти тысяч товаров с умеренным числом свойств выигрыш измеряется десятками миллисекунд. Их не видно на фоне сетевой задержки и рендеринга, а пересчёт вы получите в полном объёме.
Фильтром почти не пользуются. Прежде чем включать, посмотрите в метрике долю сессий, где фильтр реально применяли. На каталогах, где покупатели ходят по разделам и поиску, эта доля бывает единицами процентов, и оптимизировать имеет смысл совсем другое.
Свойств в фильтре мало. Три-четыре свойства не создают той нагрузки, ради снятия которой строится индекс.
Когда индекс мешает
Здесь интереснее, потому что вред неочевиден и проявляется не сразу.
Частый обмен
Главный конфликт. Массовое изменение товаров делает часть индекса неактуальной, и до пересчёта фильтр отдаёт устаревшие счётчики.
Простая арифметика: обмен каждые пятнадцать минут, пересчёт сорок минут. Индекс не успевает достроиться до следующего обмена — то есть каталог постоянно находится в неактуальном состоянии, а сервер постоянно занят пересчётом. Формально всё включено и работает, фактически вы получили и медленный сервер, и неверный фильтр.
Выходов три, и выбирают между ними по ситуации:
- развести обмен и пересчёт по времени: тяжёлый импорт ночью, пересчёт следом, лёгкие обновления цен днём без затрагивания свойств фильтра;
- пересчитывать не весь индекс, а затронутые разделы;
- отказаться от индекса, если расхождение фильтра со складом дороже, чем задержка страницы.
Пересчёт, запущенный агентом, может не выполниться вовсе. Агенты в режиме «на хите» ночью не работают — механика разобрана в статье про расписание обмена и агенты. Ночной обмен обновил товары, пересчёт поставлен агентом, посетителей нет — и до утра каталог живёт с устаревшим индексом. Симптом со стороны бизнеса: «фильтр показывает товары, которых нет», причём только по утрам.
Пересчёт внутри обмена
Вторая ошибка — обновлять индекс по ходу импорта, на каждом элементе. Это ровно тот множитель, который превращает десятиминутный обмен в многочасовой; общий принцип разобран в статье про производительность импорта. Пересчёт — операция для завершения обмена, один раз.
Частая смена структуры свойств
Индекс строится под конкретный набор свойств. Проект, где свойства регулярно добавляют, удаляют и переименовывают, будет столь же регулярно перестраивать индекс целиком.
Как честно измерить эффект
Синтетические тесты здесь бесполезны: выигрыш зависит от числа свойств, их заполненности и распределения значений. Мерить нужно на копии с боевыми данными.
# Время ответа страницы раздела с применённым фильтром, 10 замеров
for i in $(seq 1 10); do
curl -s -o /dev/null \
-w "%{time_starttransfer}\n" \
"https://staging.site.ru/catalog/smesiteli/filter/color-is-blue/apply/"
done | sort -n | awk '{a[NR]=$1} END {print "медиана:", a[int(NR/2)+1]}'Замер делается дважды: с выключенным индексом и с включённым, после полного пересчёта. Сравнивать нужно медиану, а не среднее — единичный выброс из-за фонового процесса исказит среднее и не тронет медиану.
Отключите композит и кэш компонентов на время замера. Иначе вы измерите скорость отдачи закэшированной страницы, которая одинакова в обоих случаях. Как устроен композит и что он подменяет, разобрано в отдельной статье, а сроки жизни кэша каталога — в разборе политики кэширования.
Дополнительно стоит замерить время полного пересчёта — это то, что вы обязуетесь платить после каждого массового изменения:
# Пересчёт запускается из админки; засекаем и записываем результат.
# Это число понадобится, чтобы спланировать окно между обменами
date +%s > /tmp/facet_startЧто проверить перед включением
Пять пунктов, каждый из которых способен отменить решение:
- Сколько товаров и свойств в фильтре. Мало и того и другого — не включать.
- Как часто идёт обмен. Чаще, чем длится пересчёт, — конфликт, решать до включения.
- Работают ли агенты по cron. Нет — пересчёт ночью не запустится.
- Хватает ли памяти базе с учётом новой структуры.
- Пользуются ли фильтром вообще. Проверяется в метрике за минуту.
Что делать, если индекс не помог
Фильтр остался медленным после включения — значит, узкое место не там, где предполагали. Проверять в таком порядке:
Индекс реально построен. Включённая настройка и построенный индекс — разные вещи. Смотрите, есть ли в таблице строки.
Свойства подходят для фильтрации. Строковые свойства со свободным вводом дают тысячи уникальных значений вместо десятка. Фильтр по такому свойству будет медленным при любом индексе, а список значений — бессмысленным для покупателя.
Дело не в самом фильтре. Медленной может быть выборка товаров, вывод изображений или сторонний код в шаблоне. Порядок разбора для смежных проблем умного фильтра описан в статьях про 404 в умном фильтре и валидацию его URL.
Проблема в сортировке, а не в фильтрации. Сортировка по свойству — отдельная задача со своей стоимостью, разобранная в статье про фильтрацию и сортировку по свойству.
Итоги
Фильтр тормозит на счётчиках, а не на выборке. Понимание этого меняет направление оптимизации: смотреть надо на число свойств, а не на число товаров.
Фасетный индекс — размен места и пересчёта на скорость чтения. Как всякий размен, он выгоден не всегда, и невыгодным его делают не размеры каталога, а частота изменений.
Обмен чаще, чем длится пересчёт, — противопоказание. Вы получите постоянно занятый сервер и постоянно неактуальный фильтр одновременно.
Пересчёт агентом без cron ночью не выполнится. Проверьте это раньше, чем включите индекс.
Измеряйте на боевых данных с отключённым кэшем. И записывайте время пересчёта — это ваш будущий бюджет обслуживания.
И наблюдение, ради которого стоит открыть метрику перед тем, как трогать настройки. На заметной доле проектов, где просят «ускорить умный фильтр», фильтром пользуются считанные проценты посетителей — а тормозит у остальных не он, а тяжёлая витрина раздела. Фасетный индекс в такой ситуации честно ускорит то, чем почти никто не пользуется, добавит пересчёт после каждого обмена и оставит основную проблему нетронутой.
Частые вопросы
Что такое фасетный индекс в Битриксе?
Это вспомогательная денормализованная структура, в которой заранее посчитано, какие элементы каких разделов имеют какие значения свойств. Умный фильтр вместо соединений с EAV-таблицей читает готовые данные. Индекс не хранит товары и не заменяет каталог — он только отвечает на вопрос, какие значения фильтра доступны и сколько за каждым позиций.
Почему умный фильтр тормозит даже на небольшом каталоге?
Потому что основная стоимость не в выборке товаров, а в счётчиках. Чтобы показать «Синий (42)», нужно для каждого значения каждого свойства посчитать, сколько товаров останется при текущем наборе условий. На 20 свойствах это десятки агрегирующих запросов по EAV-таблице на одну загрузку страницы, и объём каталога здесь вторичен по отношению к числу свойств в фильтре.
Когда фасетный индекс не нужен?
На каталогах примерно до 5 000 товаров с небольшим числом свойств в фильтре — выигрыш там измеряется десятками миллисекунд и не оправдывает пересчёта. Также он избыточен, если умным фильтром почти не пользуются: посмотрите долю сессий с применённым фильтром в метрике, прежде чем включать.
Почему после обмена с 1С фильтр показывает неверные значения?
Потому что индекс устарел, а пересчёт ещё не прошёл. Массовое изменение товаров делает часть индекса неактуальной, и до пересчёта фильтр отдаёт данные на момент последнего построения. При обмене раз в 15 минут и долгом пересчёте каталог рискует находиться в неактуальном состоянии большую часть времени.
Как проверить, помогает ли фасетный индекс на моём проекте?
Замерить время ответа страницы раздела с применённым фильтром до и после включения, на копии с боевыми данными. Синтетические каталоги для этого не годятся: эффект зависит от числа свойств, их заполненности и распределения значений. Если разница в пределах сотни миллисекунд, а обмен идёт часто, индекс скорее навредит.