[Из песочницы] Sphinx distribute. Ускоряем поиск

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

Одна из неприятных особенностей Sphinx’а — очень скудная информация на русском языке. Удивишись тем, что тема распределения нагрузки не была затронута, решил поделиться данным решением на Хабре.

Цель: повысить производительность Sphinx путём разделения нагрузки на несколько потоков.
Решение: разделить индексы и указать в конфигурации количество потоков.

Потоки выполнения


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

searchd
{
    ...
    dist_threads = 4
    ...
}


Директива обозначает максимальное количество потоков для обработки запроса. Значение по умолчанию — 0, что подразумевает неиспользование распаралелливания.

Разделение индексов


Далее, разделим индексы, чтобы каждый поток обрабатывал свой интервал записей. Другими словами: допустим, в нашей таблице 1 000 000 записей и четыре потока. Необходимо, чтобы каждый поток обработал 1 000 000 / 4 = 250 000 записей, чтобы в дальнейшем Sphinx получил результаты работы этих потоков и выдал наиболее релевантный результат. Логично, что четыре потока обрабатывающие по 250 000 записей быстрее справятся с работой, чем один поток, обрабатывающий 1 000 000 записей почти в четыре раза.

Предположим, что у нас есть некоторый source и index:

source books {
    type = mysql
    sql_query = SELECT id, name FROM tb_books
}

index books {
    source = books
    min_infix_len = 3
}


Для примера оставим директиву min_infix_len.
Чтобы разделить индекс на четыре части, создадим четыре source с ограниченными интервалами записей и назначим им по index'у:

source books_base {
    type = mysql
}
source books0: books_base {
    sql_query = SELECT id, name FROM tb_books WHERE id % 4 = 0
}
source books1: books_base {
    sql_query = SELECT id, name FROM tb_books WHERE id % 4 = 1
}
source books2: books_base {
    sql_query = SELECT id, name FROM tb_books WHERE id % 4 = 2
}
source books3: books_base {
    sql_query = SELECT id, name FROM tb_books WHERE id % 4 = 3
}

index ind_books_base {
    min_infix_len = 3
}
index ind_books0: ind_books_base {
    source = books0
}
index ind_books1: ind_books_base {
    source = books1
}
index ind_books2: ind_books_base {
    source = books2
}
index ind_books3: ind_books_base {
    source = books3
}

index ind_books {
    type = distributed
    local = ind_books0
    local = ind_books1
    local = ind_books2
    local = ind_books3
}


Самый простой способ разделить таблицу на приблизительно равные части — указать в запросе кратность id записи, однако это не является единственным способом. Как вариант, можно использовать директиву sql_query_range, однако в моем случае этот метод не подошел в связи с неоднородностью распределения id-шников записей по таблице.

Хорошей практикой будет указать прародителей index'ов и sourc'ов, чтобы наследоваться от них и вынести в них некоторые повторяющиеся директивы. В данном случае, я вынес в них директивы type и min_infix_len.

Чтобы иметь некоторый индекс, к которому можно было обратиться за результатами, мы создали индекс ind_books с типом distributed в директивах local которого указали имена индексов, результаты которых необходимо получить.

Delta


Если вы используете delta индекс то самым простым решением будет мержить его с одним из полученных индексов.

Однако, в этом случае, необходимо иметь ввиду, что если delta окажется слишком большой, то выбранный индекс, с которым мы мержим её, окажется заметно больше остальных, что может негативно сказаться на производительности. Чтобы предотвратить это, оптимальнее всего мержить её со всеми индексами поочередно.

В конечном итоге, использование этого метода позволило мне сократить время запросов от 2 до 10 раз.

© Habrahabr.ru