Sphinx – настоящее быстрого поиска

by Larin

Интро

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

И вот при разработке очередного проекта стала проблема поиска. Таблицы в базе не маленькие (от 100 000 записей), да плюс искать еще нужно сразу по нескольким, так что вариант с обычным LIKE ‘%запрос%’ отпал сам собой.

Ранее я пользовался поисковым движком mnoGoSearch, но вспомнив корявость его API (иногда скаладывалось впечатление, что его писали индусы с 5-ю классами приходской школы) отбросил и этот вариант.

В итоге, осталось 2 варианта:

  1. написать поиск по основе построения своих индексов и хранить эти индексы, например, в BerkleyDB;
  2. не изобретать велосипед и воспользоваться одним из самых быстрых поисковых движков – Sphinx.

Я выбрал второй вариант :)

К слову, Sphinx – это проект нашего соотечественника Андрея Аксенова. Стоит заметить, что у Андрея хватает сил и времени не только постоянно улучшать проект, но и активно учавствовать в поддержке пользователей, опреативно отвечая на вопросы на англоязычном и русскоязычном форуме. За что ему огромное спасибо!

Установка

И так, приступим к практике. Установка стандартна и не вызывает никаких проблем.

Скачиваем архив с исходниками (последняя версия на момент написания статьи), распаковываем и устанавливаем:

# ./configure
# make
# make install (под root)

Под FreeBSD выполнив эти 3 команды я получил продукт готовый к работе. А вот в Debian пришлось еще минуту повозиться (правда, в первый раз это заняло полчаса :) и выполнить:

# sudo aptitude install libmysql++-dev libmysqlclient15-dev checkinstall

после этого все прекрасно заработало.

Подготовка

Теперь все готово для создания индекса (я предполагаю, что у вас есть некий проект с готовой БД) . Наша база имеет следующую структуру:

Структура БД

Будем искать по названию продукта, его описанию и тегам. Таким образом нам нужно создать индекс по этим полям. Для этого создадим 2 каталога /home/larin/data/ – тут будем хранить файлы индекса и /home/larin/log/ – тут логи и конфигурационный файл /home/larin/sphinx.conf

sphinx.conf



source product
{
        type = mysql
        sql_host = localhost
        sql_user = test
        sql_pass = ******
        sql_db = product
        sql_port = 3306

        sql_query_pre = SET NAMES cp1251
        sql_query_pre = SET CHARACTER SET cp1251

        sql_query = SELECT p.product_id, p.producе_title, p.product_description, t.tag_name FROM `product` p LEFT JOIN `product_tag` pt ON pt.product_id = p.product_id LEFT JOIN `tag` t ON pt.tag_id = t.tag_id

        sql_query_info = SELECT * FROM `product` WHERE `product_id` = $id
        sql_ranged_throttle = 0
}

index product
{
        source = product
        path = /home/larin/data/product
        docinfo = extern
        mlock = 0
        morphology = stem_enru
        min_word_len = 2
        charset_type = sbcs
        charset_table = 0..9, A..Z->a..z, _, a..z, U+A8->U+B8, U+B8, U+C0..U+DF->U+E0..U+FF, U+E0..U+FF
        min_infix_len = 2
        enable_star = 1
}

indexer
{
        mem_limit = 32M
}

searchd
{
        address = 127.0.0.1
        port = 3312
        log = /home/larin/log/searchd.log
        query_log = /home/larin/log/query.log
        read_timeout = 5
        max_children = 30
        pid_file = /home/larin/log/searchd.pid
        max_matches = 1000

}

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

Морфология

Spninx поддерживает индексацию (а следовательно и поиск) с учетом морфологии русского и английского языка. Поддержка морфологии реализована при помощи стемминга.

Стемминг – это процесс ведущий к выделению основы слова из сложных словоформ.

Вот строки в конфиге отвечающие за конфигурацию:


#подключение русского и английского стемминга
morphology = stem_enru
#задание таблицы символов, данная настройка для Windows-1251
charset_type = sbcs
charset_table = 0..9, A..Z->a..z, _, a..z, U+A8->U+B8, U+B8, U+C0..U+DF->U+E0..U+FF, U+E0..U+FF

#важно отметить, что в Sphinx прификсы считаются частью инфикса, т.е. указав индексацию по инфиксам мы автоматически индексируем и по префиксам
min_infix_len = 2

#так должно быть :) Почему? Читайте документацию ;)
enable_star = 1

Индексация

Запускаем создание индекса:
# indexer –config /home/larin/sphinx.conf –all

Все, индекс создан! Давайте искать!

Поиск

Для отладки и проверки работоспособности индекса подойдет утилита search. Пример использования:
# search –config /home/larin/sphinx.conf искомая фраза

А вот для реальной работы, нужно запустить демон сфинкса – searchd:
# searchd –config /home/larin/sphinx.conf

Все, демон успешно стартовал (кстати, не плохо было бы добавить его в автозагрузку :) , и теперь мы с ним можем работать из наших PHP-скриптов, через официальное API. Необходимая библиотека sphinxapi.php находится внутри скачанного архива к каталоге api.

Пример скрипта

<?php
require ( "sphinxapi.php" );
//....

$string = @$_GET['string'];
$sphinx = new SphinxClient();
$sphinx->SetServer('localhost', 3312);

//Совпадение любого из слов
$sphinx->SetMatchMode(SPH_MATCH_ANY);

//Результаты отсортированы по релевантности
$sphinx->SetSortMode(SPH_SORT_RELEVANCE);

//Задаем полям веса, для подсчета релевантности
$w = array ('product_title' => 20, 'product_description' => 10, 'tag_name' => 5);
$sphinx->SetFieldWeights($w);

$result = $sphinx->Query($string, '*');
if ($result && is_array($result['matches']))
{
 //Получаем массив ID найденых товаров
 $ids = array_keys($result['matches']);

//А теперь выводим эти товары отсортированные по релевантности
 $id_list = implode(',', $ids);
 $sql = sprintf('SELECT * FROM `product` WHERE `product_id` IN (%s) ORDER BY FIELD(`product_id`, %s)', $id_list, $id_list);

//Все далее выполняем этот запрос и наслаждаемся результатами
}
//....

?>

Итого

Вот и все. И мы в очередной раз (как и в случае с UML и кэшированием) убедились, что все гениальное просто :)