Архив рубрики: Sphinx

Использование Sphinx с MongoDB

Рассмотрим подключение поисковика Sphinx к каталогу книг на MongoDB.

Sphinx умеет индексировать данные из MySQL и PostgreSQL. Также он имеет механизм для настройки индексации из произвольных источников, делается это с помощью xmlpipe2. Суть xmlpipe2 проста, нужно написать спкрит на любом языке который пишет в stdout xml-файл с данными для индексации.

На выходе дложен генерироваться подобный XML.

<?xml version="1.0" encoding="utf-8"?>
<sphinx:docset>

<sphinx:schema>
<sphinx:field name="name"/>
<sphinx:field name="author"/>
<sphinx:field name="isbn"/>
</sphinx:schema>

<sphinx:document id="1234">
<name>Deadline. Роман об управлении проектами</name>
<author>Том Демарко</author>
<isbn>978-5-91657-150-9</isbn>
</sphinx:document>

<sphinx:document id="1235">
<name>Мифический человеко-месяц, или Как создаются программные системы</name>
<author>Фредерик Брукс</author>
<isbn>5-93286-005-7</isbn>
</sphinx:document>

В зависимости от данных можно указать разные поля. В моем случае это текстовые поля name, author, и ISBN. Сначала я написал тэг ISBN в верхнем регистре, но почему-то Сфинксу это не понравилось, пришлось указать в нижнем. И еще замечу id не может быть нулем или отрицательным числом.

sphinx.conf

source books {
    type = xmlpipe
    xmlpipe_command = python /path/to/xmlpipe.py
}

index books_index {
        morphology              = stem_enru
        charset_type            = utf-8
        source                  = books
        path                    = /path/to/data/sphinx/books
}

searchd {
    port                = 3312
    log                 = /path/to/searchd.log
    query_log           = /path/to/query.log
    pid_file            = /path/to/searchd.pid
}

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

Вариант 1 — Используем счетчики

В MomgoDB нету поля auto increment, поэтому решаем это с помощью коллекции счетчиков (самая обычная коллекция)

db.counters.insert({_id: "books", value: 0});

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

Вариант 2 — Используем в качестве идентификатора числовой хеш

Если у наших объектов, есть глобальный идентификатор, например как ISBN у книг. Мы можем использовать в качестве идентификатора 64-битный хеш. С учетом того что 64 битное число в MongoDB имеет знак (спецификация BSON), мы можем использовать только 63-бита. Вобщем тоже неплохо.

Питон легко работает с числами произвольной точности. А вот Sphinx по умолчанию понимает беззнаковые 32-битные числа, чтобы он понимал беззнаковые 64-битные числа нужно собрать его указав специальную опцию.

$ ./configure --enable-id64
$ make 
$ sudo make install

На этом технические нюансы не заканчиваются. Дело в том что в Javascript для представления 64-битных чисел используется формат double. Максимальная точность лишь 53 бита. Это может представлять проблему, если вы захотите поиграться с этими числами в терминале монги. Можете сравнить выполнив в терминале монги Math.pow(2,60) и с аналогичной операцией в питоне. Если же вы не собираетесь произваодить математических операций в терминале монги, то с 63 битными хешами не должно быть проблем.

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

> Math.pow(2,53)
9007199254740992

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

Пример 53-битной хеш-функции.

import hashlib

def hash53(text):
    return long(hashlib.md5(text).hexdigest(),16) & long('1'*53,2)

Тут мы испоьзуем любой из стандартных хешей (md5, sha1, и пр.) с равномерным распределением и берем младшие 53 бита.

Ссылки
https://en.wikipedia.org/wiki/Double_precision
http://sphinxsearch.com/docs/current.html#xmlpipe2
http://bsonspec.org/#/specification