Настройка Elasticsearch для фасетного поиска с агрегациями

Настройка Elasticsearch для фасетного поиска с агрегациями

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Настройка Elasticsearch для фасетного поиска с агрегациями
Сложный
~3-5 дней

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    983
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1245
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    998

Настройка Elasticsearch для фасетного поиска с агрегациями

Представьте каталог с миллионом товаров. Без фасетного поиска пользователь листает 200 страниц. С ним — за секунду находит нужный бренд и ценовой диапазон. Но счётчики врут, если не настроить post_filter и global aggregations. В этой статье разберём, как собрать корректные фасетные счётчики на Elasticsearch, избежав типовых ошибок. Экономия времени разработки при использовании готового решения — до 30%.

Мы интегрируем Elasticsearch с фасетным поиском в каталогах и интернет-магазинах. Это фильтры-счётчики: «Бренд: Samsung (143), Apple (89)», «Цена: до 30 000 (234), 30–60 000 (145)». При выборе фильтра список товаров сужается, а счётчики других значений пересчитываются. В реляционных базах такие операции дороги — GROUP BY с COUNT(*) по отфильтрованной выборке. В Elasticsearch задача решается агрегациями часто за единственный запрос. Оценим ваш проект и реализуем под ключ за 2–4 рабочих дня.

Как реализовать корректные счётчики фасетов?

Типичный запрос для каталога: поисковый запрос + активные фильтры + агрегации для подсчёта фасетов. Пример базового запроса:

POST /products/_search { "query": { "bool": { "must": [ { "match": { "title": "ноутбук" } } ], "filter": [ { "term": { "is_active": true } }, { "range": { "price": { "gte": 20000, "lte": 80000 } } } ] } }, "aggs": { "by_brand": { "terms": { "field": "brand", "size": 20, "order": { "_count": "desc" } } }, "price_ranges": { "range": { "field": "price", "ranges": [ { "key": "до 30 000", "to": 30000 }, { "key": "30–60 000", "from": 30000, "to": 60000 }, { "key": "60–100 000", "from": 60000, "to": 100000 }, { "key": "от 100 000", "from": 100000 } ] } }, "price_stats": { "stats": { "field": "price" } }, "by_category": { "terms": { "field": "category", "size": 10 } }, "by_rating": { "histogram": { "field": "rating", "interval": 1, "min_doc_count": 1 } } }, "size": 20, "from": 0 } 

Проблема исчезающих счётчиков при выборе фасета

Если пользователь выбирает бренд «Samsung» и добавляет его в filter, агрегация by_brand считает только внутри отфильтрованной выборки. Счётчики других брендов обнуляются — пользователь не видит, сколько товаров у других брендов. Решение — Post Filter в сочетании с Global Aggregation:

POST /products/_search { "query": { "bool": { "must": [ { "match": { "title": "ноутбук" } } ], "filter": [ { "term": { "is_active": true } } ] } }, "post_filter": { "bool": { "filter": [ { "term": { "brand": "Samsung" } } ] } }, "aggs": { "all_brands": { "global": {}, "aggs": { "filtered_brands": { "filter": { "bool": { "must": [ { "match": { "title": "ноутбук" } } ], "filter": [ { "term": { "is_active": true } } ] } }, "aggs": { "brands": { "terms": { "field": "brand", "size": 20 } } } } } }, "price_ranges": { "range": { "field": "price", "ranges": [ { "key": "до 30 000", "to": 30000 }, { "key": "30–60 000", "from": 30000, "to": 60000 }, { "key": "от 60 000", "from": 60000 } ] } } }, "size": 20 } 

Согласно документации Elasticsearch, post_filter применяется к результатам после агрегации, а global агрегация выходит за пределы контекста query. post_filter применяется к результатам после агрегации. Агрегации считаются по всей выборке (до post_filter), поэтому счётчики брендов остаются корректными. global агрегация выходит за пределы контекста query — позволяет считать агрегаты по всем документам индекса с дополнительными фильтрами.

Как работать с вложенными атрибутами?

Если атрибуты (размер, цвет, материал) хранятся как nested объекты:

"mappings": { "properties": { "attributes": { "type": "nested", "properties": { "name": { "type": "keyword" }, "value": { "type": "keyword" } } } } } 

Агрегация по вложенным атрибутам:

"aggs": { "attributes": { "nested": { "path": "attributes" }, "aggs": { "attribute_names": { "terms": { "field": "attributes.name", "size": 50 }, "aggs": { "attribute_values": { "terms": { "field": "attributes.value", "size": 20 } } } } } } } 

