Полнотекстовый поиск в CouchDB - PullRequest
14 голосов
/ 13 марта 2011

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

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

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

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

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

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

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

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

Ответы [ 3 ]

19 голосов
/ 13 марта 2011

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

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

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

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

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

15 голосов
/ 20 ноября 2011

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

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

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

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

1) text_pattern_ops

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

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));

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


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

10 голосов
/ 13 марта 2011

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

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

...