В первой части этой серии статей мы создали скелет приложения на laravel, создали необходимые модели и заполнили базу данными. Настало время перейти непосредственно к установке Sphinx'а и его настройке.

Устанавливать Sphinx будем из исходников. Можно конечно для установки использовать пакетный менеджер вашего linux дистрибутива, но не стоит расчитывать на актуальность версии. Находим ссылку для скачивания на странице загрузки, на момент написания статьи актуальной является версия 2.2.10.

cd /tmp
wget http://sphinxsearch.com/files/sphinx-2.2.10-release.tar.gz
tar zxf sphinx-2.2.10-release.tar.gz
cd sphinx-2.2.10-release
./configure
make
sudo make install

Если появляется ошибка ERROR: cannot find MySQL include files. - нужно установить пакет mysql-devel. В ubuntu он называется libmysqld-dev.

Перейдем к конфигурации нашего поискового движка. При установке Sphinx создает несколько демонстрационных файлов конфигурации. Они расположены в директории /usr/local/etc - файлы sphinx.conf.dist и sphinx-min.conf.dist. Первый файл содержит описание всех возможных настроек, второй содержит минимально необходимый набор настроек для работы движка, оба файла можно использовать как скелет для вашей конфигурации.

Конфигурационный файл Sphinx состоит из нескольких разделов:

  • source - описание источника данных для нашего индекса, параметров доступа к ним, атрибутов и правил их анализа.
  • index - описание нашего поискового индекса, настройки стемминга и мофрологии.
  • indexer - настройки индексатора - механизма, который преобразует наши сырые данные описанные в разделе source в данные, с которыми работает Sphinx в соответствии с настройками index.
  • searchd - настройки поискового демона - адреса, порта, логирования, ограничений памяти и времени, и др.
  • common - общие настройки Sphinx.

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

source:

  • type - обязательный параметр, определяет тип нашего источника. Может принимать значения mysql, pgsql, mssql, xmlpipe, xmlpipe2 и odbc.
  • sql_host, sql_user, sql_pass, sql_db, sql_port - параметры соединения с базой данных.
  • mysql_connect_flags - параметры соединения с базой данных mysql, значение будет передано в mysql_real_connect(). К примеру для включения сжатия данных нужно передать флаг 32.
  • sql_query_pre - запрос или запросы, которые будут выполнены после установки соединения с базой данных.
  • sql_query - основной запрос получения данных для индекса. Если записей много, то можно получать их частями, используя ranged query.
  • sql_query_range - запрос который должен возвращать минимальное и максимальное значение id индексируемых записей, парамаметр пригодится для ranged query.
  • sql_range_step - количество записей получаемых за одну итерацию в ranged query.
  • sql_attr_uint, sql_attr_bool, sql_attr_timestamp, sql_attr_float, sql_attr_multi и другие атрибуты - специфичные свойства записей, например такие как дата создания, активная или неактивная запись, список категорий и т.д.

Стоит отметить что раздел source конфигурации поддерживает наследование, и это весьма удобно, т.к. можно создать один source с настройками соединения с базой данных, и в остальных наследовать его, чтобы не дублировать одни и те же настройки. Итак, наши source разделы будут иметь следующий вид:

# Настройки соединения с базой данных
source mainSource
{
    type = mysql
    sql_host = your_database_host
    sql_user = your_database_login
    sql_pass = your_database_password
    sql_db = your_database_name
    sql_port = 3306
    
    mysql_connect_flags = 32
    
    sql_query_pre = SET NAMES utf8
    sql_query_pre = SET SESSION query_cache_type=OFF    
}
# source постов, который наследуется от mainSource
source postsSource : mainSource
{
    sql_query = \
        SELECT posts.id, posts.title, posts.content, UNIX_TIMESTAMP(posts.created_at) as date_add, posts.published, posts.views, posts.user_id \
        FROM posts \
        WHERE posts.id>=$start AND posts.id<=$end

    sql_query_range = SELECT MIN(id),MAX(id) FROM posts
	
    sql_range_step = 1000

    sql_attr_bool = published

    sql_attr_timestamp = date_add
    
    sql_attr_uint = user_id
    
    sql_attr_multi = uint category from query; \
        SELECT p.id, cp.category_id FROM posts AS p, category_post as cp WHERE cp.post_id = p.id;

}
# source категорий, тоже наследуется от mainSource
source categoriesSource : mainSource
{
    sql_query = \
        SELECT categories.id, categories.name,  UNIX_TIMESTAMP(categories.created_at) as date_add \
        FROM categories \
        WHERE categories.id>=$start AND categories.id<=$end

    sql_query_range = SELECT MIN(id),MAX(id) FROM categories
	
    sql_range_step = 5000

    sql_attr_timestamp = date_add

}

