В проекте, в котором я сейчас участвую, одним из центральных элементов является полнотекстовый поиск по базе данных продуктов объемом около 1 млн. записей. У ребят уже был реализован поиск, используя встроенные возможности Postgresql (поле типа TSVector и специальный полнотекстовый индекс). Но скорость поиска достаточно низка, плюс усложняющая логику реализация в ORM Джанги. Поэтому возникла идея попробовать поиск через Sphinx. С этого момента sphinx приятно удивил меня несколько раз своими возможностями, а удовольствие от работы с ним с каждым разом только возрастало :) Если в кратце, то оказалось, что sphinx заменяет собой не только полнотекстовый поиск в базе данных, но фактически вообще поиск в базе данных, а так же группировку и сортировку результатов. Итак, по порядку.
Во-первых, скачиваем архив с исходниками Sphinx-1.10beta. Там есть несколько вкусных плюшек, без которых мне не захотелось жить, по сравнению с версией 0.9.9 :) Компилируем и устанавливаем, с поддержкой Postgresql:
tar -xzf sphinx-1.10-beta.gz cd sphinx-1.10-beta ./configure --prefix=/usr/local/sphinx --with-pgsql-includes=/opt/local/include/postgresql84 --with-pgsql-libs=/opt/local/lib/postgresql84 --with-pgsql make sudo make install
Настраиваем линки к индексирующей утилите и демону Sphinx для удобства:
ln -s /usr/local/sphinx/bin/searchd searchd ln -s /usr/local/sphinx/bin/indexer indexer
Для интеграции Sphinx с Django я попробовал библиотеку Django-sphinx и остался доволен. Как мне говорили, какое-то время назад она была еще сыровата, но сейчас она меня вполне устраивает и легко поддается расширению. Единственный недостаток пожалуй, что похоже она не будет в ближайшее время поддерживаться создателем David Cramer, так как похоже у него просто теперь нет на это времени из-за его перехода в команду проекта Disques. Но уже сейчас на GitHub есть 12 форков библиотеки с разными расширениями функциональности. Я в том числе собираюсь в ближайшее время создать свой :)
Установка: pip install django-sphinx
Если хотите разобраться быстро, то просто пойдите на страницу django-sphinx по ссылке выше. Там достаточно понятно описаны основные возможности. Я же опишу все подробно и плюс несколько продвинутых фишек, для которых пришлось покопаться в исходниках. Итак, прежде всего нужно добавить в settings.py переменную:
# Sphinx 0.9.9 + SPHINX_API_VERSION = 0x116
Для добавления поиска через sphinx с указанием веса каждого полнотекстового поля в модель Джанго пишем:
from djangosphinx.models import SphinxSearch
class Tag(models.Model):
title = models.CharField(unique=True)
description = models.TextField()
types = models.ManyToManyFields(TagType, verbose_name=u'типы тэга')
status = models.SmallIntegerField(choices=[(1, u'первый статус'),
(2, u'второй статус')])
search = SphinxSearch(weights={
'title': 100,
'description': 30
})
Дополнительно при инициализации класса SphinxSearch можно задать название индекса, режим ранжирования результатов и т.д.
Далее нужно создать конфигурационный файл для sphinx. Тут нам тоже поможет django-sphinx, так как он умеет создавать либо полностью, либо примерно половину этого файла. Для этого добавим библиотеку djangosphinx в INSTALLED_APPS проекта. Затем, в корне проекта пишем:
./manage.py generate_sphinx_config <application name>
Для примера модели выше получаем:
source application_tag
{
type = pgsql
sql_host = host
sql_user = user
sql_pass = password
sql_db = database
sql_port =
sql_query_pre =
sql_query_post =
sql_query = \
SELECT id, title, description, status\
FROM application_tag
sql_query_info = SELECT * FROM `application_tag` WHERE `id` = $id
sql_attr_uint = status
}
index application_tag
{
source = application_tag
path = /var/data/application_tag
docinfo = extern
morphology = none
stopwords =
min_word_len = 2
charset_type = utf-8
min_prefix_len = 0
min_infix_len = 0
}
Значения переменных sql_host, sql_user, sql_pass, sql_db, sql_port будет заполнено из вашего settings.py. application - название приложения в котором объявлена модель. Рекомендую сразу заменить morphology = none на morphology = stem_enru для учета морфологии русского и английского языков. Теперь вручную в будущий конфиг-файл нужно будет только добавить служебный раздел sphinx. У меня он такой:
searchd
{
# какой порт и какой протокол "слушает" служба
listen = 3312
# файл с логами
log = /usr/local/sphinx/var/log/searchd.log
# файл с логами поисковых запросов
query_log = /usr/local/sphinx/var/log/query.log
# PID file, searchd process ID file name
# mandatory
pid_file = /usr/local/sphinx/var/log/searchd.pid
}
Все вместе сохраняем в файле sphinx.conf. В общем случае можете называть файл как хотите, но это название файла по умолчанию, который ищет в текущей папке демон searchd. Остается создать индексы sphinx командой: indexer --config sphinx.conf --all и запустить демон командой searchd.
Теперь самое интересное :) - что мы можем делать. Очевидно можем делать полнотекстовый поиск с учетом морфологии русского и английского языка:
Tag.search.query(u'тэгу')
Еще интереснее - можем искать так же по атрибутам. В нашем примере выше, атрибутом является поле status:
Tag.search.query(u'тэгу').filter(status=[1, 2])
Мы можем искать по диапазону:
Tag.search.query(u'тэгу').filter(status__range=[1, 3])
В версии 0.9.9 поддерживаются так же следующие типы атрибутов: sql_attr_bool, sql_attr_float, sql_attr_timestamp. В версии 1.1 дополнительно добавлен часто необходимый строковый тип атрибута: sql_attr_string. Следует отметить, что текстовый атрибут и другие атрибуты сохраняются вместе с основным индексом и поиск по ним напоминает поиск по полям в обычной базе данных, но быстрее. Впрочем текстового атрибута оказалось создателям sphinx мало, поэтому так же было добавлено специальное текстовое поле sql_field_string, которое одновременно является и текстовым атрибутом и полнотекстовым полем. Примечание: на данный момент django-sphinx не поддерживает текстовые атрибуты.
А теперь переходим к действительно интересным вещам: мы можем искать по ManyToMany полям в модели используя Multi Value Attribute (MVA). Это нам дает устранить еще одно узкое место баз данных в плане быстродействия и масштабирования - join таблиц. Сначала пропишем MVA атрибут в конфиге sphinx в конце раздела source application_tag:
sql_attr_multi = uint types from query; SELECT tag_id, typetag_id \ FROM application_tag_types
теперь мы можем искать по нему, как и по другим атрибутам:
Tag.search.query(u'тэг').filter(types=[1, 2, 3])
Радуемся дальше :) В sphinx 1.1 было добавлена поддержка поля sql_joined_field. Для чего оно нам? Представим стандартную ситуацию, например, у нас есть модель товар. К товару можно прикрепить несколько тэгов. Потом мы хотим найти все товары, через полнотекстовый поиск sphinx, у которых есть данный тэг. Без данного поля нам скорее пришлось бы сделать два запроса к sphinx. Сначала найти все тэги. Потом все товары у которых есть один из найденных тэгом. Теперь же мы можем без всяких join'ов обойтись одним запросом, сразу находя товары по их тэгам. Объявляем поле в конфиг-файле, например так:
sql_joined_field = tags_text from query; SELECT product_id, title \ FROM application_product_tags apt left join application_tag at on at.id = apt.tag_id \ order by product_id ASC
Примечание: данные, должны быть обязательно отсортированы по товарам.
Не забываем переиндексировать Sphinx. Теперь при обычном полнотекстовом поиске, так же будет производиться поиск и по этому полю.
Еще одна радость использования Sphinx: мы можем группировать результаты поиска по любому атрибуту или группе атрибутов:
from djangosphinx.apis.current import SPH_GROUPBY_ATTR
Tag.search.query(u'тэг').filter(types=[1, 2, 3]).group_by('status', SPH_GROUPBY_ATTR)
В результате sphinx вернет для каждой группы лучший результат, а так же в специальном дополнительном поле @count число элементов в группе. К полю @count можно будет достучаться в дальнейшем так: item._sphinx['attrs']['@count']
И последняя возможность Sphinx, о которой хочу упоминуть в этой статье - вы можете сортировать как по весам, так и по атрибутам:
Tag.search.query(u'тэг').filter(types=[1, 2, 3]).order_by('status')
Дополнительно django-sphinx поддерживает команды select_related и extra, которые передаются в ORM джанго. Не проблема будет добавить так же ORM команду filter, если после поиска в sphinx мы хотим дополнительно отфильтровать результат с помощью базы данных. Мне же, на данный момент, удалось обойтись исключительно поиском через sphinx, так что дополнительной фильтрации не потребовалось.
Как оказалось sphinx, помимо полнотекстового поиска, закрыл собой сразу несколько проблемных мест с быстродействием и масштабируемостью поиска. Благодаря использованию MVA атрибутов и sql_joined_field полей, а так же возможности сортировки и группировки результата. И все это вместе с в несколько раз более высокой скоростью поиска, а так же высокой скоростью переиндексации данных. В предстоящем релизе 1.1 будет так же добавлена возможность обновления индекса в реальном режиме.
Тем временем мое знакомство со sphinx продолжается и я еще обязательно напишу на эту тему.
| krvss - 31.10.2010 |
При конфигурации у меня почему-то упорно автоопределялся совершенно отсутствующий MySQL, помогла опция --without-mysql |
| alex - 18.12.2010 |
Спасибо за статью.
Как раз буду это реализовывать в своих задачах сейчас :) |
| Толик Востряков - 19.12.2010 |
Пожайлуста. Хочу еще дописать вторую статью в продолжение. Я достаточно допилил в процессе использования django-sphinx. Может пригодиться мой вариант: https://github.com/avostryakov/django-sphinx
|
| AXE7 - 03.05.2011 |
Анотолий, спасибо! Отличная статья. "Примечание: на данный момент django-sphinx не поддерживает текстовые атрибуты, но есть fork от основного проекта, в котором поддержка была добавлена." на git-hub я не нашёл этот форк. Не трудно ли тебе будет поделиться ссылкой? |
| AXE7 - 06.05.2011 |
Искать перестал, нашёл другое решение |
| Толик Востряков - 11.05.2011 |
Похоже я ошибся. Такого форка действительно нет. Удалил из поста этот текст. |
| Levik - 04.08.2011 |
Спасибо.. В дополнение к "своему" интерфейсу SPHINX предоставляет интерфейс для подключения к нему как к Mysql =) |