Блог Галс Софтвэр

Что такое нечёткий поиск в Elasticsearch и как с ним работать

Elastic Stack
Для полнотекстового поиска точные поисковые выражения должны находиться в одном из полей документа. Ну, а если пользователь вводит что-то, что не является точным совпадением, но все же этот запрос должен что-то вернуть? Например, пользователь опечатался и вместо «ваза» написал «виза»; в этом случае Elasticsearch все еще может вернуть в ответе прекрасные вазы в интернет-магазине. Такое поведение можно реализовать благодаря нечеткому поиску. Нечеткость запроса интерпретируется как расстояние Левенштейна, которое представляет собой количество изменений одного символа, внесенных в строку, чтобы сделать ее такой же, как другая строка. Это означает, что для того, чтобы «виза» соответствовала «вазе», необходима нечеткость, равная единице. Таким образом, нечеткость — это количество символов, которые могут отличаться от заданного искомого выражения.

Нечеткий поиск можно выполнять не только в поисковом запросе, но и в строке запроса. Чтобы запустить нечеткий поиск, добавьте знак тильды (~) в конец поискового запроса, за которым следует необязательное целое число. Если после знака тильды не указано число, расстояние по умолчанию равно двум.

GET /catalog_index/_search?q=виза~1

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

Теперь посмотрим, как это делается с помощью DSL запросов, которые используются чаще.

GET /catalog_index/_search
{
  "query": {
    "match": {
      "name": {
        "query": "виза",
          "fuzziness": 1
      }
    }
  }
}

Этот запрос идентичен запросу через URL-строку выше. Он ищет выражения «виза», где один символ может быть другим. Если посмотрим на результаты, то увидим, что совпадающие результаты не имеют выражения «виза», но есть «ваза», которая совпадает с разницей в один символ. Это удовлетворяет ограничению, которое указано в запросе, поэтому документы считаются совпадающими. Обратите внимание, что максимальная нечеткость, поддерживаемая Lucene, равна двум. Нечеткость больше двух слишком затратна с точки зрения производительности и будет игнорироваться Lucene.

Выражение, которое мы искали в запросе, состоит всего из четырех символов. Например, если установить свойство fuzziness равным двум, это может привести к результатам низкого качества. Результат связан с тем, что допустимое расстояние очень близко к общему количеству символов в поисковом запросе; в этом случае 50% символов могут отличаться. Решение этой проблемы заключается в увеличении и уменьшении максимального расстояния в зависимости от длины поискового запроса. Хотя это легко сделать в приложении, Elasticsearch предоставляет эту функциональность из коробки. Указав вместо этого значение параметра fuzziness в виде строки со значением auto, Elasticsearch позаботится об этом автоматически.

GET /catalog_index/_search
{
  "query": {
    "match": {
      "name": {
        "query": "виза",
          "fuzziness": "AUTO"
      }
    }
  }
}

Когда используется режим auto, Elasticsearch определяет наиболее подходящее максимальное расстояние на основе длины поискового запроса. Если запрос имеет длину от 0 до 2 символов, он должен точно совпадать. Если это от 3 до 5 символов, используется расстояние 1. Если запрос длиннее 5 символов, используется расстояние равное 2. Хотя эта логика очень проста, ее удобнее выносить Elasticsearch. В большинстве случаев для свойства fuzziness рекомендуется использовать как раз auto.

А теперь несколько замечаний. Важно понимать, что при выполнении нечетких запросов, запрос сравнивается с выражениями (если быть точным, то инвертированным индексом), которые хранятся в индексе. Выражения являются результатом анализа текстовых полей при добавлении или обновлении документов в индексе. Следовательно, поиск осуществляется по проанализированным выражениям, а не по фактически сохраненному документу. Из-за такого анализа текста запрос может быть сравнен с непредвиденным значением, что иногда может приводить к неожиданным результатам. Это важно помнить при отладке поискового запроса и попытке выяснить, почему поисковая выдача вдруг стала соответствовать нечеткому запросу.

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

Есть несколько вещей, которые нужно учитывать с точки зрения производительности. Несмотря на то, что реализация нечеткого поиска в Lucene работает быстро, она медленнее, чем запуск простого match-запроса. Время, необходимое для выполнения нечеткого запроса, увеличивается с увеличением количества уникальных выражений в индексе. Причина такой разницы в производительности заключается в том, что для match-запросов Lucene выполняет двоичный поиск по своему индексу выражений, который хранится внутри. Двоичный поиск чрезвычайно масштабируем и быстр даже для очень больших наборов данных. Нечеткий поиск использует другой алгоритм, использующий детерминированный конечный автомат или DFA, который медленнее, чем бинарный поиск. Нечеткий поиск должен обрабатывать большее количество выражений по сравнению с бинарным поиском, поэтому эти запросы всегда будут медленнее. Не страшно, если вы не знаете, что такое бинарный поиск или DFA. Достаточно знать, что нечеткие запросы медленнее, чем простые match-запросы, поэтому, если возникнут проблемы производительности, это может быть хорошей точкой местом для оптимизации запросов.

Один из способов сделать это — потребовать, чтобы совпадения имели точное совпадение префикса в запросе. Это означает, что первые X символов должны точно совпадать и не должны отличаться от символов поискового запроса. Это уменьшает количество символов, которые могут различаться, и, следовательно, уменьшает количество выражений, которые должен обрабатывать нечеткий поиск. Например, можно определить, что первые два символа поискового запроса должны точно совпадать. Чем больше длина префикса, тем быстрее будет выполняться запрос. Использование этого метода является компромиссом между производительностью и качеством результатов поиска, поскольку любая опечатка в первых символах может привести к несоответствию некоторых релевантных документов. Для этого нужно добавить свойство prefix_length к объекту в запросе на соответствие и добавить целое число, определяющее количество начальных символов, которые должны точно совпадать.

Спасибо за внимание!

Если вы только начинаете изучение Elastic, приходите на наш 3-дневный семинар инструктаж и освойте весь функционал в сжатый срок.

Если нужна консультация по Elastic или у вас другой вопрос, оставьте заявку в форме обратной связи.