Як пришвидшити роботу із plain index у Manticore Search?


Стикнулися із проблемою: повнотекстовий пошук дуже довго проходив через великий індекс.

Проблема пов’язана із тим, що “проходження”, по одному “простому індексу”(plain index) виконується із використанням одного ядра, у той час, коли інші ядра “простоюють”.
У офіційній документації знаходимо що існує “розподілений індекс”(distributed index), який утилізує стільки ядер, скільки вказано у директиві dist_threads.
UPD. Директива dist_threads від 2020 року застаріла, на заміну пришла директива threads, яка за замоченням відповідає кількості ядер процесора.

Перейдемо до демонстрації, як це працює.
Наприклад ви маєте “простий індекс” такого плану:

source src_products:connect_database
{
        sql_query_pre = SET CHARACTER_SET_RESULTS=utf8
        sql_query_pre   = SET GROUP_CONCAT_MAX_LEN = 2000
        sql_range_step      = 1000;
		sql_query_range     = SELECT min(id), max(id) 
		
		sql_query               = \
                SELECT t1.id AS product_id, \
            t2.id AS supplier_id, \
            count(distinct t1.id) as product_count, \
            t1.product_count as total_product_count, \
            unix_timestamp(t2.supplier_start_date) AS date_sale_start, \
            unix_timestamp(t2.date_modified) AS date_modified, \
            unix_timestamp(t2.sale_end_date) AS date_end_sale, \
            unix_timestamp(t2.created_at) AS created_at, \
            t1.title AS product_title, \
            t1.description AS product_description, \
            t1.title_en AS product_title_en, \
            t1.description_en AS product_description_en, \
            t2.title, \
            t2.description, \
            t2.title_en, \
            t2.description_en, \
            t2.status_id as status_id, \
            t2.identifier AS supplier_identifier, \
            t2.name AS supplier_name, \
            t2.id AS supplier_id, \
                FROM products t1 \
            inner join supplier t2 on t2.id = t1.supplier_id \
        WHERE t1.id >= \$start AND t1.id <= \$end AND t2.enabled = 1 \
        GROUP BY t1.id
		
		sql_field_string = product_title
		sql_field_string = product_description
		sql_attr_uint = product_id
		sql_attr_uint = supplier_id
		sql_attr_multi = uint code_id from query; \
			select t1.id as id, t3.codes_id as code_id \
			from products t1 \
			inner join items t2 on t1.product_id = t2.product_id \
			inner join codes_item t3 on t2.id = t3.item_id
}

index products
{
    source   = src_products
    path   = /var/lib/manticore/data/src_products
    min_stemming_len    = 3
    min_word_len        = 3
    phrase_boundary     = ., ?, !, ^, U+2026
    min_infix_len = 3
}

Рішення проблеми:

Нам необхідно розбити один великий “простий індекс” на декілька(бажано за кількістю ядер) та об’єднати їх у “розподілений індекс”.
Можемо взяти сумарну кількість записів розділити порівну й описати декілька сорсів із якимось офсетом.
Але що робити якщо таблиця постійно наповнюється та кількість записів змінюється? А ще записи можуть видалятися.
Тому цю проблему ми віддамо на рішення самої БД, щоб вона завжди нам віддавала рівні частини звідки починати й де закінчувати для кожного шматочка “простого індексу”.
Тут на допомогу приходить модифікація нашого sql_query_range, який вказує звідки починати й де закінчувати вибірку для побудови індексу.

Наприклад, щоб поділити на 3 рівні частини:

sql_query_range     = SELECT min(S.ID), max(S.ID) FROM \
						  ( \
							SELECT L.ID, \
							  @RN:=@RN+1 ROWNUMBER, \
							  (SELECT ROUND(COUNT(*)*0) FROM products) startN, \          # вказуємо звідки починаемо, для другого індексу буде: COUNT(*)*0.334. Для останнього: COUNT(*)*0.668
							  (SELECT ROUND(COUNT(*)*0.334) FROM products) stopN \ 		  # де закінчуємо, для другого індексу буде: COUNT(*)*0.668. Для останнього: COUNT(*)
							FROM products L \
							  CROSS JOIN (SELECT @RN:=0) R \
							ORDER BY L.ID \
						  ) S \
						WHERE S.ROWNUMBER >= S.startN and S.ROWNUMBER <= S.stopN

Далі розмножуємо опис джерела(source), де змінюємо назву, та змінюємо sql_query_range.

source src_products2:connect_database
{
        sql_query_pre = SET CHARACTER_SET_RESULTS=utf8
        sql_query_pre   = SET GROUP_CONCAT_MAX_LEN = 2000
        sql_range_step      = 1000;
        sql_query_range     = SELECT min(S.ID), max(S.ID) FROM \
								  ( \
									SELECT L.ID, \
									  @RN:=@RN+1 ROWNUMBER, \
									  (SELECT ROUND(COUNT(*)*0.334) FROM products) startN, \
									  (SELECT ROUND(COUNT(*)*0.668) FROM products) stopN \
									FROM products L \
									  CROSS JOIN (SELECT @RN:=0) R \
									ORDER BY L.ID \
								  ) S \
								WHERE S.ROWNUMBER >= S.startN and S.ROWNUMBER <= S.stopN
		...
    ...
    ...		
}

Розмножуємо опис index:

index products2 
{
	source   = src_products2
    path   = /var/lib/manticore/data/src_products2
    min_stemming_len    = 3
    min_word_len        = 3
    phrase_boundary     = ., ?, !, ^, U+2026
    min_infix_len = 3
}

І накінець описуємо “розподілений індекс”(distributed index):

index products_distributed
{
    type = distributed
    local = products
    local = products2
    local = products3
}

Перезапускаємо індексацію, перезагружаємо сервіс Manticore Search, перевіряємо чи всі данні проіндексувались вірно.
Ну і також необхідно буде змінити у конфігах вашого аплікейшену назву індексу: products -> products_distributed.

Післямова

Таким чином ми розділили один звичайний індекс, на декілька, та об’єднали їх до розподіленого індексу.
Що дозволить ядру Manticore Search, використовувати більше ядер для пошуку по індексу.

Безумовно ця історія створює одну неприємну річ, це обслуговування джерел(source) для індексів, при зміні одного, необхідно виконати зміни у кожному джерелі.
Але приріст продуктивності коштує цього. Також для зміни конфігурації можливо використовувати різні інструменти, наприклад Ansible.
Або створити Docker образ, конфігурацію manticore закинути до Git, та перебудовувати образ в процесі вашого CI/CD пайплайну.


Leave a Reply

Your email address will not be published. Required fields are marked *