Индексация plain индексов и kill-list

Инфра

Есть Manticore версии:

Server version: 7.0.0 92c650401@25013002 (columnar 4.0.0 5aa8e43@25012409) (secondary 4.0.0 5aa8e43@25012409) (knn 4.0.0 5aa8e43@25012409) git branch manticore-7.0.0...origin/manticore-7.0.0

Сервера:
indexer - для индексации и доставки индексов на поисковые сервера
proxy - Manticore с настроенными agents distributed индексов
search nodes - поисковые сервера с plain индексами
PostgreSQL - источник данных для индексов

Конфигурация индексов

Есть индекс products с конфигурацией main + delta и chunks 16, т.е. 32 индекса: 16 main и 16 delta. Каждая delta под свой main.

Конфигурация индексов:

Summary
source products_main_0
{
    type = pgsql
    sql_host = ***
    sql_user = ***
    sql_pass = ***
    sql_db = ***
    sql_port = ***
    sql_query_pre = \
    INSERT INTO manticore_indexer (index_name, indexing_started_at) \
      VALUES ('products_main_0', NOW()) \
      ON CONFLICT (index_name) DO UPDATE SET indexing_started_at = EXCLUDED.indexing_started_at

    sql_query = \
    SELECT id, name, seo_slug as slug, main_image as image, price_retail, price_retail_min, marketing_statuses as marketing_status_list, \
      availability_status, availability_quantity, vendor_id, vat as nds, description, selling_text, \
      reasons_to_buy as reasons_to_buy_list, literature_work_publishing_year, EXTRACT(EPOCH FROM preorder_available_at) AS preorder_available_at, EXTRACT(EPOCH FROM released_at) AS released_at, \
      EXTRACT(EPOCH FROM created_at) AS created_at, EXTRACT(EPOCH FROM updated_at) AS updated_at, isbns, printing_page_count, printing_page_format, printing_copy_count, weight, height, width, length, \
      excerpts, additional_images, rating_average, rating_weight, rating_star, rating_count, review_count, \
      sales_stat_half_year_day_avg_quantity as purchase_stats_day_avg_count, \
      sales_stat_year_turnover_amount_without_vat, sales_stat_year_turnover_quantity, sales_stat_year_markup, \
      sales_stat_half_year_turnover_amount_without_vat, sales_stat_half_year_turnover_quantity, sales_stat_half_year_markup, \
      sales_stat_quarter_turnover_amount_without_vat, sales_stat_quarter_turnover_quantity, sales_stat_quarter_markup, \
      sales_stat_month_turnover_amount_without_vat, sales_stat_month_turnover_quantity, sales_stat_month_markup, \
      school_grade_id_list, school_subject_id_list, school_material_type_id_list, school_exam_id_list, \
      school_education_system_id_list, school_umk_id_list, school_umk_title_list as school_umk_title, school_exam_year_id_list, \
      school_purpose_id_list, seo_title, seo_description, main_category_id_list, category_id_list, tbk_id_list, ekn_id_list, \
      author_id_list, translator_id_list, illustrator_id_list, publisher_series_id, publisher_id, publisher_brand_id, \
      manufacturer_brand_id, literature_work_cycle_id, literature_work_cycle_volume_id, age_restriction, \
      printing_binding_id as binding_id, tag_id_list, product_set_id_list, author_full_name_list, publisher_series_name, \
      publisher_name, publisher_brand_name, manufacturer_brand_name, product_type_id, article_number_id, \
      stationery_brush_shape_id_list, stationery_brush_material_id_list, stationery_brush_number_id_list, \
      stationery_painting_technique_id_list, stationery_lead_hardness_id_list, stationery_format_id_list, \
      stationery_line_type_id_list, stationery_ink_color_id_list, stationery_lead_diameter_id_list, \
      stationery_case_shape_id_list, stationery_mechanism_type_id_list, stationery_diameter_id_list, \
      stationery_feature_id_list, stationery_colors_quantity_id_list, stationery_gender_id_list, stationery_length_id_list, \
      stationery_staple_number_id_list, stationery_stapler_number_id_list, stationery_material_id_list, \
      stationery_punched_sheets_quantity_id_list, stationery_pen_thickness_id_list, stationery_mounting_type_id_list, \
      stationery_pen_tip_shape_id_list, stationery_ink_base_id_list, \
      stationery_calculator_capacity_id_list, stationery_calendar_year_id_list, stationery_calendar_type_id_list, \
      stationery_calendar_subject_id_list, stationery_clasp_type_id_list, stationery_compartments_quantity_id_list, \
      stationery_cover_binding_id_list, stationery_package_type_id_list, stationery_cover_surface_id_list, \
      stationery_universal_id_list, stationery_sheets_quantity_id_list, stationery_color_id_list, stationery_volume_id_list, \
      comic_universal_id_list, comic_character_id_list, comic_genre_id_list, comic_series_id_list, comic_type_id_list, \
      comic_line_id_list, comic_section_id_list, comic_subject_id_list, game_player_quantity_id_list, \
      game_use_case_id_list, game_skill_id_list, game_audience_id_list, game_child_age_id_list, game_series_id_list, \
      game_duration_id_list, constructor_detail_quantity_id_list, constructor_nation_id_list, \
      constructor_equipment_type_id_list, souvenir_reason_id_list, souvenir_format_id_list, souvenir_set_quantity_id_list, \
      toy_type_id_list, toy_height_id_list, gift_hobby_id_list, gift_for_children_idea_id_list as gift_for_children_id_list, \
      gift_for_new_year_idea_id_list as gift_new_year_id_list, gift_section_id_list, gift_books_on_interest_id_list, \
      product_collection_id_list, active_shops_id_list, active_cities_id_list, active_shop_brands_id_list, all_shops_id_list, \
      all_cities_id_list, all_shop_brands_id_list, videos, attended_foreign_agents, \
      EXTRACT(EPOCH FROM synchronized_at) AS synchronized_at \
    FROM product \
    WHERE is_removed = 'f' AND id >= $start AND id <= $end AND (id % 16) = 0

    sql_query_range = \
    SELECT MIN(id), MAX(id) FROM product
    sql_range_step = 50000
    sql_query_post_index = \
    INSERT INTO manticore_indexer (index_name, indexing_ended_at) \
      VALUES ('products_main_0', (SELECT indexing_started_at FROM manticore_indexer WHERE index_name='products_main_0')) \
      ON CONFLICT (index_name) DO UPDATE SET indexing_ended_at = EXCLUDED.indexing_ended_at

    # attributes
    sql_attr_string = name
    sql_attr_string = slug
    sql_attr_string = description
    ...
        sql_attr_multi = uint all_shop_brands_id_list from field
    # attributes end
        
}

