[Поисковые технологии, Sphinx] Укрощаем Manticoresearch

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
28-Июн-2021 15:35

Manticoresearch - это Open-Source проект, форк проекта sphinxsearch от Андрея Аксенова и его команды. Проект позиционирует себя как открытое высокопроизводительное решение для полнотекствого поиска. Судя по бенчмаркам (правда они от самих создателей Мантикоры), "средняя по больнице" скорость превышает скорость популярного Elasticsearch.В своей заметке я расскажу, как устроены индексы и как их можно потюнить с графиками и картинками на живом примере покажу что на что влияет.ИндексыВ manticore (сокращенно от manticoresearch - далее буду писать manticore для простоты) есть четыре типа индексов: plain, real-time, distributed и percolate. Plain индексы - это т.н. простые индексы, которые хранятся на диске, создаются один раз, поддерживают обновление атрибутов, но не полнотекстовых полей. Real-time индексы похожи на таблицы в базах данных, поддерживают полную замену документов через REPLACE, вставку, обновление в т.ч. и полнотекстовых полей в режиме "онлайн". Также существует тип percolate индекс, основанный на Real-time-индексе. Этот тип не хранит данные, но хранит запросы.Четвертый тип - это distributed индекс. Он ничего не хранит ни в каких файлах, это просто составной индекс который под собой содержит несколько plain или/и rt-индексов.Plain и real-time индексы могут состоять из трех видов полей и атрибутов:1) id - это идентификатор документа в индексе. Механизма автоинкремента для id в мантикоре не имеется, обеспечение уникальности id ложится на плечи программиста. Тип id всегда unsigned int64.2) Полнотекстовые поля - хранят проиндексированный текст. Упрощенно полнотекстовые поля -- это структура инвертированного индекса: в памяти хранится словарь, на диске цепочки указателей на местоположение слова (терма) в документе. В настоящее время manticore не поддерживает хранение оригинального текста в полнотекстовых полях. В одном индексе может быть сразу несколько полнотекстовых полей, и разумеется, можно искать (сопостовлять, матчить) документы сразу по нескольким полнотекстовым полям. Именно по полнотекстовым полям manticore вычисляет релевантность - weight() для дальнейшего ранжирования результатов поиска. Для подробностей см. раздел searching3) Атрибуты - дополнительные поля, по которым можно дофильтровать, сгруппировать или сортировать сматченные документы прежде чем отдать их клиенту. Атрибуты могут быть использованы в формуле ранжирования. Существует несколько типов поддерживаемых manticore атрибутов:
  • беззнаковый int32 и int64 со знаком. В manticore их типы обозначаются соответсвенно - uint и bigint.
  • 32 битные числа с плавающей точкой одинарной точности (float)
  • unix-timestamps
  • булевые типы (bool)
  • строки (string)
  • JSON
  • multi-value - MVA, это типа массивов, которые могут содержать только лишь 32 битные целые без знака