index:

  • type - опциональный параметр, индекс может быть трех типов - plain (disk), rt (real-time) и distributed. Подробнее про типы можно прочитать в документации.
  • source - название источника индекса.
  • path - пусть к файлам индекса (без расширения).
  • docinfo - тип хранения атрибутов индекса. Может принимать значения none, extern и inline. None уместен когда индекс не имеет атрибутов, при выборе inline атрибуты будут храниться в .spd файле вместе с документами, а при extern для них будет создан отдельный .spa файл.
  • dict - тип словаря, crc или keywords. crc лучше использовать когда не нужен поиск по подстрокам. Keywords быстрее работает с подстроками, поддерживает wildcard поиск и размер индекса получается в 3-10 раз меньше.
  • morphology - морфологический препроцессор. Препроцессор применяется к индексируемым словам чтобы заменить различные формы одного слова нормализованной формой. Sphinx имеет три вида морфологических препроцессоров: лемматизатор, стеммер и фонетические алгоритмы. Стоит отметить, что Sphinx имеет поддержку русского языка из коробки. Также имеется поддержка libstemmer.
  • stopwords - путь к файлу со списком стоп-слов (разделенных пробелами). Стоп-слова не учитываются при поиске.
  • wordforms - путь к файлу со списком словоформ, например "hypertext preprocessor > php".
  • min_word_len - минимальная длина индексируемого слова.
  • html_strip - параметр определяет нужно ли удалять html теги из входных данных.

Соберем конфигурацию наших индексов:

index postsIndex
{
    source = postsSource
    path = /path/to/index/file
    docinfo = extern
    dict = keywords
    mlock = 0
    morphology = stem_enru, soundex, metaphone
    min_word_len = 1
    html_strip = 1
}
index categoriesIndex
{
    source = categoriesSource
    path = /path/to/index/file
    docinfo = extern
    dict = keywords
    mlock = 0
    morphology = stem_enru, soundex, metaphone
    min_word_len = 1
    html_strip = 1
}

С индексатором все проще, из настроек оставим лишь ограничение памяти:

indexer 
{
    mem_limit = 32M
}

В настройках демона тоже все предельно понятно:

  • listen - адрес и порт или путь до unix сокета демона.
  • log - путь к файлу лога.
  • query_log - путь к файлу лога запросов.
  • read_timeout - тайм-аут чтения в секундах.
  • max_children - максимальное количество форков.
  • pid_file - путь к PID файлу, обязательный параметр.
  • workers - тип мультипроцессорного режима. None для выключения, все запросы будут выполняться синхронно один за одним. Fork и prefork - новый процесс будет форкнут для обработки запроса. И thread - когда для обработки запроса будет создан новый поток. Последний необходим для работы real-time индекса.
searchd
{
    listen = 127.0.0.1:9312
    log = /var/log/sphinx/searchd.log
    query_log = /var/log/sphinx/query.log
    read_timeout = 5
    max_children = 30
    pid_file = /var/log/sphinx/searchd.pid
}

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

