В этой статье мы расскажем о текущих рабочих процессах, реализованных в Manticore Search, и о том, как настроить их параметры.
В Manticore Search в настоящее время существует два режима многопроцессности, управляемых директивой workers . По умолчанию текущий MPM — thread_pool, альтернативой является threads.
Потоки
В режиме многопроцессности ' threads ' создаётся новый выделенный поток для каждого входящего сетевого соединения. Поток активен до отключения клиента — то есть, пока соединение живо. За это время поток будет выполнять входящие запросы из соединения.
По умолчанию движок будет создавать неограниченное количество потоков — если это позволяет операционная система. Хотя это может выглядеть хорошо, на практике разрешение создания неограниченного числа потоков влечёт за собой некоторые затраты. Во‑первых, создание/уничтожение потока требует ресурсов ЦП. Хотя это небольшие затраты по сравнению с форками, они всё же существуют.
Создание сотен или тысяч потоков в секунду может влиять на производительность системы, в зависимости от доступного количества ядер ЦП. Когда количество потоков значительно превышает количество доступных ядер, потоки начинают конкурировать за ресурсы — одни за ядра ЦП, другие — за хранилище.
Например, если у нас система с 16 ядрами, но мы позволяем создавать 160 поисковых потоков, это означает 10 потоков на одно ядро ЦП. Потоки не будут работать одновременно, а будут ждать в очереди, пока ядро выделит им такты. С точки зрения хранилища, может быть 160 потенциальных запросов на чтение данных. Если хранилище не успевает обслуживать потоки без задержек (латентности), эти задержки могут влиять на распределение ресурсов ЦП, поскольку поток может получить разрешение выполнить расчёты на ядре, но вынужден ждать данные.
max_children настройка позволяет установить ограничение на количество одновременно активных потоков. Когда лимит достигается, движок начинает отклонять входящие соединения с ошибкой 'maxed out'. При выборе значения max_children следует помнить, что поток завершается, когда клиент закрывает соединение. Если соединение бездействует, поток остаётся активным и учитывается в max_children. max_children можно увеличить до предела, зависящего от возможностей сервера обрабатывать количество активных запросов. Это включает вычислительные возможности (CPU) и операции ввода‑вывода (хранилище). Частая ошибка — повышать max_children до очень больших значений. Существует точка, зависящая от возможностей системы, когда создание слишком большого количества потоков приводит лишь к замедлению запросов. Если нет ничего более, чем оптимизация индексов и запросов, следует рассмотреть добавление нового поискового сервера для распределения нагрузки.
Thread_pool
В предыдущем разделе мы говорили о том, что использование потоков имеет стоимость, и что соединения могут удерживать потоки активными, даже если они не используются.
Другой стратегией управления потоками является не создавать поток для каждого соединения, а использовать pool потоков для обработки работы. В этом случае рабочие потоки не связаны с входящими соединениями, поскольку соединения обрабатываются отдельным классом потоков. При запуске демона он создаёт определённое количество потоков. Входящие сетевые соединения обрабатываются так называемыми сетевыми потоками, задача которых — принимать запросы из соединений и направлять их в потоки пула.
Если пул слишком занят (все рабочие потоки обрабатывают запросы), новые запросы отправляются в pending queue. Сервер будет 'max out', если очередь ожидания заполнится. По умолчанию ограничение не задано, однако длительное ожидание в очереди ничего не меняет, получая запросы, которые требуют много времени. Хорошая идея — установить ограничение на очередь, чтобы сигнализировать, что сервер получает больше запросов, чем способен обработать. Это можно задать директивой queue_max_length . По сравнению с max_children в модели threads, где перегрузка начинается после определённого количества открытых соединений, в случае thread_pool демон начнёт 'max out' после того, как активных запросов станет больше, чем max_children (количество потоков в пуле) + queue_max_length.
В случае thread_pool , директива max_children определяет количество потоков в пуле, которые создаются при запуске демона. По умолчанию используется значение 1.5x количества ядер ЦП. Установка max_children на высокие значения не повысит производительность или ёмкость сервера. Поскольку эти потоки занимаются только обработкой, им необходимо как можно быстрее получить доступ к ЦП.
Установка высоких значений приведёт, как и в предыдущем разделе, к конкуренции за ЦП и более низкой производительности, а также к более медленному запуску демона. По сравнению с режимом threads, где max_children может быть установлен в несколько раз больше количества ядер ЦП, и значения в сотнях имеют смысл, в случае thread_pool имеет мало смысла увеличивать его в 2‑3 раза по сравнению с количеством ядер.
По умолчанию для обработки сетевых соединений используется один выделенный поток. Задача потока обычно проста, так как он лишь выступает в роли прокси для thread_pool и, как правило, способен выдерживать значительный объём трафика. Количество сетевых потоков можно увеличить с помощью директивы net_workers . Увеличение количества сетевых потоков дало прирост производительности в конфигурациях с экстремальными показателями запросов в секунду.
Сетевые потоки также имеют несколько настроек точной настройки. Сетевой поток может быть не всегда занят, так как могут быть моменты (в субсекундных пределах), когда он не получает запросов. Поток переходит в спящий режим и теряет свой приоритет на процессоре. Время, затрачиваемое на пробуждение или возвращение на своё место в процессоре, мало, но для высокопроизводительных систем каждая миллисекунда имеет значение.
Чтобы решить эту проблему, можно активировать занятый цикл с помощью директивы net_wait_tm . Если значение net_wait_tm положительно, поток будет 'tick' процессор каждые 10*net_wait_tm (где значение net_wait_tm задаётся в миллисекундах). Если net_wait_tm равно нулю, занятый цикл работает непрерывно — следует отметить, что это приведёт к дополнительному использованию процессора, даже если сетевой поток получает мало трафика. Чтобы отключить занятый цикл, следует использовать отрицательное значение ‑1. По умолчанию net_wait_tm имеет значение 1, что означает, что занятый цикл тикает каждые 10 мс.
Сетевой поток также может быть ограничен. Параметр net_throttle_accept задаёт предел количества клиентов, которые сетевой поток может принимать одновременно, а net_throttle_action указывает, сколько запросов обрабатывать за одну итерацию. По умолчанию ограничений нет. Ограничение сетевого потока имеет смысл в сценариях высокой нагрузки.
Одним из часто неправильно понимаемых аспектов является связь между max_children и dist_threads . dist_threads отвечает за многопоточность некоторых операций, таких как локальный distributed индекс, snippets с load_files или команда CALL PQ . Однако эти потоки являются подпроцессами и не учитываются директивой max_children , которая считает только основные потоки запросов.