Введение
Как известно, на многих сайтах/веб-приложениях необходимо тем или иным образом реализовать поиск. Все хотят быстрый и качественный поиск. Разработчики помимо всего прочего хотят, чтобы поисковик был прост в установке и использовании. Так как речь идет о django, то перед нами встает ряд ограничений в реализации поиска (при условии, что в сутках 24 часа и дедлайны никто не отменял). Предлагаю вашему вниманию небольшой туториал о том, как поставить и максимально безболезненно интегрировать в django проект такой мощный поисковик, как apache solr. Всех заинтересованных прошу под кат.
Наши грабли
В прошлом у нас был не очень приятный опыт работы с django-sphinx. Сам sphinx отличный поисковик, django-sphinx тоже замечательная библиотека, но после django 1.3 приходится немного танцевать с бубном, чтобы завести весь этот стек. Но даже после того как все это настроено и работает остается ощущение, что все это лежит находится где-то там, отдельно от проекта, со своими индексами по крону и конфигами, не связанными с моделями на проекте. Да, там есть автогенератор конфигов, но завести его под 1.4+ не удалось, да и после каждой генерации пришлось бы править конфиги самого поисковика (searchd). В целом поиск django-sphinx работает, причем быстро и качественно, но поддерживать это не очень удобно, как и интегрировать в проект.
Как мы нашли для себя иголку в haystack
Вот настал черед нового проекта. И снова встал вопрос выбора поисковика. От django-sphinx мы сразу же отказались и стали искать альтернативное решение. Первая библиотека, на которую мы обратили внимание, это django-haystack, что неудивительно, т.к. это самая популярная библиотека для интеграции поисковиков в django. Посмотрев api, на ней и остановились. Нам обещали полную интеграцию с любым из предложенных поисковиков: elasticsearch, solr, whoosh, xapian. После беглого осмотра были приняты следующие решения:
- Whoosh исключили из-за отсутствия кучи мелких фич, некоторые из которых были необходимы для проекта. Да и поговаривают, что он по скорости проигрывает остальным вариантам.
- Xapian исключили после неудачной попытки интеграции стороннего бекенда.
- Осталось 2 варианта — elasticsearch и solr. Оба на java, оба на lucene, фичи практически идентичны. “Подбросив монетку” было решено использовать solr.
Больше граблей
Сразу скажу, что в данной статье описана в основном работа именно с solr, а не с haystack. Информацию по haystack можно найти в официальных доках, она хорошо изложена и дублировать ее здесь я не вижу смысла.
Библиотека выбрана, поисковик выбран. Пора приступать к работе. Первой же ошибкой была установка из официального репа ubuntu 12.04. В официальном репе версия solr 1.4, хотя документация haystack рекомендует версию 3.5+ во избежание оказий. Решили ставить сами. После серии проб и ошибок пришли к решению, которое отлично сработало как на локалках разработчиков, так и на тестовом и боевом серверах. Вот примерная последовательность действий:
- Ставим системные пакеты jre и jetty:
apt-get install openjdk-7-jre-headless jetty
- Python либы:
pip install pysolr django-haystack
- Немного “темной магии” для глобальной установки самого solr’а:
#!/bin/sh #variables LIB_DIRECTORY="/opt/solr" CONF_DIRECTORY="/etc/solr" OLD_CONF_DIRECTORY="$LIB_DIRECTORY/example/solr/conf" #check if already installed if ( [ -d $LIB_DIRECTORY ] && [ -d $CONF_DIRECTORY ] ); then echo "solr is already installed" exit fi #install if not #download and unpack wget http://archive.apache.org/dist/lucene/solr/3.6.2/apache-solr-3.6.2.tgz tar -xvzf apache-solr-3.6.2.tgz #install mv apache-solr-3.6.2 $LIB_DIRECTORY mv $OLD_CONF_DIRECTORY $CONF_DIRECTORY ln -s $CONF_DIRECTORY $OLD_CONF_DIRECTORY #cleanup rm apache-solr-3.6.2.tgz echo "solr installed"
Конечно если вы не хотите ставить solr так “глобально”, то можно немного подкрутить скрипт и ставить в любую другую папку — solr работает сразу после разархивации без дополнительных танцев, просто мы хотели видеть конфиги в привычных местах и чтобы очередное “стороннее” приложение было там же, где и остальные.
В общем уже можно запускать и все будет работать:
cd /opt/solr/example/ && java -jar start.jar
Но не можем же мы так все оставить. Как же конфиги, переиндексация и нормальный запуск? Давайте по порядку.
Конфиги
В данном контексте нас интересует 2 конфига:
/etc/solr/schema.xml
— сама схема данных и прочая информация для индексации и поиска, связанная непосредственно с нашими данными. Этот конфиг необходимо обновлять каждый раз, когда вы меняете индексы у себя в проекте./etc/solr/solrconfig.xml
— настройки самого solr’а, т.е. модули, handler’ы и прочие настройки, которые напрямую не касаются данных. Этот конфиг вам может пригодиться, если вам нужны дополнительные фичи solr’а.
После обновления любого из конфигов необходимо ребутать solr, чтобы изменения вступили в силу.
Для генерации schema.xml haystack поставляет замечательную команду build_solr_schema. Но у нее есть небольшой минус — при ее использовании нужно помнить куда надо класть конфиг, т.к. она либо принтит конфиг в консоль, либо в файл, если он указан. Можно немного поправить дело, если сделать собственную команду, полную копию build_solr_schema, просто указать дефолтное значение имени файла (в нашем случае это /etc/solr/schema.xml). Таким образом мы немного упростили жизнь разработчикам, ведь теперь достаточно только запустить эту команду и конфиг будет обновлен.
Переиндексация
В нашем случае есть 2 варианта как поддерживать актуальность индексов:
- “Классический вариант” — полная переиндексация раз в N минут. Единственный плюс такого подхода, который приходит в голову, это его простота. Минусов немного больше: при большой базе это долго, между переиндексациями данные могут стать неактуальными.
- “Атомарная переиндексация” — переиндексация каждого объекта по сигналам post_save, post_delete и т.д. Главным плюсом очевидно будет скорость независимо от размера бд. Немаловажно, что при таком подходе весь контроль переиндексации находится “на глазах” в коде проекта, а не в каком-то там кроне или в лучшем случае таске celery. Но и тут не без минусов: неактуальность данных при неправильной реализации и оверхед при вызове сигналов с переиндексацией. Последний устраняется вынесением в асинхронный таск (в нашем случае celery).
Мы выбрали второй вариант (никто не запрещает выбрать оба сразу) и для реализации воспользовались простым миксингом для моделей и парой тасков:
Миксин
class RefreshIndexMixin(object):
#index - соответствующий индекс модели, например PostIndex или CarIndex
def update_index(self, index):
current_app.send_task('project.tasks.update_index', args=[self, index])
def remove_index(self, index):
current_app.send_task('project.tasks.remove_index', args=[self, index])
В модели соответственно достаточно вызвать update_index или remove_index в соответствующем сигнале.
Таски
from celery import shared_task
@shared_task
def update_index(obj, index):
index().update_object(obj)
@shared_task
def remove_index(obj, index):
index().remove_object(obj)
Нормальный запуск.
Мы повсеместно используем supervisor, поэтому в нашем случае долго думать не пришлось. Вот пример конфига для локалки/тестового сервера:
[program:solr]
command=java -jar start.jar
directory=/opt/solr/example/
stderr_logfile=/var/log/solr.error.log
stdout_logfile=/var/log/solr.log
autorestart=true
Почему именно для локалки/тестового? Потому что solr “из коробки” идет с собственной админкой, где можно посмотреть индексы, настройки и т.д., а так же производить всякие манипуляции с этими данными. На боевом сервере вы скорее всего захотите отказаться от такой фичи, поэтому вот команда запуска только на localhost’е на дефолтном порте:
command=java -Djetty.host=127.0.0.1 -Djetty.port=8983 -jar start.jar
Итог
Что мы имеем в сухом остатке:
- мощный поисковый движок, который отлично интегрирован с django проектом
- атомарное индексирование
- легкое изменение любых настроек в рамках проекта
- целый ворох дополнительных фич (faceting, more like this и т.д.), которые действительно работают
За то время, что мы используем эту связку, она показала себя отличнейшим образом со всех сторон. Пользователи и заказчик довольны быстрым и адекватным поиском на сайте. Для разработчиков наличие такого java-монстра практически незаметно, т.к. им нужны только индексы, пара команд manage.py и перезагрузка solr’а через supervisor.
Автор: PukeCloud