Использование Sphinx в сложных задачах поиска

    Использование Sphinx в сложных задачах поиска

    Sphinx – это поисковый сервер / движок. Sphinx предназначен для практически всех поисковых задач. Он может искать по тексту по релевантности, умеет искать склоняемые слова («котят» – «котята»), масштабируется горизонтально и имеет удобный интерфейс. Мне недавно пришлось изучить данный комбайн для одной весьма специфической задачи.

    Постановка задачи

    котят часто продают...

    На самом деле, в проекте много сложных для объяснения вещей (не совсем полное техническое задание занимает под 500 страниц 12-м шрифтом), но в сильно упрощённом виде всё выглядит следующим образом:

    Имеется Classified система - система с объявлениями различных категорий. У всех объявлений одной категории имеется определённый набор полей различных типов. Набор полей изменяется из административной панели.

    Например, для объявлений раздела “Авто - Легковые авто” у нас имеется следующий набор полей:

    • Марка
    • Модель
    • Тип кузова
    • ...

    Для объявлений раздела “Недвижимость - Квартиры - Продажа” поля такие:

    • Область
    • Город
    • Район
    • ...

    Необходимо реализовать все возможные операции над объявлениями и, самое главное, их поиск.

    Почему именно Sphinx?

    Без Sphinx тут только костыли...

    Я много, много думал и нашел всего 2 варианта хранения значения полей - сериализованные данные (JSON / XML / ...) в общей таблице объявлений или “кучка таблиц”. На первых порах, когда мы затевали этот проект, я не нашел ни одного варианта полноценного поиска по сериализованным данным (возможно, я просто плохо искал) и нам пришлось реализовывать “костыльный” вариант №2 - создавать по таблице значений для каждой категории, но с появлением в Sphinx (с версии 2.2.x) возможности поиска по JSON столбцам решено было перевести поиск на Sphinx.

    Кроме того, у Sphinx есть определённые преимущества:

    • Sphinx быстрый и стабильный.
    • Sphinx лёгкий в освоении. Официально рекомендуемым протоколом работы со Sphinx есть SphinxQL - протокол на базе SQL. Полнотекстовый поиск, поиск по JSON и даже манипуляции с RealTime индексами не составляют особого труда тем, кто уже знает SQL.
    • Sphinx масштабируемый. Если в процессе роста проекта, загруженность поискового сервера будет подходить к пиковой - вы спокойно можете поднять поисковый кластер из нескольких машин и распределить нагрузку.
    • Sphinx популярный. Следовательно, в сети полно документации, проект постоянно развивается и его уверенно можно использовать в коммерческих продуктах.
    • Sphinx кроссплатформенный. У нас машины разработчиков работают, в основном, на Windows, а на сервере установлен Debian. Однако подобный “зоопарк” по зубам нашему монстру.

    Итак, решено - будем использовать Sphinx. Так с чего же начать?

    Установка Sphinx на Debian

    В репозиториях Debian версии Sphinx обновляются достаточно редко, поэтому нам придётся устанавливать пакет вручную.

    Шаг 1. Качаем отсюдава (http://sphinxsearch.com/downloads/release/). необходимый нам пакет.

    Шаг 2. Устанавливаем библиотеки, необходимые для Sphinx:

    $ sudo apt-get install mysql-client unixodbc libpq5

    Шаг 3. Устанавливаем Sphinx из скачанного .deb пакета:

    $ sudo dpkg -i sphinxsearch_2.3.1-beta-0ubuntu11~trusty_amd64.deb

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

    Настройка Sphinx

    Все настройи хранятся в файле sphinx.conf.

    Исходная таблица

    Я сильно упростил таблицу

    Получение исходных данных

    Конфигурация обычно начинается с секций source. Эти секции описывают данные, которые мы будем тянуть с SQL сервера.

    Тут мы описываем тип подключения (mysql), параметры подключения, SQL запросы (самый главный - sql_query - для выборки данных).

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

    1. source src_classified_offers{
    2.     type   		 = mysql
    3.     sql_host   	 = 127.0.0.1
    4.     sql_user   	 = *******
    5.     sql_pass   	 = *******
    6.     sql_db   	 = classified_test
    7.     sql_port   	 = 3306    # optional, default is 3306
    8.     #pre-query
    9.     sql_query_pre   	 = SET NAMES utf8
    10.     # main document fetch query
    11.     sql_query   	 = \
    12.    	 SELECT `id`,`catid`,`vals`, \
    13.    	 `price`,`currency`, \
    14.    	 `name`,`description`, \
    15.    	 UNIX_TIMESTAMP(`published_from`) as published_from_ts \
    16.    	 FROM classified_ads \
    17.    	 WHERE ((`published`=1)AND(`id`>=$start)AND(`id`<=$end))
    18.  
    19.     sql_query_range   	 = SELECT MIN(id),MAX(id) FROM classified_ads
    20.     sql_range_step   	 = 1000
    21.     sql_attr_uint   	 = catid
    22.     sql_attr_json   	 = vals
    23.     sql_attr_float   	 = price
    24.     sql_attr_uint   	 = currency
    25.     sql_attr_timestamp    = published_from_ts    
    26.     sql_ranged_throttle    = 0
    27.     }

    Параметры sql_query_range, sql_range_step и sql_ranged_throttle описывают “выборку частями” из нашей таблицы. Поскольку таблица будет большая, теоретически (в процессе выборки большого количества данных) нас может выбить по таймауту. Запросы частями решают данную проблему.

    Как видите, в конце секции source, идёт описание типов некоторых полей. Например, sql_attr_json - это значения наших полей. Все неописанные поля добавляются в полнотекстовый индекс.

    Выбор типа индекса

    Далее нам необходимо создать секцию индекса. В Sphinx есть 2 типа индекса - обычный и realtime. Давайте выберем обычный индекс. Он проще в обслуживании и более производительный (минус - обновляется с запаздыванием).

    1. index classified_offers{
    2.     source   		 = src_classified_offers
    3.     path   		 = /data/sphinx/classified_offers/
    4.     docinfo   		 = extern
    5.     dict   		 = keywords
    6.     mlock   		 = 0
    7.     morphology   	 = stem_enru
    8.     min_word_len   	 = 1
    9.     html_strip   	 = 0
    10.     }

    Стоит отметить, что директория path должна существовать на момент запуска демона.

    Запуск Sphinx и поиск

    Перед стартом сервиса нам необходимо проиндексировать данные. При изменении структуры (добавили столбец в индекс или поменялся тип) необходимо вначале остановить сервис, а затем запускать полное индексирование командой:

    1. $ sudo service sphinxsearch stop
    2. $ indexer --all

    Затем запускаем демон:

    1. $ sudo service sphinxsearch start

    Если ничего не изменилось - просто вызываем переиндексацию данных без остановки сервиса. Данную команду следует запихнуть в cron для поддержания валидных данных:

    1. $ indexer --all --rotate

    Подключение к серверу Sphinx в PHP (мы используем протокол SphinxQL) ничем не отличается от SQL. Вот только порт придётся использовать нестандартный (обычно 9306).

    Для поиска по JSON указываем поле через точку. Например:

    1. SELECT values.fuel_type from classified_ads where (values.fuel_type=4);

    Для полнотекстового поиска используется ключевое слово MATCH:

    1. SELECT * FROM classified_ads WHERE MATCH('my document');

    Вот и всего-то.

    Комментарии

    Пока еще никто не комментировал эту запись. Вы можете быть первым!

    Adding comments is temporarily disabled for unregistered users.

    Go Top