Elasticsearch — Урок 6.3 Поиск

Одной из самых мощных функций Elasticsearch является DSL (Domain specific Language) или язык запросов. Он очень выразителен и может использоваться для определения фильтров, запросов, сортировки, разбивки на страницы и агрегирования в одном запросе. Чтобы выполнить поисковый запрос, используется HTTP-запрос к _search Api. Индекс и тип, по которому должен выполняться запрос, указывается в URL-адресе. Индекс и тип являются необязательными. Если индекс / тип не указан, Elasticsearch выполняет запрос по всем индексам в кластере. Поисковый запрос в Elasticsearch может быть выполнен двумя разными способами:

  • Передавая запрос поиска в качестве параметров запроса.
  • Передавая запрос поиска в теле запроса.

Простой поисковый запрос с использованием параметров запроса:

Не забудьте декодировать кириллицу.

Простые запросы могут быть выполнены с использованием параметров URL-адреса. Все, кроме простого запроса, как в приведенном выше примере, должно быть передано как тело запроса. Предыдущий запрос, переданный как тело запроса, выглядит следующим образом.

Предыдущий запрос выполняется по example6 индексу и типу product. Запрос также может выполняться одновременно по нескольким индексами/типами, как показано здесь:

Обратите внимание, что в двух предыдущих случаях мы использовали HTTP POST, так как большинство браузеров не поддерживает передачу тела при GET запросе.

Здесь показана базовая структура тела запроса:

Мы обсудим каждый раздел запроса, например, разбивку на страницы, сортировку и т. д. чуть позже. Пока посмотрим на структуру ответа:

Основной запрос (поиск точного значения)

Основной запрос в Elasticsearch — это term (термин) запрос. Он очень простой и может использоваться для запроса чисел, бинарных значений, дат и текста. Данный запрос используется для поиска одного термина в инвертированном индексе. Запрос соответствия, с другой стороны, заботится о отображении и вызывает термин запрос внутри.

Запрос выглядит следующим образом:

Обратите внимание, что используем основу слова без окончания куртк, а не куртка. Потому что в индексе храниться, как раз слово без окончания и данные запрос будет искать точное соответствие.

Термин-запрос отлично подходит для одного термина. Чтобы запросить более одного термина, мы должны использовать запрос терминов. Если документ соответствует любому из условий, он попадает в результат. Например, мы хотим найти все документы, которые содержат куртк или кожан в имени продукта. Запрос будет выглядеть следующим образом:

Ответ на запрос следующий:

Пагинация

В предыдущем разделе мы узнали об основном запросе в Elasticsearch. В этом разделе мы обсудим, как ограничить количество результатов в ответе. Пагинация поддерживается с помощью полей from и size полей в запросе. Размер страницы по умолчанию 10. Например:

В предыдущем запросе мы получаем лучшие 2 документа. Если размер страницы 10, чтобы получить третью страницу, надо from установить 20 и size 10. Разбивка страниц в распределенной системе очень дорога по сравнению с традиционной базой данных. Данные, принадлежащие индексу, распространяются по нескольким осколкам. Например, если индекс состоит из пяти осколков, чтобы получить третью страницу, все пять осколков отправляют тридцать результатов в координационный узел. Координационный узел, в свою очередь, сортирует результаты всех осколков и передает результаты от 20 до 30. Чем больше номер страницы, тем дороже запрос.

Сортировка на основе существующих полей

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

Так же можно сортировать по нескольким полям сразу. Напирмер:

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

Выбор полей в ответе

Когда вы выполняете запрос, по умолчанию _source в ответ возвращается исходный документ JSON. Иногда мы хотим включать только определенные поля в ответ, особенно когда документ большой или имеет много полей.

Это можно сделать, указав поля в _source во время запроса. Значения полей извлекаются из документа _source. Если источник отключен с помощью сопоставления, это не сработает. Ответ следующего запроса будет иметь только product_name в ответе:

Ответ на предыдущий запрос выглядит следующим образом:

