В предыдущих лекциях я показал вам, как выполнять различные виды поиска. Для полнотекстового поиска точные термины, которые я искал, должны были совпадать где-то в документе. Но что если пользователь вводит что-то, что не является точным совпадением, но все же должно считаться совпадением? Например, пользователь может допустить опечатку и ввести past вместо pasta; в этом случае мы все равно хотим, чтобы продукты и рецепты из макаронных изделий совпадали. Обеспечить такое поведение на самом деле довольно просто благодаря тому, что называется нечетким поиском.
Нечеткость для запроса интерпретируется как так называемое расстояние редактирования Левенштейна, которое представляет собой количество изменений в один символ, которые необходимо внести в одну строку, чтобы она стала такой же, как другая строка. Это означает, что для того, чтобы термин past соответствовал термину pasta, необходима нечеткость, равная единице. Таким образом, размытость - это количество символов, которые могут отличаться от данного искомого термина.
Сначала я покажу вам, как использовать нечеткий поиск с поиском в строке запроса. Чтобы выполнить нечеткий поиск, просто добавьте знак тильды (~) в конец термина, за которым следует необязательное целое число. Если после знака тильды не указано число, расстояние редактирования по умолчанию равно двум.
1 | GET /ecommerce/product/_search?q=past~1 |
В этом примере я ищу термин past, но с расстоянием редактирования, равным единице. Если я выполню этот запрос, вы увидите, что продукты, названия которых включают термин pasta, будут включены, поскольку расстояние редактирования между этим термином и поисковым запросом равно единице. При желании вы можете сделать это для нескольких терминов в запросе.
Давайте посмотрим, как это делается с помощью DSL запроса, что является более распространенным подходом. Вместо того чтобы просто вложить пары ключ-значение имен полей и значений в запрос на соответствие, я добавлю ключ с именем поля и объект в качестве его значения. Это связано с тем, что объект позволяет мне использовать свойство fuzziness для управления тем, насколько значение поля может отличаться от поискового запроса.
1 2 3 4 5 6 7 8 9 10 11 | GET /ecommerce/product/_search { "query": { "match": { "name": { "query": "past", "fuzziness": 1 } } } } |
Этот запрос идентичен запросу, который я показывал вам ранее с использованием подхода строки запроса. Он ищет термин past, где один символ может быть другим. Если мы посмотрим на результаты, то увидим, что в названии совпадающих продуктов нет термина past, а есть термины paste и pasta, которые совпадают с разницей в один символ. Это удовлетворяет ограничению, которое я указал в своем запросе, поэтому документы считаются совпавшими. Обратите внимание, что максимальная нечеткость, поддерживаемая Lucene, равна двум. Нечеткость больше двух слишком дорога с точки зрения производительности и будет проигнорирована Lucene.
Термин, который я искал в своем запросе, состоит только из четырех символов. Если я установлю свойство нечеткости, например, на два, это может привести к непредсказуемым результатам и результатам низкого качества. Это происходит потому, что допустимое расстояние редактирования очень близко к общему количеству символов в поисковом запросе; в этом случае 50% символов будут отличаться друг от друга. Решением этой проблемы является увеличение и уменьшение максимального расстояния редактирования в зависимости от длины поискового запроса. Хотя это легко сделать в приложении, Elasticsearch удобно предоставляет эту функциональность из коробки. Указав значение параметра fuzziness как строку со значением auto, Elasticsearch позаботится об этом за нас.
1 2 3 4 5 6 7 8 9 10 11 | GET /ecommerce/product/_search { "query": { "match": { "name": { "query": "past", "fuzziness": "AUTO" } } } } |
Когда используется значение нечеткости auto, Elasticsearch определит наиболее подходящее максимальное расстояние редактирования на основе длины поискового запроса. Если длина запроса составляет от 0 до 2 символов, он должен точно совпадать. Если он составляет от 3 до 5 символов, используется расстояние редактирования, равное 1. Если длина запроса превышает 5 символов, используется расстояние редактирования, равное 2. Хотя эта логика очень проста, удобно не делать этого внутри приложения, использующего Elasticsearch. На самом деле, в большинстве случаев рекомендуется использовать auto для свойства fuzziness.
Теперь, когда я показал вам, как выполнять нечеткие запросы, я хочу поделиться с вами несколькими замечаниями и соображениями по поводу производительности. Прежде всего, важно понимать, что при выполнении нечетких запросов запрос сравнивается с терминами, которые хранятся в индексе. Термины являются результатом анализа текстовых полей при добавлении или обновлении документов в индексе. Поэтому поиск ведется именно по анализируемым терминам, а не по реальному хранящемуся документу. Из-за такого анализа текста запрос может быть сопоставлен с непредвиденным значением, что иногда приводит к путаным результатам. Это важно иметь в виду при отладке поискового запроса и попытке выяснить, почему данное значение соответствует нечеткому запросу.
Помимо использования запроса на соответствие со свойством нечеткости, существуют и другие способы выполнения нечетких запросов, наиболее важным из которых является нечеткий запрос. Этот запрос не допускает никакого анализа текста запроса и предоставляет только часть функций запроса на соответствие. Поэтому он менее универсален, и предпочтение следует отдавать запросу на соответствие, если нет веских причин поступать иначе.
Есть несколько моментов, которые следует учитывать с точки зрения производительности. Несмотря на то, что реализация нечеткого поиска в Lucene работает быстро, она медленнее, чем выполнение обычного запроса на совпадение. Время, необходимое для выполнения нечеткого запроса, увеличивается с ростом числа уникальных терминов в индексе. Причина такого различия в производительности заключается в том, что для запросов на совпадение Lucene выполняет бинарный поиск по индексу терминов, который хранится внутри. Бинарный поиск является чрезвычайно масштабируемым и быстрым, даже для очень больших наборов данных. Нечеткий поиск использует другой алгоритм с использованием детерминированного конечного автомата, или DFA, который, естественно, медленнее, чем двоичный поиск. Нечеткий поиск требует обработки большего количества терминов по сравнению с бинарным поиском, поэтому такие запросы всегда будут медленнее. Не волнуйтесь, если вы не знаете, что такое двоичный поиск или DFA, это не так важно для вас. Просто знайте, что нечеткие запросы медленнее, чем запросы с простым совпадением, поэтому если вы столкнулись с проблемами производительности, то это может быть хорошим местом для оптимизации ваших запросов.
Один из способов сделать это - потребовать, чтобы совпадения имели точные префиксные соответствия в запросе. Это означает, что первые X символов должны точно совпадать и не должны отличаться от символов поискового запроса. Это уменьшает количество символов, которые могут отличаться, и, следовательно, уменьшает количество терминов, которые должен обработать нечеткий поиск. Например, вы можете определить, что первые два символа поискового запроса должны точно совпадать. Чем больше длина префикса, тем быстрее будет выполняться запрос. Для небольших или средних наборов данных вам не нужно беспокоиться об этом, поэтому для большинства людей это будет просто то, о чем нужно помнить, если количество данных в вашем индексе сильно вырастет. Использование этой техники - это компромисс между производительностью и качеством результатов поиска, поскольку любая опечатка в первых символах приведет к тому, что некоторые релевантные документы не будут найдены. Как я уже сказал, в общем случае этого делать не стоит, но если у вас много данных и вам нужен прирост производительности, то это может быть решением. Для этого просто добавьте свойство prefix_length к объекту в запросе на соответствие и добавьте целое число, определяющее количество ведущих символов, которые должны точно совпадать.
Вот и все для нечеткого поиска! В следующей статье мы рассмотрим поиск по близости, который в некоторой степени связан с нечетким поиском. До тех пор, счастливого поиска!