source products_delta_0: products_main_0
{
    sql_query_pre = \
    INSERT INTO manticore_indexer (index_name, indexing_started_at) \
      VALUES ('products_delta_0', NOW()) \
      ON CONFLICT (index_name) DO UPDATE SET indexing_started_at = EXCLUDED.indexing_started_at

    sql_query = \
    SELECT id, name, seo_slug as slug, main_image as image, price_retail, price_retail_min, marketing_statuses as marketing_status_list, \
      availability_status, availability_quantity, vendor_id, vat as nds, description, selling_text, \
      reasons_to_buy as reasons_to_buy_list, literature_work_publishing_year, EXTRACT(EPOCH FROM preorder_available_at) AS preorder_available_at, EXTRACT(EPOCH FROM released_at) AS released_at, \
      EXTRACT(EPOCH FROM created_at) AS created_at, EXTRACT(EPOCH FROM updated_at) AS updated_at, isbns, printing_page_count, printing_page_format, printing_copy_count, weight, height, width, length, \
      excerpts, additional_images, rating_average, rating_weight, rating_star, rating_count, review_count, \
      sales_stat_half_year_day_avg_quantity as purchase_stats_day_avg_count, \
      sales_stat_year_turnover_amount_without_vat, sales_stat_year_turnover_quantity, sales_stat_year_markup, \
      sales_stat_half_year_turnover_amount_without_vat, sales_stat_half_year_turnover_quantity, sales_stat_half_year_markup, \
      sales_stat_quarter_turnover_amount_without_vat, sales_stat_quarter_turnover_quantity, sales_stat_quarter_markup, \
      sales_stat_month_turnover_amount_without_vat, sales_stat_month_turnover_quantity, sales_stat_month_markup, \
      school_grade_id_list, school_subject_id_list, school_material_type_id_list, school_exam_id_list, \
      school_education_system_id_list, school_umk_id_list, school_umk_title_list as school_umk_title, school_exam_year_id_list, \
      school_purpose_id_list, seo_title, seo_description, main_category_id_list, category_id_list, tbk_id_list, ekn_id_list, \
      author_id_list, translator_id_list, illustrator_id_list, publisher_series_id, publisher_id, publisher_brand_id, \
      manufacturer_brand_id, literature_work_cycle_id, literature_work_cycle_volume_id, age_restriction, \
      printing_binding_id as binding_id, tag_id_list, product_set_id_list, author_full_name_list, publisher_series_name, \
      publisher_name, publisher_brand_name, manufacturer_brand_name, product_type_id, article_number_id, \
      stationery_brush_shape_id_list, stationery_brush_material_id_list, stationery_brush_number_id_list, \
      stationery_painting_technique_id_list, stationery_lead_hardness_id_list, stationery_format_id_list, \
      stationery_line_type_id_list, stationery_ink_color_id_list, stationery_lead_diameter_id_list, \
      stationery_case_shape_id_list, stationery_mechanism_type_id_list, stationery_diameter_id_list, \
      stationery_feature_id_list, stationery_colors_quantity_id_list, stationery_gender_id_list, stationery_length_id_list, \
      stationery_staple_number_id_list, stationery_stapler_number_id_list, stationery_material_id_list, \
      stationery_punched_sheets_quantity_id_list, stationery_pen_thickness_id_list, stationery_mounting_type_id_list, \
      stationery_pen_tip_shape_id_list, stationery_ink_base_id_list, \
      stationery_calculator_capacity_id_list, stationery_calendar_year_id_list, stationery_calendar_type_id_list, \
      stationery_calendar_subject_id_list, stationery_clasp_type_id_list, stationery_compartments_quantity_id_list, \
      stationery_cover_binding_id_list, stationery_package_type_id_list, stationery_cover_surface_id_list, \
      stationery_universal_id_list, stationery_sheets_quantity_id_list, stationery_color_id_list, stationery_volume_id_list, \
      comic_universal_id_list, comic_character_id_list, comic_genre_id_list, comic_series_id_list, comic_type_id_list, \
      comic_line_id_list, comic_section_id_list, comic_subject_id_list, game_player_quantity_id_list, \
      game_use_case_id_list, game_skill_id_list, game_audience_id_list, game_child_age_id_list, game_series_id_list, \
      game_duration_id_list, constructor_detail_quantity_id_list, constructor_nation_id_list, \
      constructor_equipment_type_id_list, souvenir_reason_id_list, souvenir_format_id_list, souvenir_set_quantity_id_list, \
      toy_type_id_list, toy_height_id_list, gift_hobby_id_list, gift_for_children_idea_id_list as gift_for_children_id_list, \
      gift_for_new_year_idea_id_list as gift_new_year_id_list, gift_section_id_list, gift_books_on_interest_id_list, \
      product_collection_id_list, active_shops_id_list, active_cities_id_list, active_shop_brands_id_list, all_shops_id_list, \
      all_cities_id_list, all_shop_brands_id_list, videos, attended_foreign_agents, \
      EXTRACT(EPOCH FROM synchronized_at) AS synchronized_at \
    FROM product \
    WHERE is_removed = 'f' AND changed_at_timestamp >= $start AND changed_at_timestamp <= $end AND (id % 16) = 0

    sql_query_range = \
    SELECT (SELECT EXTRACT(EPOCH FROM data_relevant_to AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='products_main_0') min, \
      (SELECT EXTRACT(EPOCH FROM indexing_started_at AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='products_delta_0') max

    sql_range_step = 1000
    sql_query_post_index = \
    INSERT INTO manticore_indexer (index_name, data_relevant_to) \
      VALUES ('products_delta_0', (SELECT indexing_started_at FROM manticore_indexer WHERE index_name='products_delta_0')) \
      ON CONFLICT (index_name) DO UPDATE SET data_relevant_to = EXCLUDED.data_relevant_to

    sql_query_killlist = \
    SELECT id FROM product \
      WHERE changed_at_timestamp >= (SELECT EXTRACT(EPOCH FROM data_relevant_to AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='products_main_0')::int \
      AND changed_at_timestamp <= (SELECT EXTRACT(EPOCH FROM indexing_started_at AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='products_delta_0')::int AND (id % 16) = 0
    
}

Сам индекс собирается по частям с chunks и содержит свой диапазон с документами.

Индексация запускается по cron:

#Ansible: index-products
0 */1 * * * …
#Ansible: index-products-delta
*/1 * * * * …

main индексируется каждый час, а delta каждую минуту.

В источнике PostgreSQL в таблице product используются поля:
changed_at_timestamp - с timestamp измененной записи
is_removed - флаг отметки удаления документа, где t - удаленный, f - не удаленный.

Процесс индексации и доставки

Условно весь процесс состоит из этапов:

  • запуск cron под индексы
  • индексация
  • синхронизация на поисковые node
  • подготовка и запуск ротации

Индексация идет параллельно с ограничением количества chunks.
Синхронизация идет параллельно на все поисковые node.
Ротация запускается параллельно на все поисковые node.

Если запущена delta, то на этапе подготовки к ротации идет проверка на уже ротиванный main и delta пропускается, если main уже был ротирован в этот интервал времени (минута).

Проблема

На скриншоте представлен график по метрике количество документов в индексе: main + delta с одной из поисковых node (результат идентичен на всех).


Хронология событий:

1, 2, 3 delta отрабатывают на 0, 1 и 2 минуте часа и все хорошо.

Индексируются они с диапазоном от прошлой полной индексации main и применяются к еще текущему индексу main.

Затем на почти конце 2 минуты приходит ротация нового main и количество документов верное, совпадает с источником.

До полной индексации main количество документов не верное в индексе. Это проблема.

Далее на 3 минуте отрабатывает уже новая delta (с новым диапазоном на начала часа и до старта новой delta) и применяется к уже новому main.

И на этом этапе происходит странное - в индексе main подавляется большое количество документов, что быть не должно!

Это стабильно всегда повторяется в одинаковой последовательности, первая delta после полной индексации main убивает большое количество документов, оно разное.

delta при этом индексируется корректно, количество документов и ее kill-list возрастает от последней полной индексации до начала новой delta.

Ниже представлены метрики по количеству документов и количеству документов в kill-list в delta.
Последняя delta примененная до индекса main:

products_delta_0	start_time:	1751540401	end_time:	1751544121	count:	584			kill_list_count:	2035
products_delta_1	start_time:	1751540401	end_time:	1751544121	count:	614			kill_list_count:	2052
products_delta_2	start_time:	1751540401	end_time:	1751544121	count:	608			kill_list_count:	2006
products_delta_3	start_time:	1751540401	end_time:	1751544121	count:	608			kill_list_count:	2078
products_delta_4	start_time:	1751540440	end_time:	1751544123	count:	606			kill_list_count:	1887
products_delta_5	start_time:	1751540440	end_time:	1751544123	count:	583			kill_list_count:	1888
products_delta_6	start_time:	1751540440	end_time:	1751544123	count:	584			kill_list_count:	1954
products_delta_7	start_time:	1751540440	end_time:	1751544123	count:	607			kill_list_count:	1896
products_delta_8	start_time:	1751540464	end_time:	1751544124	count:	610			kill_list_count:	1776
products_delta_9	start_time:	1751540465	end_time:	1751544124	count:	602			kill_list_count:	1799
products_delta_10	start_time:	1751540465	end_time:	1751544124	count:	616			kill_list_count:	1759
products_delta_11	start_time:	1751540465	end_time:	1751544124	count:	598			kill_list_count:	1733
products_delta_12	start_time:	1751540486	end_time:	1751544126	count:	560			kill_list_count:	1655
products_delta_13	start_time:	1751540486	end_time:	1751544126	count:	550			kill_list_count:	1669
products_delta_14	start_time:	1751540487	end_time:	1751544126	count:	578			kill_list_count:	1649
products_delta_15	start_time:	1751540487	end_time:	1751544126	count:	628			kill_list_count:	1611

Итого: 9536 - количество документов в индексе delta, 29447 - количество документов в kill-list. Как правило количество совпадет.

И фактическое количество документов под каждый индекс delta:

collected	608
collected	608
collected	614
collected	584
collected	606
collected	585
collected	583
collected	608
collected	602
collected	610
collected	616
collected	598
collected	550
collected	560
collected	578
collected	628

Итого: 9538:

Первая delta примененная после индекса main:

products_delta_0	start_time:	1751544001	end_time:	1751544182	count:	30			kill_list_count:	30
products_delta_1	start_time:	1751544001	end_time:	1751544182	count:	35			kill_list_count:	35
products_delta_2	start_time:	1751544001	end_time:	1751544182	count:	36			kill_list_count:	36
products_delta_3	start_time:	1751544001	end_time:	1751544182	count:	33			kill_list_count:	33
products_delta_4	start_time:	1751544046	end_time:	1751544183	count:	25			kill_list_count:	25
products_delta_5	start_time:	1751544046	end_time:	1751544183	count:	30			kill_list_count:	30
products_delta_6	start_time:	1751544046	end_time:	1751544183	count:	24			kill_list_count:	24
products_delta_7	start_time:	1751544046	end_time:	1751544183	count:	33			kill_list_count:	33
products_delta_8	start_time:	1751544071	end_time:	1751544184	count:	20			kill_list_count:	20
products_delta_9	start_time:	1751544071	end_time:	1751544184	count:	23			kill_list_count:	23
products_delta_10	start_time:	1751544071	end_time:	1751544184	count:	27			kill_list_count:	27
products_delta_11	start_time:	1751544071	end_time:	1751544184	count:	23			kill_list_count:	23
products_delta_12	start_time:	1751544091	end_time:	1751544185	count:	29			kill_list_count:	29
products_delta_13	start_time:	1751544091	end_time:	1751544185	count:	29			kill_list_count:	29
products_delta_14	start_time:	1751544091	end_time:	1751544185	count:	26			kill_list_count:	26
products_delta_15	start_time:	1751544091	end_time:	1751544185	count:	19			kill_list_count:	19

Итого: 442 - количество документов в индексе delta, 442 - количество документов в kill-list.

И фактическое количество документов под каждый индекс delta:

collected	36
collected	33
collected	30
collected	35
collected	24
collected	25
collected	33
collected	30
collected	20
collected	27
collected	23
collected	23
collected	29
collected	19
collected	29
collected	26

Итого: 442

Видно, что количество документов в индексе и количество документов в kill-list корректное.

Но почему подавляется 25К документов с новой delta в уже новом индексе main непонятно.

Причем в самом процессе нет какого-либо наложения, гонки или чего-либо еще.

Ротация происходит выборочно только под требуемые индексы.

Уже всю голову сломали, но не ясно в чем проблема и почему Manticore так делает, что применяется не верный kill-list, который ведет к подавлению большого количества документов?

searchd.log во вложении за этот интервал.
searchd.log (96.4 KB)

И еще более свежий пример.

Последний индекс delta перед новым main:

products_delta_0	start_time:	1751547602	end_time:	1751551322	count:	4631			kill_list_count:	4631
products_delta_1	start_time:	1751547602	end_time:	1751551322	count:	4752			kill_list_count:	4752
products_delta_2	start_time:	1751547602	end_time:	1751551322	count:	4630			kill_list_count:	4630
products_delta_3	start_time:	1751547602	end_time:	1751551322	count:	4676			kill_list_count:	4676
products_delta_4	start_time:	1751547689	end_time:	1751551325	count:	4711			kill_list_count:	4711
products_delta_5	start_time:	1751547689	end_time:	1751551325	count:	4651			kill_list_count:	4651
products_delta_6	start_time:	1751547689	end_time:	1751551325	count:	4762			kill_list_count:	4762
products_delta_7	start_time:	1751547689	end_time:	1751551325	count:	4651			kill_list_count:	4651
products_delta_8	start_time:	1751547721	end_time:	1751551328	count:	4656			kill_list_count:	4656
products_delta_9	start_time:	1751547721	end_time:	1751551328	count:	4729			kill_list_count:	4729
products_delta_10	start_time:	1751547721	end_time:	1751551328	count:	4692			kill_list_count:	4692
products_delta_11	start_time:	1751547722	end_time:	1751551329	count:	4581			kill_list_count:	4581
products_delta_12	start_time:	1751547740	end_time:	1751551331	count:	4729			kill_list_count:	4729
products_delta_13	start_time:	1751547741	end_time:	1751551331	count:	4675			kill_list_count:	4675
products_delta_14	start_time:	1751547741	end_time:	1751551332	count:	4658			kill_list_count:	4658
products_delta_15	start_time:	1751547741	end_time:	1751551332	count:	4707			kill_list_count:	4707

Итого: 74891 - количество документов в индексе, 74891 - kill-list

Фактически индексировано:

collected	4676
collected	4752
collected	4631
collected	4630
collected	4711
collected	4651
collected	4651
collected	4762
collected	4656
collected	4729
collected	4692
collected	4581
collected	4729
collected	4675
collected	4658
collected	4707

Итого: 74891

Первый индекс delta после нового main:

products_delta_0	start_time:	1751551202	end_time:	1751551382	count:	2			kill_list_count:	2
products_delta_1	start_time:	1751551202	end_time:	1751551382	count:	6			kill_list_count:	6
products_delta_2	start_time:	1751551202	end_time:	1751551382	count:	6			kill_list_count:	6
products_delta_3	start_time:	1751551202	end_time:	1751551382	count:	2			kill_list_count:	2
products_delta_4	start_time:	1751551229	end_time:	1751551383	count:	0			kill_list_count:	0
products_delta_5	start_time:	1751551229	end_time:	1751551383	count:	2			kill_list_count:	2
products_delta_6	start_time:	1751551229	end_time:	1751551383	count:	2			kill_list_count:	2
products_delta_7	start_time:	1751551229	end_time:	1751551383	count:	1			kill_list_count:	1
products_delta_8	start_time:	1751551253	end_time:	1751551384	count:	2			kill_list_count:	2
products_delta_9	start_time:	1751551253	end_time:	1751551384	count:	3			kill_list_count:	3
products_delta_10	start_time:	1751551253	end_time:	1751551384	count:	1			kill_list_count:	1
products_delta_11	start_time:	1751551253	end_time:	1751551384	count:	3			kill_list_count:	3
products_delta_12	start_time:	1751551272	end_time:	1751551385	count:	1			kill_list_count:	1
products_delta_13	start_time:	1751551272	end_time:	1751551385	count:	2			kill_list_count:	2
products_delta_14	start_time:	1751551273	end_time:	1751551385	count:	2			kill_list_count:	2
products_delta_15	start_time:	1751551273	end_time:	1751551386	count:	0			kill_list_count:	0

Итого: 35 - количество документов в индексе, 35 - kill-list

Фактически индексировано:

collected	6
collected	2
collected	6
collected	2
collected	1
collected	0
collected	2
collected	2
collected	1
collected	2
collected	3
collected	3
collected	0
collected	1
collected	0
collected	0

Итого: 31

Похоже после ротации нового main и после ротации первой delta применяется не верный kill-list. Должен быть новый от delta за период от 17:00 до 17:04.

может вы соберете пример который можно воспроизвести локально и расследовать?

тк в этих шагах уже не понятно

Индексируются они с диапазоном от прошлой полной индексации main и применяются к еще текущему индексу main.
Затем на почти конце 2 минуты приходит ротация нового main и количество документов верное, совпадает с источником.
До полной индексации main количество документов не верное в индексе. Это проблема.
Далее на 3 минуте отрабатывает уже новая delta (с новым диапазоном на начала часа и до старта новой delta) и применяется к уже новому main.
И на этом этапе происходит странное - в индексе main подавляется большое количество документов, что быть не должно!

тк в main при каждой индексации имеет не правльное количество документов - как такое получается - не понятно

как вы считаете количество документов - не понятно

какие номера документов лишние - не понятно

именно документы лишние или статистика по индексам кривая - не понятно

ну и другой вопрос если у вас сотни тысяч или миллионы документов в индексе, почему вы не используете RT индекс и постинг данных напрямую, без индексации?

если вы хотите держать количество диск чанков \ plain index под контролем - то вы можете регулировать это опцией optimize_cutoff

Это сделать пока сложно. Для этого нужна база источник, в которой нужно так же постоянно вносить изменения в записях: обновлять, добавлять и отмечать записи для удаления.

Несколькими способами:

  1. Это запросом с count котором идет выборка записей для индексации main:
  • из Источника:
SELECT count(id) FROM product WHERE is_removed = 'f'

где is_removed - это флаг отметки на удаление документа: f - значит не удаленный

  • из индекса: получаем индексированные документы из лога с collected
  • из индекса: запросом с count для main:
SELECT count(*) FROM products_main_0, ... products_main_15
  1. Это запросом с count котором идет выборка записей для индексации delta:
  • из Источника:
sql_query_range: |
        SELECT (SELECT EXTRACT(EPOCH FROM data_relevant_to AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='INDEX_MAIN') min, \
          (SELECT EXTRACT(EPOCH FROM indexing_started_at AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='INDEX_DELTA') max
sql_query_where:
        delta: |
          WHERE is_removed = 'f' AND changed_at_timestamp >= $start AND changed_at_timestamp <= $end AND (id % CHUNK_SIZE) = CHUNK_MOD

где is_removed - это флаг отметки на удаление документа: f - значит не удаленный

  • из индекса: получаем индексированные документы из лога с collected
  • из индекса: запросом с count для delta:
SELECT count(*) FROM products_delta_0, ... products_delta_15

Это же количество совпадет из статистики:

show table products_main_0 status LIKE 'indexed_documents';
...
show table products_main_15 status LIKE 'indexed_documents';

Аналогично для delta.
На это же построен мониторинг.
По аналогии получаю количество для kill-list запросом из конфигурации:

SELECT count(id) FROM product \
            WHERE changed_at_timestamp >= (SELECT EXTRACT(EPOCH FROM data_relevant_to AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='INDEX_MAIN')::int \
            AND changed_at_timestamp <= (SELECT EXTRACT(EPOCH FROM indexing_started_at AT TIME ZONE 'Europe/Moscow') FROM manticore_indexer WHERE index_name='INDEX_DELTA')::int AND (id % CHUNK_SIZE) = CHUNK_MOD
    sql_query_check_rotate:

где CHUNK_SIZE = 16, а CHUNK_MOD = от 0 до 15.

Тут не лишние, а delta с kill-list подавляет не правильно в новом индексе main.

В Источнике у нас порядка 2028927 записей и столько же должено быть в индексе.
Мы на текущий момент на RT и по ряду причин пытаемся перейти на plain индексацию, вместо RT.

Сам main индекс приходит правильный после ротации. А вот последующая delta, собранная уже с новым диапазоном, которая содержит малое количество документов и kill-list применяется, но что-то в Manticore идет не так и мы теряем документы.

Опытным путем была выявлена одна закономерность, связанная с установкой даты для последующей индексации delta.
В Источнике (PostgreSQL) есть специальная таблица для индексации:

                                                                               Таблица "public.manticore_indexer"
           Столбец            |              Тип               | Правило сортировки | Допустимость NULL |           По умолчанию            | Хранилище | Сжатие | Цель для статистики | Описание  
-------------------------------+--------------------------------+--------------------+-------------------+-----------------------------------+-----------+--------+---------------------+----------
index_name                    | character varying(50)          |                    | not null          |                                   | extended  |        |                     |  
indexing_started_at           | timestamp(0) without time zone |                    | not null          | CURRENT_TIMESTAMP                 | plain     |        |                     |  
data_relevant_to              | timestamp(0) without time zone |                    | not null          | CURRENT_TIMESTAMP                 | plain     |        |                     |  
indexing_ended_at             | timestamp(0) without time zone |                    | not null          | CURRENT_TIMESTAMP                 | plain     |        |                     |  
indexing_product_changed_from | timestamp(0) without time zone |                    |                   | NULL::timestamp without time zone | plain     |        |                     |  
indexing_product_changed_to   | timestamp(0) without time zone |                    |                   | NULL::timestamp without time zone | plain     |        |                     |  
data_relevant_to_old          | timestamp(0) without time zone |                    | not null          | CURRENT_TIMESTAMP                 | plain     |        |                     |  
rotate_ended_at               | timestamp(0) without time zone |                    |                   | NULL::timestamp without time zone | plain     |        |                     |  
Индексы:
   "manticore_indexer_pkey" PRIMARY KEY, btree (index_name)
Метод доступа: heap

По окончанию индексации main на уровне утилиты indexer устанавливалась дата data_relevant_to в sql_query_post_index:

        INSERT INTO manticore_indexer (index_name, data_relevant_to) \
          VALUES ('INDEX_MAIN', (SELECT indexing_started_at FROM manticore_indexer WHERE index_name='INDEX_MAIN')) \
          ON CONFLICT (index_name) DO UPDATE SET data_relevant_to = EXCLUDED.data_relevant_to

data_relevant_to = indexing_started_at, т.е. началу индексации

В этом случае картина была иная:

Хронология событий:

1 delta отрабатывают на 0 минуте часа и все хорошо. Она собирает документы от прошлой полной индексации main до начала текущей индексации delta, т.е. уже за 1 час.

2, 3, 4 delta отрабатывают на 1, 2, 3 минуте часа и подавляет документы во все еще старом индексе main, но собранным с уже новым диапазоном от новой индексации main.
Но, т.к. дата data_relevant_to устанавливается в sql_query_post_index, то сам индекс еще не доставлен и не применен на поисковых node.

Затем на почти конце 3 минуты приходит ротация нового main и количество документов верное, совпадает с источником. А последующие delta не подавляют большое количество документов и вроде все хорошо.

Чтобы это исправить, запрос sql_query_post_index для main был исправлен на:

sql_query_post_index: |
        INSERT INTO manticore_indexer (index_name, indexing_ended_at) \
          VALUES ('INDEX_MAIN', (SELECT indexing_started_at FROM manticore_indexer WHERE index_name='INDEX_MAIN')) \
          ON CONFLICT (index_name) DO UPDATE SET indexing_ended_at = EXCLUDED.indexing_ended_at

Здесь устанавливается indexing_ended_at вместо data_relevant_to.
indexing_ended_at** это только для истории устанавливается и отслеживания работы.

А data_relevant_to устанавливается в скрипте уже после доставки и фактической ротации индекса main на поисковых node.
Но в этом случае имеем ситуацию как описал в начале - подавление большого количества документов в новом индексе main.

Ведь собранный индекс delta, который содержит в себе новые документы и список kill-list для подавления должен быть применен к main корректно на основе того, что собрал.
В обоих случаях есть не корректное применение kill-list к main ДО или ПОСЛЕ.
Вот это и не понятно.

Уж не знаю, есть ли какой способ узнать как внутри применяется kill-list? Т.к. в логах этого нет, кроме простой записи, чтоб применялся некий kill-list.

Также неоднократно проверял в песочнике с примером: Manticore Search - Main+delta schema

Но здесь еще старая версия и пример очень простой, + нет изменений в реальном времени в источнике.

Там уже сам прорабатывал варианты, вносил изменения в источнике и запускал индексацию.
И в этом примере, если запустить индексацию main и после ротации корректное количество документов.

Затем если внести изменения в источнике и запустить delta и ротацию, то там уже собран индекс с новым диапазоном и данными + kill-list. И delta применяется корректно.

Мы сами разбивали на chunk для ускорения индексации и поиска на распределенном индексе. И это работает хорошо.

считает точно только в пределах одного индекса в пределах окна max_matches вы можете прочесть об этом Searching > Grouping | Manticore Search Manual

By default, counts are approximate

и там же написано, что делать если вам нужны точные значения

1 Like

так же могут быть разные race когда indexer послал SIGHUP вы думаете что индекс ротировался, и запускаете запрос для определения количества документов. но запрос использует еще старый индекс, а ротация случится позже

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

Было бы лучше если бы вы

  • добавили какой-то не нужный документ в источник и подавляли его дельтой
  • выполняли запрос SELECT * FROM idx WHERE id='non_sence' OPTION comment=‘rotation_gen_num’` - который бы показал наличие или подавление этого документа
  • дальше если демон запущен с опцией --logdebug то в логе демона будут логированы события с файлами, в том числе и начало и окончание ротации файлов. И эти события можно было бы сравнить в тайстампом из query.log для запроса с меткой rotation_gen_num

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

  1. запускать индексацию без ротации Data creation and modification > Adding data from external storages > Plain tables creation | Manticore Search Manual
manticore indexer --rotate --nohup plain_table
  1. после ротировать файлы индекса командой MySQL Data creation and modification > Adding data from external storages > Rotating a table | Manticore Search Manual
RELOAD TABLE plain_table;

чтобы быть уверенным когда началась и завершилась ротация файлов индекса

Это можно попробовать.

Непонятно вот что. Зашел у нас новый main с корректным количеством документов как в источнике. Количество совпадает по разным запросам и метрикам/статистикам.
Затем собираем новую delta уже на новом диапазоне за 4 минуты: main в начале часа и до старта delta. За это время она в себе несет N измененных и новых документов. Соответственно при ротации delta в main должны попасть новые документы и подавлены измененные. Так?

У нас как раз ручная индексация и ротация.
Для индексации только готовим индексы: manticore indexer --rotate --nohup <plain_table>
Для ротации готовятся команды с

RELOAD TABLE <plain_table>; и затем применяются.
Пример лога с logdebug как раз скидывал выше.
Вот пример под индекс:

[Fri Jul  4 11:03:09.179 2025] [1827296] rotating table 'products_delta_0': started
[Fri Jul  4 11:03:09.179 2025] [1827296] DEBUG: prealloc enough RAM and lock new table
[Fri Jul  4 11:03:09.189 2025] [1827296] DEBUG: Locking the table via file /dev/shm/manticore/indexes/products/products_delta_0.new.spl
[Fri Jul  4 11:03:09.189 2025] [1827296] DEBUG: lock /dev/shm/manticore/indexes/products/products_delta_0.new.spl success
[Fri Jul  4 11:03:09.189 2025] [1827296] DEBUG: CSphIndex_VLN::Preread invoked 'products_delta_0'(/dev/shm/manticore/indexes/products/products_delta_0.new)
[Fri Jul  4 11:03:09.189 2025] [1827296] DEBUG: Preread successfully finished
[Fri Jul  4 11:03:09.189 2025] [1827296] DEBUG: activate new table
[Fri Jul  4 11:03:09.189 2025] [1827296] RW-idx for rename to .old, acquiring...
[Fri Jul  4 11:03:09.189 2025] [1827296] RW-idx for rename to .old, acquired...
[Fri Jul  4 11:03:09.190 2025] [1827296] DEBUG: rotating table 'products_delta_0': applying other tables killlists
[Fri Jul  4 11:03:09.190 2025] [1827296] DEBUG: rotating table 'products_delta_0': applying other tables killlists... DONE
[Fri Jul  4 11:03:09.190 2025] [1827296] DEBUG: rotating table 'products_delta_0': apply killlist from this table to other tables (killlist_target)
[Fri Jul  4 11:03:09.190 2025] [1827296] DEBUG: rotating table 'products_delta_0': apply killlist from this table to other tables (killlist_target)... DONE
[Fri Jul  4 11:03:09.190 2025] [1827296] DEBUG: all went fine; swap them
[Fri Jul  4 11:03:09.190 2025] [1827296] rotating table 'products_delta_0': success
[Fri Jul  4 11:03:09.191 2025] [1827296] DEBUG: unlink /dev/shm/manticore/indexes/products/products_delta_0.old
[Fri Jul  4 11:03:09.193 2025] [1827296] DEBUG: Unlocking the table (lock /dev/shm/manticore/indexes/products/products_delta_0.old.spl)
[Fri Jul  4 11:03:09.193 2025] [1827296] DEBUG: File ID ok, closing lock FD 505, unlinking /dev/shm/manticore/indexes/products/products_delta_0.old.spl

Забыл еще сказать, что ротация у нас бесшовная с seamless_rotate, как по документации.

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

да видно что стадия klist проходит норм

applying other tables killlists
...
apply killlist from this table to other tables

теперь надо сопоставить ваш запрос для подсчета с моментом ввода индекса

all went fine; swap them
...
rotating table 'products_delta_0': success

так же не понятно какой режим killlist_target у вас используется в разных индексах.

Вы пишете

Соответственно при ротации delta в main должны попасть новые документы и подавлены измененные. Так?

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

1 Like

Проверим этот момент.

Для delta указан:
killlist_target: "INDEX_MAIN:kl", где INDEX_MAIN заменяется фактическим именем main.
Вот пример из конфигурации:

index products_main_0
{
    type = plain
    source = products_main_0
    path  = /dev/shm/manticore/indexes/products/products_main_0

    # options
    min_prefix_len = 3
    index_exact_words = 1
    charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F, U+0401->U+0435, U+0451->U+0435, U+401->U+0435, U+451->U+0435
    ignore_chars = U+002E, U+002D, U+005C
    morphology = lemmatize_ru_all, lemmatize_en_all
    min_stemming_len = 4
    expand_keywords = 1
    # options end
        
}

index products_delta_0
{
    type = plain
    source = products_delta_0
    path  = /dev/shm/manticore/indexes/products/products_delta_0
    killlist_target = products_main_0:kl
    
}

Пробовали так: killlist_target = products_main_0, но в этом случае мы получили вместо провала - горб вверх по документам.

Не корректно написал. Имею ввиду, что при поиске в индексе main, delta уже будут обновленные документы, без учета отмеченных на удаление.
Простой пример:
В Источнике 10 документов.
Запустили main и прошла ротация. В индексе main 10 документов.
Затем мы добавили в Источник: 2 новых документа, 2 изменили и 1 отметили на удаление.
Запустили delta и после ротации у нас в main: 3 документа подавлены: 2 измененных и 1 удаленный.
И если искать по main, delta, то у нас будет уже 11 документов. Так?
Порядок индексов при поиске так же указан верно: main, delta.

Есть ли возможность получить список id kill-list? Т.к. id документов в индексе можно посмотреть командой:

indextool --dumpdocids products_delta_0 | grep -P "docinfo"
docinfo-bytes: docinfo=594864, min-max=9792, total=604656
docinfo-stride: 204
docinfo-rows: 2916

И еще о количестве документов пишет сам indexer в вывод.
В то время как kill-list храниться в бинарном файле *.spk и не посмотреть какие в нем id.

получается

  • main 3 документа отмечены удалёнными
  • delta 4 документов

10 - 3 + 4 = 11 документов всего в main.delta

1 Like

нет klist никак не посмотреть

Вот. А у нас получается, что документы подавляются, а где измененные тогда? А они недоступны для поиска.

У нас по большей части записи в Источнике очень часто меняются: обновление и реже немного новых может быть добавлено. На удаление очень мало выходит с отметкой.
В Источнике:

select count(id) from product where is_removed = 'f';
  count  
---------
 2029494
(1 строка)

select count(id) from product where is_removed = 't';
 count 
-------
   113
(1 строка)

Жаль.

нет никаких измененных документов в индексе, в дельту вы индексируете отдельно документы, отдельно killlist. killlist должен содержать все номера документов, которые вы проиндексировали в delta индекс, если вы выбрали такой путь и используете опцию killlist_target = products_main_0:kl

дальше при ротации delta индекс, он просто берет и отмечает все документы из delta.killlist_target в main индексе убитые. Есть ли такие документы в delta, изменены они или удалены - вы сами должны следить, сами индексы не знают изменен ли этот документ или это дубликат или это убитый документ

может вам больше подойдет режим killlist_target = products_main_0 чтобы исключить, что delta killist расходится с тем какие документы есть в самом delta индекс

но опять же мне до сох пор не понято каких именно документов нет из delta из main, их на самом деле нет или присутствует race ротации или запрос подсчета не правильный

Это понятно.
Но ведь для поиска у нас используется распределенный индекс со списком: main, delta.
И это значит, что после подавления в main от delta.kill-list, мы при поиске в main, delta получим документы: не подавленные и новые от delta. Все как в примере по курсу, где добавили, обновили и удалил документы.

Пробовали, об этом выше писал и есть так же график.

Если получится собрать информацию, то предоставлю.
Я верно Вас понял, что при подсчете количества может быть так, что значение можем получить приближенное или старое? И потому, нужно проверить на тесте с записями измененными в источнике и запросами До и После ротации: количества и также поиск по этим id и все это сопоставить с логом в searchd.log?

@tomat удалось выяснить вот что.

  1. Выгрузил все индексированные id из main и delta.
    Затем прошелся скриптом по ids из delta и пачками по 100 слал запросы в proxy и результат сохранял в файл.
    Итого:
    • в main после его ротации id проиндексировано: 2029747
    • в delta после ротации (одной из последней) id проиндексировано: 16544
    • через proxy прошли запросы группами по 100: 166 запросов
    • по всем id (из 16544) есть результаты поиска

Отсюда можно сделать предварительный вывод, что все документы проиндексированные в новых delta, доступны после ротации для поиска.

  1. Прошелся скриптом по ids из main (2029747) и пачками по 1000 слал запросы в proxy и результат сохранял в файл.
    Итого:
  • в main после его ротации id: 2029747
  • через proxy прошли 2018015 из 2029747, нет для 11760

Но при этом из 11732 (запросы группами по 1000: 2034 запросов

  • результаты есть по (ранее 11760) - 4949 нет в delta, а там было 16544.

Взяв еще раз список id из 4949 выше, прогнал запросами и получил, что теперь из них недоступны для поиска только 1575.

P.S. Здесь можно не обращать внимание на такую разницу, т.к. проверка шла на прошлом и новом часе с новой индексацией.
Суть не меняет, просто не быстрый процесс проверок всех и обработки.

Выборочная проверка в таблице Источнике product по этим id показала вот что:

db_libra=# select id,name,is_removed,changed_at,changed_at_timestamp from product where id=2110110;
   id    |                    name                     | is_removed |     changed_at      | changed_at_timestamp 
---------+---------------------------------------------+------------+---------------------+----------------------
 2110110 | Атлант расправил плечи (комплект из 3 книг) | f          | 2025-07-07 17:58:14 |           1751900294
(1 строка)

Суть в том, что для поиска недоступны документы, которые были изменены ДО начала нового часа уже после нового main и delta.

Сделал еще сравнение на основе результата выше.
Из 1575 для поиска были доступны 1567, а 8 нет.
Их id:

2082214
2095283
2103865
2140157
2193693
2769455
3085452
8108576

Запрос по ним в proxy:

mysql> select id,name from products where id in (2082214,2095283,2103865,2140157,2193693,2769455,3085452,8108576);
+---------+-------------------+
| id      | name              |
+---------+-------------------+
| 2769455 | Три любви         |
+---------+-------------------+
1 row in set (0,00 sec)

И в Источник:

db_libra=# select id,name,is_removed,changed_at,changed_at_timestamp from product where id in (2082214,2095283,2103865,2140157,2193693,2769455,3085452,8108576);;
   id    |                           name                            | is_removed |     changed_at      | changed_at_timestamp 
---------+-----------------------------------------------------------+------------+---------------------+----------------------
 2082214 | Подводный мир. Полная энциклопедия.                       | f          | 2025-07-07 17:57:25 |           1751900245
 2095283 | Доктор Живаго                                             | f          | 2025-07-07 17:57:45 |           1751900265
 2103865 | Отцы и дети                                               | f          | 2025-07-07 17:58:03 |           1751900283
 2140157 | Маленький принц                                           | f          | 2025-07-07 17:59:01 |           1751900341
 2193693 | История государства Российского                           | f          | 2025-07-07 18:00:39 |           1751900439
 2769455 | Три любви                                                 | f          | 2025-07-07 18:20:07 |           1751901607
 3085452 | Машенька                                                  | f          | 2025-07-07 17:58:40 |           1751900320
 8108576 | Секрет чёрного камня. Вампир Полумракс. Приключения Алисы | f          | 2025-07-07 17:58:40 |           1751900320
(8 строк)

Для 7 записей из 8 видно, что у них changed_at до начала 18:00 включительно.
И эти id были подавлены в mian и недоступны для поиска на распределенном индексе.

Пробовал менять killlist_target: "INDEX_MAIN:kl" на:

  • killlist_target: "INDEX_MAIN:id" - только для id из delta
  • killlist_target: "INDEX_MAIN" - оба варианта: id из delta и kill-list

И запрос в sql_query_killlist:

SELECT id FROM product \
WHERE is_removed = 't' AND (id % CHUNK_SIZE) = CHUNK_MOD

И результат не изменился, документы в main как подавлялись, так и продолжают подавляться после ротации новой delta на новом main.

При этом id подавленных документов нет в delta. И при этом количество подавленных документов не сходится с delta и ее kill-list.