Нечеткий поиск (fuzzy search) в реляционных базах данных

habr.png

Для поиска нужной информации на веб-сайтах и в мобильных приложениях часто используется поиск по словам или фразам, которые пользователь свободно вводит с клавиатуры (а не выбирает например из списка). Естественно, что пользователь может допускать ошибки и опечатки. В этом случае полнотекстовый поиск, полнотекстовые индексы, которые реализованы в большинстве базы данных не дают ожидаемого результата и практически бесполезны. Такой функционал все чаще реализуют на основе elasticsearch.

Решения с использованием elasticsearch имеют один существенный недостаток — очень большая вероятность рассогласования основной базы данных, например PostgreSQL, MySQL, mongodb и elasticsearch, в которой хранятся индексы для поиска.

Идеальным вариантом было бы наличие «моста», который бы брал на себя функцию согласования данных, в том случае когда база для поискового движка окажется недоступной во время обновления основной базы данных. Но я не нашел пока реализации такого моста. Например в одном из проектов связки mongodb и lucene говорится как раз об этой проблеме.

On a normal shutdown of a LuMongo node, all segments committed and are distributed to existing nodes. This allows for rolling shutdowns of the nodes to update them. On unexpected shutdown the segments will fail to the existing nodes without committing. These indexes could require rollback or repair. Currently this is not handled automatically but it will be in future releases using Lucene’s built in index repair. Since the documents are stored in MongoDB and not in the index, another possible solution could be fetching the documents for a corrupted segment and reindexing them. MongoDB also provides seamless failover through replication. MongoDB’s replication is data center aware backups across datacenters are possible.

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

Надеюсь что устойчивые «мосты», которые будут устойчиво обновлять индексы в elasticsearch или lucene рано или поздно появятся. Сейчас же есть необходимость найти рабочее решение.

Один из вариантов — это использовать единую базу для хранения данных и для поиска. По поводу использования в качестве такой единой базы elasticsearch, практически во всех обсуждениях на форумах был консенсус, что такое решение не подходит. Поэтому я начал искать базу данных в которой можно было бы создавать полнотекстовые индексы, поддерживающие нечеткий поиск. Поскольку основной движок для таких индексов, lucene, разработан на java, круг баз данных, в которых я искал такую возможность был явно очерчен.

Как оказалось, есть как минимум два решения, которые используют библиотеку lucene и находятся на уровне приложений poduction ready: это orientdb и h2.

В orientdb работать с нечетким полнотекстовым поиском очень просто:

create class russian
create property russian.message string
create index russian.message on russian(message)
  fulltext engine lucene metadata {
    "analyzer": "org.apache.lucene.analysis.ru.RussianAnalyzer"
  }
select * from russian where message lucene 'Харбахрб~0.5' limit 2


В h2 немного сложнее т.к. индекс это отдельная таблица с которой нужно основную таблицу связать. Но немного сложнее это не означает сложно.

CREATE ALIAS IF NOT EXISTS FTL_INIT FOR 
"org.h2.fulltext.FullTextLucene.init";
CALL FTL_INIT();
DROP TABLE IF EXISTS TEST;
CREATE TABLE TEST(ID INT PRIMARY KEY, FIRST_NAME VARCHAR, 
LAST_NAME VARCHAR);
CALL FTL_CREATE_INDEX('PUBLIC', 'TEST', NULL);
INSERT INTO TEST VALUES(1, 'John', 'Wayne');
INSERT INTO TEST VALUES(2, 'Elton', 'John');
SELECT * FROM FTL_SEARCH_DATA('John', 0, 0);
SELECT * FROM FTL_SEARCH_DATA('LAST_NAME:John', 0, 0);


apapacy@gmail.com
22 апреля 2018 года

© Habrahabr.ru