Каждый определенный в конфигурации индекс состоит из нескольких файлов на диске. Некоторые файлы по умолчанию всегда лежат на диске и обращение к ним происходит через стандартные средства ОС обращения к диску. Некоторые файлы для ускорения обращения к ним отображаются в память, это словари, а также скалярные атрибуты. Ниже для удобства приведена таблица файлов индекса и как они отображаются или не отображаются в память:Файл, расширениеЧто хранится?Как хранится по умолчаниюspaСкалярные атрибутыОтображение в память через mmap() spdСписок документовЧитается с дискаspiСловарьВсегда в памятиsphЗаголовок индекса(или блока)Всегда в памятиspkkill listЗагружается в память при запуске и выгружается после применения spllock файлВсегда на дискеspmrow mapmmapsphiгистограммыВсегда в памятиsptструктуры для обращения к docidmmapsppпозиции ключевых словЧитается с дискаspbАтрибуты - mva, строки и jsonmmapReal-time индексы имеют дополнительные файлы:Файл, расширениеЧто хранится?Как хранится по умолчаниюkillRT kill - документы, которые были заменены через REPLACE, прошли очистку и сброшеные как блок (чанк)Всегда на дискеmetaЗаголовок rt-индексаВсегда в памятиlockRT lock-файлВсегда на дискеramКопия блока (чанка) из памяти - создается, когда блок из памяти сбрасывается на диск. Всегда на дискеДля чтения индексов мантикора использует два метода - seek+read и mmap.В seek+read режиме (значение опций acess_* = file, об опциях доступа ниже) чтение выполняется через pread(2), то есть мантикора с настройками по умолчанию использует этот системный вызов для чтения файлов spd и spp с диска. Для оптимизации чтения на старте алоцируются буферы, размер которых можно подстроить через опции read_buffer_docs и read_buffer_hits для чтения spd (документы) и чтения spp (позици ключевых слов) соответственно. Важно знать также, что по умолчанию на старте мантикоры индексы еще не открыты, то есть вызов open() происходит при каждом обращении к файлам индекса. Это поведение регулируется опцией preopen. У меня на практике (довольно высокая нагрузка) эта опция всегда была выставлена в 1, это позволяет избежать вызовов open() на каждый запрос, однако в таком режиме мантикора создает 2 файловых дескриптора на каждый индекс. Плохо это или хорошо, зависит от ситуации, если много индексов у вас, и не такая большая нагрузка, то имеет смысл оставить по умолчанию.В режиме mmap файлы отображаются в память системным вызовом mmap(2). Опции read_buffer_docs и read_buffer_hits не влияют на производительность никаким образом. Этот режим доступа может быть применен к файлам, хранящим скалярные атрибуты (spa), документам (spd), позициям ключевых слов (spp) и атрибутам переменной длины - json, строкам и mva (spb). Есть еще один режим в котором можно запретить ОС свопить на диск кешированные данные индексов из памяти. Это осуществляется через системный вызов mlock(2), но чтобы воспользоваться им, мантикора должна быть запущена в привелигированном режиме (например, из под рута). Теперь про настройки и выбор их значений в различных ситуациях.НастройкаЗа что отвечаетПо умолчаниюaccess_plain_attrsОпределяет доступ к скалярным атрибутам (типы bigint, bool, float, timestamp, uint).mmap_prereadaccess_blob_attrsОпределяет доступ к blob-атрибутам - json, string, mvammap_prereadaccess_doclistsОпределяет доступ к документамfileaccess_hitlistsОпределяет доступ к позициямfileЗначения настроек доступа:ЗначениеКраткое описаниеfileБуферизированное чтение с диска с вызовом pread(2). Размеры буферов регулируются read_buffer_docs и read_buffer_hits.mmapФайлы индекса будут отображаться в память через системный вызов mmap(2), ОС будет кешировать все обращения к файлам в памяти.mmap_prereadТоже самое что mmap, но файлы индекса будут прочитаны на старте мантикоры. Своего рода "прогрев" кеша.mlockФайлы индекса будут отображены в память и через системный вызов mlock(2) данные будут закешированы ОС и заблокированы от сброса на диск.Я проводил небольшой эксперимент, подкрутил настройки access_doclists и access_hitlists выставив в значение mmap. Ниже дан график, наглядно демонстрирующий, что особо сильно это не дает прироста производительности. Настройки применены в 1:05PM.
Машина имела такую конфигурацию:
  • Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 40 ядер
  • 96Гб памяти
  • Рабочие виртуалки редиса и редиса под кеш портала
  • 43 640 037 проиндексированных документов
  • 16 Gb (spd) +9 Gb (spa) + 8Gb (spp) + 25 Gb (spb) ~ 60Gb монолитный plain-индекс + rt-индекс
