Sphinx: delta-индексы и несколько серверов поиска

в 7:32, , рубрики: mysql, sphinx, Веб-разработка, системное администрирование, метки: ,

Более-менее большой проект рано или поздно приходит к необходимости полнотекстового поиска по контенту.
Для этой цели был придуман поисковый движок Sphinx.

Когда база становится большой либо индексов много переиндексация начинает занимать довольно длительное время, что может иметь разные негативные последствия для проекта. В этот момент стоит задуматься об использовании delta-индексов.
Автор столкнулся с этой необходимостью в тот момент, когда переиндексация начала занимать более часа.

Но это все подробно описано в документации и делается довольно просто:

source src_mysql
{
    type                = mysql
    sql_host            = localhost
    sql_user            = sphinx
    sql_pass            = secret
    sql_range_step      = 1000
}

source src_news : src_mysql
{
    sql_db		= project

    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_pre       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id) FROM news
    sql_query_range     = SELECT MIN(id), MAX(id) FROM news
    sql_query = SELECT id as news_id, title, content FROM news WHERE id>=$start AND id<=$end
}

source src_news_delta : src_news
{
    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_range     = 
    sql_query           = SELECT id as news_id title, content FROM news 
                            		WHERE id > ( SELECT max_value FROM sph_counter WHERE source = 'src_news')

    sql_query_post       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id) FROM news
}

Теперь рассмотрим случай когда индексирующих серверов несколько. Причин для этого может быть несколько, но появляется проблема: один сервер произведя индексацию переписывает в БД значение последнего ID. При запуске индексации на следующем сервере в выборку не попадут записи проиндексированные предыдущим сервером. Возникают «пробелы» в индексах и поисковая выдача будет постоянно разная, причем будут упущены случайные результаты.

Что делать?

Нужно в таблице sph_counter дополнительно хранить ID индексирующего сервера.

Добавляем колонку hostname.

В итоге таблица будет иметь следующий вид:

CREATE TABLE IF NOT EXISTS `sph_counter` (
  `source` varchar(100) NOT NULL DEFAULT '',
  `max_value` bigint(20) NOT NULL,
  `hostname` varchar(50) NOT NULL DEFAULT '',
  PRIMARY KEY (`source`,`hostname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

* Обратите внимание на индекс, он необходим для корректной работы REPLACE INTO

Далее в зависимости от вашей конфигурации варианта 2:

1. Процесс Sphinx работает на том же сервере что и БД, настроена master-master репликация

В такой ситуации задача решается довольно просто: используем глобальную переменную MySQL — 'hostname'
Наша конфигурация приобретает следующий вид:

source src_news : src_mysql
{
    sql_db		= project

    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_pre       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id), @@hostname FROM news
    sql_query_range     = SELECT MIN(id), MAX(id) FROM news
    sql_query = SELECT id as news_id, title, content FROM news WHERE id>=$start AND id<=$end

}

source src_news_delta : src_news
{
    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_range     = 
    sql_query           = SELECT id as news_id, title, content FROM news 
                            		WHERE id > ( SELECT max_value FROM sph_counter WHERE source = 'src_news' AND hostname = @@hostname)

    sql_query_post       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id), @@hostname FROM news
}
2. Сервера MySQL и Sphinx — разные, запросы идут в master

В такой ситуации использовать глобальную переменную hostname уже не получится — мы будем всегда получать имя сервера, на котором работает БД.

Выход: будем использовать информацию о соединении и пользовательские переменные.

source src_news : src_mysql
{
    sql_db		= project

    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_pre       = SELECT @sphinx_instance:=IF(STRCMP(@sphinx_host:=SUBSTRING_INDEX(host,':',1),'localhost'),@sphinx_host,@@hostname) AS sphinx_instance 
FROM information_schema.processlist WHERE ID=connection_id();
    sql_query_pre       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id), @sphinx_instance FROM news
    sql_query_range     = SELECT MIN(id), MAX(id) FROM news
    sql_query = SELECT id as news_id, title, content FROM news WHERE id>=$start AND id<=$end
}

source src_news_delta : src_news
{
    sql_query_pre       = SET NAMES utf8
    sql_query_pre       = SET CHARACTER SET utf8
    sql_query_pre       = SELECT @sphinx_instance:=IF(STRCMP(@sphinx_host:=SUBSTRING_INDEX(host,':',1),'localhost'),@sphinx_host,@@hostname) AS sphinx_instance 
FROM information_schema.processlist WHERE ID=connection_id();
    sql_query_pre       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id), @sphinx_instance FROM news
    sql_query_range     = 
    sql_query           = SELECT id as news_id, title, content FROM news 
                            		WHERE id > ( SELECT max_value FROM sph_counter WHERE source = 'src_news' AND hostname = @sphinx_instance)

    sql_query_post       = REPLACE INTO sph_counter SELECT 'src_news', MAX(id), @sphinx_instance FROM news
}

Эта конфигурация будет работать в обоих случаях.

Автор: darken99

* - обязательные к заполнению поля