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

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

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

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

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

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

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

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

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

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

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

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

Оставить структуру документа и отключить часть полей из поиска можно:
указав свойство "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 содержит просто поиск, введенный пользователем, например «Уфа Искино»

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

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

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

Подведем некоторые итоги:

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

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

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

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

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

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

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