Перейти к содержанию

Фасеты и область подсчёта групп

Точные фильтры помогают сузить поиск, но читателю полезно видеть, какие группы вообще доступны для текущей фразы. Добавим фасеты — названия разделов и меток с количеством подходящих документов. Результат урока — две terms-агрегации внутри того же поискового запроса, где уже действуют ограничения публикации и аудитории.

Используем итоговый query.py; изменяемых копий проекта по урокам в архиве нет. Здесь разбираем раздел aggs и возвращаемую структуру facets. Агрегации не запускались. Документация terms aggregation описывает buckets и ограничения распределённого подсчёта; конкретный смысл фасетов выбирает наше приложение.

Группа документов вместо списка карточек

Запрос «HTTP» может найти материалы нескольких рубрик. В карточках мы покажем только десять первых, а в фасетах хотим увидеть, сколько подходящих документов относится к backend и frontend. Поэтому фасеты нельзя подсчитать простым перебором текущей страницы: такой счётчик зависел бы от size и порядка выдачи.

Агрегация выполняется на стороне движка над множеством документов, соответствующих запросу. Она создаёт bucket для каждого выбранного значения поля. У bucket есть key и doc_count. Когда поле имеет тип keyword, ключ соответствует согласованному значению рубрики или метки. Для анализируемого текста такое использование потребовало бы других настроек и дало бы иной смысл группировки.

Следующий листинг — полный раздел aggs из build_query, который добавляется рядом с query, а не внутрь полнотекстового условия:

'aggs': {
    'sections': {'terms': {'field': 'section', 'size': 10}},
    'tags': {'terms': {'field': 'tags', 'size': 20}}
}

В sections ограничиваем число возвращаемых групп десятью, в tags — двадцатью. Это количество групп, а не документов в поисковой странице. Поле size самого запроса и одноимённый параметр terms имеют независимые назначения. Ошибка в этом различии может привести к интерфейсу, где число фасетов неожиданно меняется при переключении страницы.

Выбор области подсчёта

Наш договор прост: фасеты учитывают все условия текущего query, включая выбранный раздел и метку. Если читатель уже выбрал backend, фасет разделов покажет группы внутри backend. Это не универсальное решение для любого поискового интерфейса. Некоторые продукты хотят показывать альтернативные разделы так, будто выбранное ограничение этого фасета ещё не применено.

Для альтернативного поведения нужны дополнительные запросы, filter-агрегации или тщательно устроенный post_filter. У каждого решения есть область подсчёта. Нельзя перенести пользовательский фильтр за пределы query, а затем предполагать, что все счётчики сохранили прежний смысл. В этом уроке оставляем одну понятную область, чтобы сначала научиться объяснять возвращаемые числа.

Обязательные условия published и audience всегда входят в область фасетов. Даже если позже расширите интерфейс альтернативных рубрик, эти условия нельзя убрать при подсчёте. Фасет — часть выдачи данных. Наличие закрытой метки с ненулевым числом может раскрыть сведения не хуже заголовка служебного документа.

Различайте «в библиотеке всего десять материалов по HTTP» и «текущий публичный запрос нашёл десять материалов». Второе число зависит от анализа строки, фильтров и видимости. Фасеты рядом с поисковой строкой следует подписывать так, чтобы читатель понимал эту связь. Иначе изменение цифр при вводе нового слова будет выглядеть как исчезновение документов из самой библиотеки.

Несколько меток у одной статьи

Документ article-107 имеет метки http и cache. В агрегации tags он входит в обе группы. Поэтому сумма doc_count всех меток может превысить общее число совпадающих документов. Это ожидаемый результат многозначного поля, а не доказательство дублирования материалов в индексе.

В разделах договор иной: у каждого документа один section. Если агрегатор вернул все разделы для полного допустимого множества и счётчики точны, их сумма соответствует количеству документов с этим полем. Но реальные условия могут отличаться: поле отсутствует, возвращены только первые группы, присутствуют ограничения подсчёта. Поэтому не добавляем универсальную проверку равенства сумм без уточнения предпосылок.

Упрощённая ожидаемая форма ответа для будущего наблюдения:

{
  "sections": {"buckets": [{"key": "backend", "doc_count": 2}]},
  "tags": {"buckets": [
    {"key": "http", "doc_count": 2},
    {"key": "cache", "doc_count": 1}
  ]}
}

Числа в этом листинге иллюстрируют перекрывающиеся группы, а не зафиксированный результат приложенного корпуса. Полный OpenSearch-ответ содержит дополнительные сведения агрегации. При будущей проверке сопоставьте одну известную запись с двумя метками и убедитесь, что она может увеличить оба счётчика, не создавая второй _id.

Ограниченное число buckets

Terms возвращает наиболее подходящие группы в пределах заданного размера. Отсутствие метки в двадцати buckets не обязательно означает, что в допустимых документах её нет. У неё может быть слишком мало совпадений по сравнению с более частыми значениями. Интерфейс не должен объявлять возвращённый список полным справочником всех меток.

В распределённом индексе сбор популярных групп происходит на нескольких шардах. Отдельные поля ответа помогают оценить неполноту и возможную погрешность счётчиков. В нашем маленьком индексе один primary shard, что упрощает наблюдение, но не даёт основания перенести все ожидания на многосегментный рабочий кластер. При расширении проекта нужно повторно определить требования к точности фасетов.

Если нужна полная перечислимая выгрузка значений, terms со случайно огромным size не обязательно лучший инструмент. Существуют другие подходы, включая отдельный справочник и composite-агрегации с порционной выдачей. Они решают другую задачу, чем компактные популярные фасеты рядом с поиском. Здесь не расширяем публичный запрос до выгрузки произвольной аналитики.

Стоимость фасетов также зависит от количества разных значений и объёма совпадений. Поле с уникальным значением для каждого документа создаёт почти столько групп, сколько записей. Использовать doc_id как пользовательский фасет обычно бессмысленно: оно не объединяет материалы по полезному признаку. Выбранные section и tags имеют понятную читателю группирующую роль.

Названия buckets приходят из документов, поэтому интерфейс должен отображать их как текст и проверять пригодность значения перед отправкой нового фильтра. Счётчик не делает ключ доверенной командой. Если метка содержит неожиданную строку, полезнее исправить договор корпуса, чем вставлять её как HTML или разрешать произвольное имя поля. Такая граница связывает фасет с уже знакомым публичным API, а не превращает его в отдельный неограниченный канал запросов.

После урока ответ приложения несёт карточки и группы из одного допустимого множества. Для упражнения добавьте метку cache второй статье, не меняя её раздел. Предскажите, какие два счётчика изменятся и почему общее число документов может остаться прежним. В следующем уроке покажем совпавшие фрагменты, чтобы карточка объясняла связь документа с поисковой фразой.

Оглавление серии