Введение.


Для логистической компании необходимо иметь геосервис. Геосервис — сервис, выдающий геоточку по адресу, и наоборот, по геоточке определяющий адрес. Все легко и просто, когда поиск делает машина, наименование города, наименование улицы и прочих атрибутов поиска можно указать сколь угодно, и это все приведет у лучшей точности и быстроте. Но все резко меняется, когда поиск начинает делать человек. Человек, используя свой контекст (знания, навыки, привычки), начинает искать настолько свободно, насколько сам свободно мыслит. И это может свести с ума любую машину. Не претендуя на знания в последней инстанции, опишу несколько выразительных этапов, которые прошли мы.


Этап первый. Эластик все сам поймет!


Уверовав в мощь Эластика, мы решили, а пусть эластик сам ищет по строке, содержащий полный адрес. То есть, воспользуемся возможностью полнотекстового поиска Эластика. Сказано — сделано. Создали индекс эластика, причем разбили слова на нграммы, привели все к одному регистру и пр. Сделали микросервис, пробуем. Эластик не подвел, но есть ньюанс:

он, конечно же, находит нужный адрес, но где-то далеко, за пределами двадцати строк, которые комфортно оценивать человеку. Начинает сказываться порядок записи данных в индекс, сколько раз повторяются слова и прочее, и прочее. Самый простой вариант причины: это при поиске улицы выдаются еще и дома, а их много…. Так дело не пойдет.


Этап второй. Все мы из реляционных баз данных.


Раз не получается точно искать полнотекстовым поиском, то нужно данные структурировать! Решили структурировать так, выбрать основные атрибуты адреса: наименование города, наименование улицы, номер дома. Это основные структуры, которые должны быть в любом адресе(хотя и не всегда, есть поселки, где нет улиц!). Сделали новую структуру индекса в эластике. Но появилась проблема: адрес приходит единой строкой. Где в ней город, улица, дом? Все легко решается если пользователь указывает якоря: г. или город, ул. или улица и т. д. Хотя и тут вариантов различных якорей — масса(только по языкам сколько). Вариант с якорями хорошо работает, эластик ищет быстро и точно. Но если пользователь не указывает якоря? Сделали предположение что пользователь вводит сначала город, потом улицу, потом дом. То есть считаем что город пользователь точно ввел. Пробуем и вроде работает, но как-то не уверенно, то ищет, то нет. И проявилась проблема двойных названий: Нижний Новгород, Верхний Уфалей, Третья Улица Строителей.

Пробовали докурить алгоритмы, но все они решали какие-то мелкие частные случаи. Тут вспомнили про венгерскую нотацию, и решили, что разбирать строку нужно не сначала строки, а с конца и сразу определиться, до какого уровня точности указал адрес человек. А именно если в конце строки стоит цифра, то скорее всего это номер дома или квартиры. А, раз указан номер дома, то слева от него это улица. Переписали алгоритм на использование венгерской нотации, пробуем. Удивительно, но большинство тестов проходит! Но есть ньюанс: проблема двойных названий никуда не ушла. Да и как определить где заканчивается наименование города и начинается наименование улицы? Приходится использовать дикий поиск в Эластике, что сказывается на производительности. Тупик….


Этап третий. Изучай инструмент, которым пользуешься.


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

а) как скопировать значения, используя Эластик

б) как обеспечить их единственное число

в) как обеспечить структурированность документа, при этом не используя поля документа в поиске

Оставить структуру документа и отключить часть полей из поиска можно:
указав свойство "index": false , для полей документа по которым не будем искать, и значения которых будем записывать в отдельную структуру. Далее, копирование значения из поля документа в выделенную структуру документа можно с помощью "copy_to": ["collector.default"]. Таким способом Эластик при индексации документа будет копировать значение из одного поля в другое, и нам не нужно беспокоиться об этом. Так же, еще нужно обеспечить копирование значений в единственном числе, этого можно добиться указанием фильтров: "filter": [
"word_delimiter",
"lowercase",
"remove_duplicates"
],

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

Создаем индекс в Эластике.

Первые результаты: размер индекса на 40% меньше.
Пробуем поиск по этому индексу:

QueryBuilders.multiMatchQuery(query) .field("collector.default", 1.0f) .prefixLength(2) .analyzer("search_ngram") .tieBreaker(0.4f);


query содержит просто поиск, введенный пользователем, например «Уфа Искино»


находит сразу и первым.

так же хорошо ищет с номерами домов

По времени: я нахожусь за тысячи километров от Эластика. Куча сетей. В деревне.

Подведем некоторые итоги:
а) индекс меньше почти в два раза, а значит и поиск будет быстрее;

б) с помощью дополнительного специально организованного поля можно избавиться от сложных алгоритмов разбора запроса;

в) ушла проблема двойных наименований;

г) можно искать на разных языках;

д) новые или старые названия;

е) слэнги и прочее, все что можно учитывать при поиске.


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

Комментарии (0)