/usr/local/bin/indexer --config /path/to/config/sphinx.conf --all
Sphinx 2.2.10-id64-release (2c212e0)
Copyright (c) 2001-2015, Andrew Aksyonoff
Copyright (c) 2008-2015, Sphinx Technologies Inc (http://sphinxsearch.com)

using config file '/path/to/config/sphinx.conf'...
indexing index 'postsIndex'...
collected 500 docs, 0.1 MB
collected 1500 attr values
sorted 0.0 Mvalues, 100.0% done
sorted 0.0 Mhits, 100.0% done
total 500 docs, 129885 bytes
total 0.049 sec, 2629570 bytes/sec, 10122.68 docs/sec
indexing index 'categoriesIndex'...
collected 30 docs, 0.0 MB
sorted 0.0 Mhits, 100.0% done
total 30 docs, 451 bytes
total 0.007 sec, 57554 bytes/sec, 3828.48 docs/sec
total 1509 reads, 0.000 sec, 0.1 kb/call avg, 0.0 msec/call avg
total 26 writes, 0.000 sec, 9.9 kb/call avg, 0.0 msec/call avg

Ошибок нет, все работает. Но запускать индексацию вручную не очень удобно. Можно сделать обновление индекса по расписанию. Категории обновляются или добавляются несколько реже, чем посты, поэтому индексировать их можно тоже реже, раз в три часа к примеру.

crontab -e

*/30 * * * *  /usr/local/bin/indexer --config /path/to/config/sphinx.conf --rotate postsIndex > /dev/null 2>&1
* */3 * * *  /usr/local/bin/indexer --config /path/to/config/sphinx.conf --rotate categoriesIndex > /dev/null 2>&1

Теперь осталось лишь запустить наш демон:

searchd --config /path/to/config/sphinx.conf
Sphinx 2.2.10-id64-release (2c212e0)
Copyright (c) 2001-2015, Andrew Aksyonoff
Copyright (c) 2008-2015, Sphinx Technologies Inc (http://sphinxsearch.com)

using config file '/path/to/config/sphinx.conf'...
listening on 127.0.0.1:9312
precaching index 'postsIndex'
precaching index 'categoriesIndex'                          
precached 2 indexes in 0.002 sec 

Кажется все работает, но давайте проверим. Для того, чтобы подключиться к поисковому демону из терминала нужно немного изменить его конфиг и добавить listen = localhost:9306:mysql41 в секцию searchd. Теперь можно использовать консольный mysql клиент:

mysql -h 127.0.0.1 -P 9306

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 2.2.10-id64-release (2c212e0)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * FROM postsIndex WHERE MATCH('quo');
+------+------------+-----------+---------+----------+
| id   | date_add   | published | user_id | category |
+------+------------+-----------+---------+----------+
|    4 | 1458403804 |         0 |       1 | 14,25,29 |
|    8 | 1458403804 |         1 |       1 | 7,16,23  |
|   22 | 1458403804 |         1 |       1 | 6,14,29  |
|   32 | 1458403805 |         1 |       1 | 7,23,29  |
|   40 | 1458403805 |         0 |       1 | 12,13,23 |
|   54 | 1458403806 |         1 |       2 | 6,13,26  |
|   67 | 1458403806 |         1 |       2 | 10,19,22 |
|   72 | 1458403806 |         1 |       2 | 7,8,16   |
|   91 | 1458403806 |         0 |       2 | 8,12,19  |
|  100 | 1458403806 |         1 |       2 | 7,13,20  |
|  107 | 1458403808 |         1 |       3 | 7,19,21  |
|  110 | 1458403808 |         1 |       3 | 1,11,19  |
|  117 | 1458403808 |         1 |       3 | 1,4,22   |
|  126 | 1458403808 |         1 |       3 | 12,15,27 |
|  150 | 1458403808 |         1 |       3 | 14,18,27 |
|  178 | 1458403809 |         1 |       4 | 9,13,22  |
|  185 | 1458403809 |         1 |       4 | 4,7,28   |
|  231 | 1458403811 |         0 |       5 | 11,19,23 |
|  241 | 1458403811 |         1 |       5 | 3,8,12   |
|  334 | 1458403814 |         1 |       7 | 1,8,11   |
+------+------------+-----------+---------+----------+
20 rows in set (0.00 sec)

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

Все исходники можно найти на github'е.