_source Поле также поддерживает подстановочные знаки для имен полей. Мы можем сказать Elasticsearch включить поля, которые начинаются с pr, как показано здесь:

Например, наряду с ценой продукта мы хотим вернуть цену, включая налог. Если налог с продаж 10%, мы можем использовать поле сценария, чтобы рассчитать цену с налогом и вернуть значение поля с результатом. Запрос выглядит следующим образом:

Язык сценариев по умолчанию для Elasticsearch называется Painless. Используя язык Painless, мы можем получить доступ к _source документу для извлечения unit_price и умножения на 1.1 чтобы рассчитать цену, включая налог. Ответ на предыдущий запрос выглядит следующим образом:

Ответ включает первоначальную цену и цену, включая налог. Хотя сценарии очень эффективны, каждый документ должен анализироваться для извлечения значения поля, которое может иметь стоимость исполнения.

Запрос на основе диапазона

Запрос диапазона может использоваться для чисел и дат для запроса всех документов в заданном диапазоне.

Запрос диапазона поддерживает следующие операторы:

  • lt: < меньше, чем оператор
  • gt: > больше оператора
  • lte: <= меньше или равно оператору
  • gte: >= больше или равно оператору

SQL запрос всех продуктов больше 70 и меньше, чем 100:

Предыдущий запрос в Elasticsearch:

Аналогично, запрос диапазона может также использоваться в датах, как показано ниже:

Работа с датами

Elasticsearch упрощает работу с датами. Он поддерживает часовые пояса, добавляет и удаляет часы / дни / месяцы / годы на определенную дату. Он также поддерживает, now как показано в следующем примере, который возвращает текущую метку времени. Например, чтобы найти все документы, которые были изменены в течение последнего часа, запрос показан здесь:

Строку даты или now следующую за ней математику можно также указать следующим образом. || символ отделяет математическую операцию и дату:

Ниже приведены примеры различных операций с датами:

  • now+1M: Добавляет один месяц. Вы также можете использовать другую единицу даты, такую ​​как час, день, месяц, год и т. д.
  • 2017-02-01||-1M: Вычитает один месяц
  • now/d: Используется для округления до ближайшего дня
  • now+1M-1d/d:  Добавляет один месяц, вычитает один день и округляется до ближайшего дня
  • 2017-02-01||+1H/d:  Добавляет один час к 2017-02-01 и округляет до ближайшего дня

Поддерживаются следующие единицы времени:

Единица времени Описание
y год
M месяц
w неделя
d день
H час
m минута
s секунда

Проанализированы или не проанализированы поля

Для хранения текстовых данных, Elasticsearch поддерживает два типа:

  • Text: Текстовые поля анализируются (разбиваются на отдельные термины) до их хранения в инвертированном индексе
  • Keyword: Поля сохраняются, как есть в инвертированном индексе

Чтобы лучше объяснить типы сопоставления Text и Keyword, давай их оба и попробуем:

Давайте проиндексируем документ:

Поскольку product_name  имеет текстовое отображение, оно будет проанализировано до того, как оно будет сохранено в инвертированном индексе. Из предыдущего примера, «Сочное красное яблоко» анализируется с помощью анализатора, он разбивается на сочн, красн, яблок токены.

Можно это проверить используя Analyse API:

Вы увидите ответ, подобный следующему:

Однако в product_name.keyword будет точное значение (Сочное красное яблоко), поскольку тип отображения является keyword. Давайте рассмотрим один и тот же запрос по product_name и product_name.keyword, чтобы увидеть, разницу:

В предыдущем запросе мы используем термин запрос он будет искать в инвертированном индексе product_name и возвращать документы, содержащие термин яблок. В ответе будет указан документ с названием продукта «Сочное красное яблоко».

Давайте запустим тот же запрос для product_name.keyword, который не анализируется:

Ответ будет пустой, так как в инвертированном индексе поле product_name.keyword не содержит записи яблок. Поскольку product_name.keyword не анализируется, инвертированный индекс будет иметь «Сочное красное яблоко». Повторим запрос:

Вы увидите ответ, подобный следующему:

Ответ будет содержать только один документ, так как это точное совпадение. Аналогичным образом, если вы запрашиваете яблоко для product_name.keyword, вы не получите никаких результатов, поскольку в инвертированном индексе product_name.keyword содержит только «Сочное красное яблоко» целиком.

Запрос match

Термин-запрос является наиболее часто используемым запросом и может использоваться для запроса чисел, булевых, дат и текста. Запрос ищет точный поиск в инвертированном индексе. Он является низкоуровневым запросом. Вы можете использовать термин в анализируемых или неанализированных полях. Он просто ищет термин в инвертированном индексе.

Запрос соответствия, в отличие от термина, понимает сопоставление полей. Входной запрос разбивается с помощью анализатора. Возьмем пример и обсудим, как работает запрос соответствия:

Этот запрос не найдет ни одного документа, поскольку запрос терминов ищет полностью «Куртка женская» в инвертированном индексе и не находит совпадений. Теперь, если мы запустим этот же запрос, используя match вместо слова term, как показано ниже, вы увидите три результата:

При использовании запроса соответствия происходит следующее:

  • Сначала запрос соответствия проверяет product_name сопоставление полей и узнает, что это анализируемое поле
  • Он использует тот же анализатор, который используется при индексировании (если во время сопоставления анализатор не задан, стандартный анализатор по умолчанию), чтобы разбить термин запроса на токены
  • Анализируя «Куртка женская» термин запроса, он будет преобразован в куртк и женск  токены (термины)
  • Затем он запускает фильтр терминов для каждого токена и объединяет результаты. Если запрос соответствия выполняется в непроанализированном поле, он будет запускать фильтр терминов без анализа запроса

Ответ на запрос соответствия выглядит следующим образом:

Вы можете видеть из ответа, что наряду с женской шерстяной курткой два других документа также соответствовали запросу. Это потому, что по умолчанию запрос соответствия преобразуется в запрос как указанно ниже:

Если вы хотите только документы, содержащие все условия, вы можете изменить запрос следующим образом:

Ответ на предыдущий запрос выглядит следующим образом:

Запрос соответствия фразе

Этот запрос аналогичен запросу match, но используется для запроса текстовых фраз. Согласование фразы необходимо, когда порядок слов важен. Согласованы только документы, содержащие слова в том же порядке, что и в поиске.

Во время процесса индексирования наряду с токеном положение слова в тексте также сохраняется в инвертированном индексе. Затем информация о местоположении используется в запросе соответствия фразе. Например:

Предыдущий запрос не будет соответствовать ни одному продукту. Продукт в котором встречаются данные слова содержит в описании «Лучший выбор. Всесезонная кожаная куртка», как вы заметили слова «Всесезонная» и «куртка» разделяет слово «кожаная». То есть для поиска используется достаточно жесткий принцип, слова должны следовать за друг другом. Но можно уменьшить жесткость задав параметр slop, который задает сколько слов может вклиниваться между словами в поисковой фразе. Предыдущий запрос со значением slop 1 выглядит следующим образом:

Ответ на предыдущий запрос выглядит следующим образом:

Префикс и префикс запроса соответствия фразе

Префиксный запрос используется для поиска всех документов с заданным префиксом. Подобно запросу термина, префиксный запрос является низкоуровневым запросом и не учитывает сопоставление полей. Условия в инвертированном индексе сортируются в алфавитном порядке, а префиксный запрос сканирует словарь терминов, чтобы найти соответствующие термины. Например, если мы хотим найти все продукты, которые начинаются с префикса ку, запрос выглядит следующим образом:

В префиксном запросе соответствия фразе сочетаются запросы соответствие фразе и префиксный. Согласование префикса выполняется только на последнем члене запроса. Количество префиксов, которые собираются из инвертированного индекса, можно контролировать с помощью max_expansions параметра, по умолчанию 50. Запрос префикса соответствия фразе выглядит следующим образом:

Подстановочный знак и регулярные выражения

Подстановочный запрос используется для сопоставления подстановочных знаков на входе поиска. Подобно фразовому запросу, термин запроса не анализируется. Поддерживаются следующие подстановочные знаки:

Знак Описание
* Соответствует любому количеству символов
? Соответствует одному символу

Пример запроса выглядит следующим образом для токена куртк (помните что запрос низкоуровневый работает с токенами):

Запрос Regex используется для поиска документов на основе соответствия регулярному выражению. Запрос регулярного выражения может быть очень дорогим в зависимости от префикса. Пример запроса выглядит следующим образом:

Существующие и отсутствующие запросы

Существующий запрос используется для поиска документа, который содержит ненулевое значение для поля, указанного в запросе. Например, если вы хотите найти весь документ, содержащий обзоры продуктов. Запрос выглядит следующим образом:

Если вы хотите найти все документы, которые не содержат reviews, мы можем использовать must_not. Запрос выглядит следующим образом:

Использование нескольких запросов

В мире реляционных баз данных мы можем использовать and/or  для объединения разных условий. В Elasticsearch мы будем использовать bool для достижения того же. Bool очень мощный; может иметь под запросы bool. Базовая структура запроса bool показана здесь:

must часть запроса содержит все запросы andmust_not раздел содержит все not запросы. should раздел содержит or запросы. filter содержит запросы на соответствие, но без учета ранжирования. Например, SQL запрос для поиска куртки по цене менее 100$:

Примерно тоже в Elasticsearch:

Поскольку условия завернуты в must, возвращаются только документы, которые удовлетворяют обоим условиям. Запрос bool также поддерживает вложенность других запросов bool, например, пользователь ищет водостойкую куртку, или кожаную стойкостью меньше 100. SQL-запрос:

В предыдущем SQL-запросе or условие становится should, и and условие становится must. Elasticsearch:

Ответ:

Ранжирование — это дорогостоящая операция, и если подсчет не является обязательным, отдельные запросы могут быть обернуты в filter, или весь запрос bool также может быть обернут constant_score. Мы подробно обсудим ранжирование в следующих разделах. А пока пример constant_score:

Маршрутизация

Ранее в 3 уроке мы обсуждали, как настроить отображение использовать маршрутизацию и как индексировать документ , используя значение маршрутизации. В этом разделе мы обсудим использование значений маршрутизации при запуске поискового запроса.

Маршрутизация может быть очень удобной, если у вас есть способ группировать ваши данные. Например, вы можете проиндексировать все документы, принадлежащие одному и тому же пользователю, используя user_id как значение маршрутизации. Все документы, индексированные с использованием одного user_id, попадают в один и тот же осколок. При выполнении запроса, если вы укажете значение маршрутизации, Elasticsearch выполнит запрос поиска только по одному осколку. Это особенно имеет смысл, если в индексе много осколков. Вместо выполнения запроса на каждом осколке индекса запрос на поиск может выполняться только на одном осколке.

Потенциальные сбои в использовании маршрутизации — это горячие точки. Если некоторые пользователи в системе намного больше других, есть вероятность, что все крупные пользователи могут оказаться в одном и том же осколке. Это приводит к неравномерному распределению данных и недоиспользованию ресурсов и потенциально может привести к разрушению всего кластера.

Значение маршрутизации может быть передано как часть запроса, как показано ниже:

Маршрутизация должна использоваться только в том случае, если есть способ логически делить ваши данные, избегая горячих точек.

Отладочный поисковый запрос

Если вы когда-либо задавались вопросом, почему документ не соответствует запросу, сначала проверьте сопоставление типа с помощью API-интерфейса GET, как показано здесь:

Если отображение правильное, убедитесь, что значение текстового поля проанализировано правильно. Вы можете использовать API анализа для проверки списка токенов, как показано ниже:

Если анализатор ведет себя так, как ожидалось, убедитесь, что вы используете правильный тип запроса. Например, используя совпадение вместо запроса термина и так далее.