Для логистической компании необходимо иметь геосервис. Геосервис — сервис, выдающий геоточку по адресу, и наоборот, по геоточке определяющий адрес. Все легко и просто, когда поиск делает машина, наименование города, наименование улицы и прочих атрибутов поиска можно указать сколь угодно, и это все приведет у лучшей точности и быстроте. Но все резко меняется, когда поиск начинает делать человек. Человек, используя свой контекст (знания, навыки, привычки), начинает искать настолько свободно, насколько сам свободно мыслит. И это может свести с ума любую машину. Не претендуя на знания в последней инстанции, опишу несколько выразительных этапов, которые прошли мы.
Этап первый. Эластик все сам поймет!
Уверовав в мощь Эластика, мы решили, а пусть эластик сам ищет по строке, содержащий полный адрес. То есть, воспользуемся возможностью полнотекстового поиска Эластика. Сказано — сделано. Создали индекс эластика, причем разбили слова на нграммы, привели все к одному регистру и пр. Сделали микросервис, пробуем. Эластик не подвел, но есть ньюанс:
он, конечно же, находит нужный адрес, но где-то далеко, за пределами двадцати строк, которые комфортно оценивать человеку. Начинает сказываться порядок записи данных в индекс, сколько раз повторяются слова и прочее, и прочее. Самый простой вариант причины: это при поиске улицы выдаются еще и дома, а их много…. Так дело не пойдет.
Этап второй. Все мы из реляционных баз данных
Раз не получается точно искать полнотекстовым поиском, то нужно данные структурировать! Решили структурировать так, выбрать основные атрибуты адреса: наименование города, наименование улицы, номер дома. Это основные структуры, которые должны быть в любом адресе(хотя и не всегда, есть поселки, где нет улиц!). Сделали новую структуру индекса в эластике. Но появилась проблема: адрес приходит единой строкой. Где в ней город, улица, дом? Все легко решается если пользователь указывает якоря: г. или город, ул. или улица и т. д. Хотя и тут вариантов различных якорей — масса(только по языкам сколько). Вариант с якорями хорошо работает, эластик ищет быстро и точно. Но если пользователь не указывает якоря? Сделали предположение что пользователь вводит сначала город, потом улицу, потом дом. То есть считаем что город пользователь точно ввел. Пробуем и вроде работает, но как-то не уверенно, то ищет, то нет. И проявилась проблема двойных названий: Нижний Новгород, Верхний Уфалей, Третья Улица Строителей.
Пробовали докурить алгоритмы, но все они решали какие-то мелкие частные случаи. Тут вспомнили про венгерскую нотацию, и решили, что разбирать строку нужно не сначала строки, а с конца и сразу определиться, до какого уровня точности указал адрес человек. А именно если в конце строки стоит цифра, то скорее всего это номер дома или квартиры. А, раз указан номер дома, то слева от него это улица. Переписали алгоритм на использование венгерской нотации, проб��ем. Удивительно, но большинство тестов проходит! Но есть нюанс: проблема двойных названий никуда не ушла. Да и как определить где заканчивается наименование города и начинается наименование улицы? Приходится использовать дикий поиск в Эластике, что сказывается на производительности. Тупик….
Этап третий. Изучай инструмент, которым пользуешься
При дальнейшем поиске приемлемого решения, возникла мысль: использовать особенность обратного индекса Эластика. Давайте соберем все слова из наших данных в поле типа «свойства», но сохранив их в единственном числе. Итого нам нужно решить несколько проблем:
а) как скопировать значения, используя Эластик
б) как обеспечить их единственное число
в) как обеспечить структурированность документа, при этом не используя поля документа в поиске
Оставить структуру документа и отключить часть полей из поиска можно:
указав свойство "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 содержит просто поиск, введенный пользователем, например «Уфа Искино»

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

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