Но! На нашей машине с мантикорой установлен SSD диск, что и не дает вау-эффекта. Если у вас HDD и памяти хватает, чтобы закешировать все индексы, то смело выставляйте access_doclists и access_hitlists в mmap, а access_plain_attrs и access_blob_attrs в mlock. Тогда это даст, на сколько я понял, максимальную производительность мантикоры.В случае, если памяти не хватает, то не используйте mlock - "горячие" данные все равно будут в памяти, а редко используемые будут погружаться с диска ОСью.Также порекомендую всегда запускать мантикору с опцией --force-preread, это задержит мантикору на начальном этапе, так как она будет считывать сначала индексы и только потом начнет принимать запросы от клиентов. Каждая часть индекса будет прочитана для того, чтобы ОС закешировала обращения к диску, это как прогрев кеша. Зато после старта ваши запросы будут обрабатываться гораздо быстрее. Но это опять же, совет для тех, у кого HDD.ConcurrencyТеперь поговорим о том, как мантикора реализует конкуретное исполнение ваших запросов. В ранних версиях, когда мантикора была сфинксом, на каждое сетевое соединение порождался тред, в котором и происходила обработка запроса/запросов. Данный режим до сих пор остался в мантикоре по соображениям обратной совместимости. С версии 2.3.1-beta сфинкса был добавлен режим конкурентности thread pool. Этот режим является самым предпочтительным. Основные настройки конкурентности в мантикоре отвечают следующие основные опции (секция searchd):ОпцияКраткое описаниеworkersопределяет режим конкурентости. По умолчанию - thread_pool. Не меняйте, особенно если у вас большая нагрузка и постоянный поток подключений от клиентов.queue_max_lengthРазмер очереди воркеров в режиме thread_pool. По умолчанию в мантикоре бесконечная очередь.max_childrenКол-во потоков запускаемых в параллель. В режиме thread_pool определяет размер пула потоков на старте. В режиме threads ограничивает максимальное кол-во параллельных воркеров. По умолчанию равняется нулю, что означает в thread_pool размер пула равен кол-ву ядер*1.5.dist_threadsКол-во потоков для обработки внутри одного запроса. По умолчанию 0, что означает, параллельность внутри обработки одного запроса отключена.Далее будет подразумеваться, что в мантикоре выставлен рекомендуемый режим - пул потоков - workers = thread_pool.Для начала давайте еще глубже спустимся и посмотрим по-диогонали, как вообще устроен пул потоков в мантикоре. Это поможет понять какие метрики надо отслеживать и какие настройки тюнить.Давайте вспомним или поймем, что такое пул потоков или thread pool. Классический пул потоков представляет собой некую структуру, которая при запуске инициализирует определенное кол-во потоков. Каждый новый таск поступает на обработку в определенный уже запущенные поток исполнения. Таски или джобы прежде чем попасть в поток исполнения попадают в очередь. Очередь позволяет балансировать нагрузку на пул.
рис.1. Схема пула потоковВзглянем на код
Рис.2. CSphThdPoolПул потоков в мантикоре представлен классом CSphThdPool. Внутри как мы видим, все по классике: очередь представленна в виде связанного списка (указатели на голову и хвост m_pHead и m_pTail соответственно), вектор пула потоков - m_dWorkers. Чуть ниже - поля показывающие статистику пула потоков - m_tStatActiveWorkers - сколько воркеров сейчас исполняется (то есть сколько активных запросов или служебных задач сейчас в исполнении), и m_tStatQueuedJobs - кол-во джобов, ожидающих в очереди на исполнение.Как же происходит обработка запроса? Взглянем на метод AddJob, который принимает указатель на джоб:
Рис.3 AddJobПока что тут все ясно: джоб добавляется в связанный список, представляющий очередь пула потоков, и увеличивается счетчик m_iStatQueuedJobs. Именно этот счетчик ототображается в результате запроса SHOW STATUS; в метрике work_queue_length:
Рис.4 Пример вывода SHOW STATUS;Джоб еще не начал свое выполнение, он просто поставлен в очередь и ждет, когда наступит событие, по которому его "возьмут" на исполнение. В идеале, метрика work_queue_length должна быть всегда равна нулю. Если она начнет расти, тогда у меня плохие новости. Это говорит о том, что пул потоков иссяк, все потоки заняты и вновь приходящие запросы от клиентов будут "висеть". За ограничение размера очереди отвечает настройка queue_max_length, по умолчанию она равна 0, что обозначает бесконечную очередь. Если задать эту настройку, то при превышении лимита, мантикора начнет отдавать вновь "прибывшим" запросам ответ maxed out в случае работы через старый бинарный протокол, либо too many requests в случае, если ваш драйвер работает по протоколу mysql. Эта ошибка возвращается со статусом "retry"
Рис.5. Статусы ответа мантикорыОшибку клиент должен обработать и попытаться повторить запрос.Далее рассмотрим "сердце" пула потоков, цикл обработки джоба в потоке:
Рис. 6. Обработка джоба в потокеЗдесь все просто, на строках 1942-1950 мы выбираем джоб из очереди, далее уменьшаем счетчик m_iStatQueuedJobs (work_queue_length), увеличиваем счетчик активных джобов, запускаем джоб на исполнение. Как только джоб выполнился (повторюсь, это может быть запрос клиента или внутренний джоб мантикоры, например flush атрибутов на диск), уменьшаем счетчик m_tStatActiveWorkers - счетчик активных джобов. Этот счетчик отражается в метрике SHOW STATUS; под названием workers_active (см. рис. 4).Итого, имеем три метрики, за которыми стоит следить:workers_active - кол-во исполняемых в данный момент джобовwork_queue_length - кол-во джобов, "висящих" в очереди на исполнениеworkers_total - текущий размер пула потоков, который задается на этапе запуска мантикоры и остается постоянным на всем протяжении работы. Этот размер задается настройкой max_children.Еще одна интересная настройка dist_threads, по умолчанию она равна 0, что означает "каждый запрос будет обработан в один поток". Если у вас достаточно большой plain индекс и есть real-time индекс, то есть смысл plain-индекс разбить на несколько частей и создать ditributed-индекс, выставив dist_threads равное или чуть большее кол-ву частей distributed-индекса. На практике у меня на работе есть два rt-индекса и один plain. dist_threads выставлен в 3, то есть каждый запрос обрабатывается в три потока и каждый поток "смотрит" в plain либо в один из rt индексов. Прироста производительности не было замечено при выставлении dist_threads в "3", однако в нынешней ситуации роста кол-ва документов в plain индексе скорее всего придется разбивать plain индекс на две части и менять dist_threads соответственно. Время покажет. ПроблемыПока что есть довольно существенная проблема в мантикоре, это то, что real-time индекс фрагментируется, по мере роста кол-ва постоянных обновлений. У real-time индексов есть такая настройка rt_mem_limit которая задает лимит данных индекса, находящихся в памяти, по мере приближения к этому лимиту, мантикора начинает сбрасывать редко используемые данные на диск в виде блоков (chunk). Если измений с последнего сброса real-time индекса накопилось довольно много, то таких блоков становится много, более того, потребляемая процессом searchd память растет и может вырасти на десятки гигабайт.
Есть такая операция в мантикоре OPTIMIZE, она решает проблему фрагментированного rt-индекса, и позволяет "схлопнуть" все блоки в один, тем самым удерживая общую производительность в пределах нормы. Существенная проблема мантикоры - это исчерпание пула потоков, если джобы "уперлись" в диск например. Тогда клиенту ничего не остается делать, как прервать запрос по таймауту и повторить позднее либо отправить запрос на другую ноду в реплике. По документации рекомендуется выставлять max_children в 1.5*кол-во ядер на машине, однако учтите также и создаваемую нагрузку на диск. Практически я пришел к выводу, что лучше все таки выставить явно max_children равное кол-ву ядер на хосте. ЗаключениеДанная заметка далеко не полная и даже не является инструкцией к действию. Это просто сборник из моих записей и наблюдений в процессе эксплуатации sphinx и далее - manticoresearch. По моему скромному мнению, проект очень даже не плохой, если нужен полнотекстовый поиск на сайте, то это вполне рабочее решение. Возможно, если понравится читателям, в следующий раз я соберусь и опишу другие интересеные части manticoresearch.Для подробностей обращайтесь к официальной документации.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_poiskovye_tehnologii (Поисковые технологии), #_sphinx, #_indeksy (Индексы), #_optimizatsija (оптимизация), #_sphinx, #_manticore, #_poiskovye_tehnologii (
Поисковые технологии
)
, #_sphinx
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 21-Сен 16:52
Часовой пояс: UTC + 5