Полнотекстовый поиск в CouchDB

У меня есть проблема и надеюсь получить от вас ответ: -)

Итак, я взял geonames.org и импортировал все данные немецких городов со всеми районами.

Если я войду в "Гамбург", в нем перечислены "Гамбургский центр, аэропорт Гамбурга" и т.д. Приложение находится в закрытой сети без доступа к Интернету, поэтому я не могу получить доступ к веб-службам geonames.org и импортировать данные.:( Город со всеми его районами работает как автоматическое завершение. Таким образом, каждое нажатие клавиши приводит к запросу XHR и т.д.

Теперь мой клиент спросил, есть ли в нем все данные мира. Наконец, около 5.000.000 строк с 45.000.000 альтернативных имен и т.д.

Postgres требуется около 3 секунд на запрос, что автоматически отключает авто.

Теперь я подумал о CouchDb, уже работал с ним. Мой вопрос:

Я бы хотел опубликовать сообщение "Ham", и я хочу, чтобы CouchDB получил все документы, начиная с "Ham". Если я войду в "Гамбург", я хочу, чтобы он вернул Гамбург и т.д.

Является ли CouchDB правой базой данных? Какие другие БД вы можете рекомендовать, чтобы они реагировали с низкой задержкой (может быть в памяти) и миллионами наборов данных? Набор данных не меняется регулярно, его довольно статический!

Ответы

Ответ 1

Если я правильно понимаю вашу проблему, возможно, все, что вам нужно, уже встроено в CouchDB.

  • Чтобы получить диапазон документов с именами, начинающимися с, например, "Ветчина". Вы можете использовать запрос со строковым диапазоном: startkey="Ham"&endkey="Ham\ufff0"
  • Если вам нужен более полный поиск, вы можете создать представление, содержащее имена других мест в качестве ключей. Таким образом, вы снова можете запрашивать диапазоны, используя технику выше.

Вот функция просмотра, чтобы сделать это:

function(doc) {
    for (var name in doc.places) {
        emit(name, doc._id);
    }
}

Также см. сообщение блога CouchOne о CouchDB typeahead и автозаполнение поиска, и это обсуждение в списке рассылки о Автозаполнение CouchDB.

Ответ 2

Оптимизированный поиск с помощью PostgreSQL

Ваш поиск привязан к началу и не требуется логика нечеткого поиска. Это не типичный вариант использования для полнотекстового поиска.

Если он становится более нечетким или ваш поиск не привязан к началу, посмотрите здесь:
Аналогичные строки UTF-8 для поля автозаполнения
Подробнее о шаблонах в Postgres.

В PostgreSQL вы можете использовать расширенные функции индекса, которые должны сделать запрос очень быстрым. В частности, посмотрите классы операторов и индексы в выражениях.

1) text_pattern_ops

Предполагая, что ваш столбец имеет текст типа, вы бы использовали специальный индекс для операторов текстового шаблона, например:

CREATE INDEX name_text_pattern_ops_idx
ON tbl (name text_pattern_ops);

SELECT name
FROM   tbl
WHERE  name ~~ ('Hambu' || '%');

Предполагается, что вы работаете с языковой базой данных, отличной от C - скорее всего, de_DE.UTF-8 в вашем случае. Вы также можете создать базу данных с языковой версией "C". Я цитирую здесь здесь:

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


2) Указатель на выражение

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

CREATE INDEX lower_name_text_pattern_ops_idx
ON tbl (lower(name) text_pattern_ops);

SELECT name
FROM   tbl
WHERE  lower(name) ~~ (lower('Hambu') || '%');

Чтобы использовать индекс, предложение WHERE должно соответствовать выражению индекса.


3) Оптимизировать размер и скорость индекса

Наконец, вы также можете наложить ограничение на число ведущих символов, чтобы свести к минимуму размер вашего индекса и ускорить его:

CREATE INDEX lower_left_name_text_pattern_ops_idx
ON tbl (lower(left(name,10)) text_pattern_ops);

SELECT name
FROM   tbl
WHERE  lower(left(name,10)) ~~ (lower('Hambu') || '%');

left() знакомится с Postgres 9.1. Используйте substring(name, 1,10) в старых версиях.


4) Закройте все возможные запросы

Как насчет строк с более чем 10 символами?

SELECT name
FROM   tbl
WHERE  lower(left(name,10)) ~ (lower(left('Hambu678910',10)) || '%');
AND    lower(name) ~~ (lower('Hambu678910') || '%');

Это выглядит излишним, но вам нужно указать его таким образом, чтобы фактически использовать индекс. Поиск индекса будет сужать его до нескольких записей, дополнительное условие фильтрует остальные. Поэкспериментируйте, чтобы найти сладкое пятно. Зависит от распределения данных и типичных случаев использования. 10 персонажей кажутся хорошей отправной точкой. Для более чем 10 символов left() эффективно превращается в очень быстрый и простой алгоритм хэширования, который достаточно хорош для многих (но не для всех) случаев использования.


5) Оптимизируйте представление диска с помощью CLUSTER

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

Когда таблица кластеризована, она физически переупорядочивается на основе информации индекса.

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

Первый вызов:

CLUSTER tbl USING lower_left_name_text_pattern_ops_idx;

Информация о том, какой индекс для использования будет сохранен, а последующие вызовы будут повторно кластеризовать таблицу:

CLUSTER tbl;
CLUSTER;    -- cluster all tables in the db that have previously been clustered.

Если вы не хотите его повторять:

ALTER TABLE tbl SET WITHOUT CLUSTER;

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


6) Предотвратите слишком много строк в результате

Спросите минимум, скажем, 3 или 4 символа для строки поиска. Я добавляю это для полноты, вы, вероятно, все равно это сделаете. И LIMIT количество возвращенных строк:

SELECT name
FROM   tbl
WHERE  lower(left(name,10)) ~~ (lower('Hambu') || '%')
LIMIT  501;

Если ваш запрос возвращает более 500 строк, попросите пользователя сузить его поиск.


7) Оптимизировать метод фильтра (операторы)

Если вы абсолютно должны выжать каждую последнюю микросекунду, вы можете использовать операторов семейства text_pattern_ops. Вот так:

SELECT name
FROM   tbl
WHERE  lower(left(name, 10)) ~>=~ lower('Hambu')
AND    lower(left(name, 10)) ~<=~ (lower('Hambu') || chr(2097151));

Вы получите очень мало с этим последним трюком. Обычно стандартными операторами являются лучший выбор.


Если вы все это сделаете, время поиска будет уменьшено до миллисекунды.

Ответ 3

Я думаю, что лучший подход - сохранить ваши данные в своей базе данных (Postgres или CouchDB) и проиндексировать их с помощью полнотекстового поискового механизма, например Lucene, Solr или ElasticSearch.

Сказав это, существует проект интегрирующий CouchDB с Lucene.