Это двухуровневые вложенные агрегации: сначала группируем по имени атрибута («Цвет», «Размер»), внутри каждой группы — значения («Чёрный», «Белый»).

Cardinality — подсчёт уникальных значений

Для отображения «Найдено 1 247 товаров от 89 брендов» используйте агрегацию cardinality с полем brand и precision_threshold 100. Это гарантирует точность до 100 уникальных значений, выше — погрешность 1–6%.

Настройка фасетного поиска за 5 шагов

  1. Определите поля для фильтрации: бренд, цена, категория, рейтинг, атрибуты. Типичный набор — 5–10 полей.
  2. Настройте маппинг: для ключевых полей используйте тип keyword, для вложенных — nested. Убедитесь, что поля для агрегаций не анализируются.
  3. Постройте запрос с агрегациями: включите все нужные агрегации (terms, range, histogram, nested). Для корректных счётчиков используйте post_filter и global агрегации.
  4. Примените post_filter: отделите фильтры от основного query. Это сохранит счётчики агрегаций.
  5. Оптимизируйте производительность: используйте execution_hint: map для полей с низкой кардинальностью, кэширование для агрегаций без поискового запроса, ограничьте size до 20.

Оптимизация агрегаций

terms агрегация с большим size — дорогая операция: каждый шард возвращает top-N значений, координирующий узел мержит результаты. Для высококардинальных полей (тысячи уникальных значений) производительность падает. Способы оптимизации:

Таблица методов оптимизации
Метод Описание Когда применять
execution_hint: map Для полей с низкой кардинальностью (<1000) работает быстрее ordinals Поля типа статус, категория
Кэширование агрегаций Агрегации без поискового запроса кэшируются в shard request cache Страницы каталога без поиска
Ограничение size Для фасетов достаточно top-20 значений; глубокая пагинация не нужна Всегда

Сравнение типов агрегаций

Тип Пример использования Производительность
Terms Группировка по бренду Быстро при кардинальности <10k
Range Ценовые диапазоны Очень быстро
Histogram Рейтинг с шагом 1 Быстро
Nested Вложенные атрибуты Средне (зависит от кол-ва объектов)
Cardinality Уникальные бренды Быстро (алгоритм HyperLogLog)

Реализация на стороне приложения

PHP класс для построения фасетных запросов:

class FacetedSearchService { public function search(array $params): array { $query = $this->buildQuery($params); $response = $this->client->search([ 'index' => 'products', 'body' => $query, ]); return [ 'hits' => $response['hits']['hits'], 'total' => $response['hits']['total']['value'], 'facets' => $this->extractFacets($response['aggregations']), ]; } private function extractFacets(array $aggs): array { $facets = []; if (isset($aggs['by_brand'])) { $facets['brand'] = array_map(fn($b) => [ 'value' => $b['key'], 'count' => $b['doc_count'], ], $aggs['by_brand']['buckets']); } if (isset($aggs['price_ranges'])) { $facets['price'] = array_map(fn($r) => [ 'label' => $r['key'], 'count' => $r['doc_count'], ], $aggs['price_ranges']['buckets']); } return $facets; } } 

Что входит в настройку фасетного поиска под ключ

  • Проектирование схемы маппингов и индексов.
  • Настройка агрегаций всех типов (terms, range, nested, cardinality).
  • Реализация логики post_filter + global aggregations для корректных счётчиков.
  • Оптимизация производительности (execution_hint, кэширование).
  • Написание серверного кода для построения запросов и обработки ответов.
  • Документация, обучение команды, поддержка после внедрения.

Сроки

  • Базовая реализация (3–5 типов агрегаций) — 2 рабочих дня.
  • Сложный сценарий (post_filter, global, nested) — 3–4 дня.
  • Оптимизация на реальных объёмах (>5 млн документов) — дополнительный день.

Наша команда имеет 6+ лет опыта в Elasticsearch и сертифицированных инженеров. Мы реализовали более 50 проектов с фасетным поиском, гарантируя точность счётчиков и производительность. Свяжитесь с нами для консультации — поможем избежать дорогих ошибок. Стоимость рассчитывается индивидуально, а экономия от внедрения может достигать 40 000 ₽ на этапе разработки и до 20 000 ₽ в месяц на инфраструктуре. Получите готовое решение с полной документацией и поддержкой.