Как мы переделали системные потоки в Manticore Search

Недавно мы переделали наши системные потоки. Поскольку мы ответвились от Sphinx, ранее searchd выполнял все системные задачи в стиле “часто просыпайся”. Каждый сервис работал в выделенном потоке, который просыпался каждые 50 мс и проверял, есть ли у него что-то делать или нет. Это означает, что даже бездействующий демон просыпается 200 раз в секунду “просто чтобы проверить” задание. Наличие 6 таких задач приведет к 1200 пробуждениям в секунду, и это стало заметно, особенно для клиентов на Amazon AWS, которые учитывают использование ЦП. Эти внутренние задачи были:

  • простая ротация индексов
  • пингование агента
  • предзагрузка индекса
  • сброс атрибутов индекса
  • сброс индекса RealTime
  • и сброс бинарных журналов

Все они довольно редки (например, сброс RT может происходить раз в 10 часов), поэтому проверка их 200 раз в секунду - это просто трата ресурсов ЦП. Также все эти задачи по своей природе не являются интенсивными (например, добавление текста в огромный журнал), а просто периодические действия (повторение одной и той же задачи периодически). Учитывая этот стиль действий, мы полностью переписали всю систему:

  1. Во-первых, мы добавили один поток, который обслуживает таймеры, где запланировано каждое действие.
  2. Во-вторых, мы добавили пул потоков для выполнения самих действий.
  3. Таким образом, в конечном итоге сервисное действие (задача) запланировано, и когда таймер срабатывает, оно перемещается в пул потоков, а затем выполняется.
  4. По завершении оно удаляется, так что периодические задачи просто пересчитывают себя в конце и производят совершенно новую задачу.

При таком подходе нет множества выделенных сервисных потоков, только один поток таймера. И он, в свою очередь, не просыпается каждые 20 мс, а имеет двоичную кучу с временными пределами и пробуждается только в указанный период по самому раннему таймеру в очереди. Пул потоков, в свою очередь, может запускать до 32 потоков параллельно, но на самом деле работает только один, иногда два. Каждый поток имеет предопределенный период бездействия (10 минут), после чего он просто завершает работу, так что в случае “нет задач” ничего не работает бездействуя (даже поток таймера инициализируется “ленивым” способом, т.е. запускается только при необходимости запланировать фактическую задачу). В случае очень редких (>10 минут период) задач пул рабочих потоков также забрасывается, так что рабочие потоки создаются только тогда, когда есть что-то делать. Все сервисные потоки, таким образом, удаляются и больше не нагружают ЦП сотни раз в секунду.

Наиболее обещающим изменением такого подхода является новое поведение задачи “пинг”. В прошлом мы собирали все хосты агентов и каждый интервал пинга отправляли команду “ping” к ним одной партией. Таким образом, если один хост работал медленно, вся партия тоже работала медленно. Кроме того, это не имело отношения к реальному состоянию хостов - например, когда вы загружаете хост запросами, нет необходимости пинговать его отдельно, так как сами запросы дают полную статистику о состоянии хоста. Теперь пинг конкретный: он запланирован для каждого хоста отдельно и зависит от фактического last_answer_time каждого из них. Если хост медленный - только его задача пинга будет ожидать, другие будут работать нормально в своё время. Если хост загружен - его last_answer_time будет монотонно обновляться, так что фактический пинг не произойдет, если с момента last_query_time и ping_interval уже произошёл какой-то запрос.

Еще одной особенностью теперь является то, что задачи могут выполняться параллельно. Скажем, сброс индекса может выполняться для любого количества индексов одновременно, а не последовательно, а параллельно. На данный момент это число установлено на 2 задания, но это просто вопрос настройки. Также, когда несколько подобных задач запланированы, теперь мы можем ограничить их количество. Скажем, “malloc_trim” не имеет смысла планировать больше одного раза, так что это своего рода “синглтон” - если одно запланировано, то другая попытка запланировать будет отклонена.

Следующая функция, добавленная с такой управлением задачами, вытекает из того факта, что теперь все задачи запланированы/планируются в одной очереди (а не в различных партиях потоков), и мы точно знаем, когда они будут выполняться. Таким образом, теперь такую статистику можно отображать, и это делается путем добавления ‘debug sched’, ‘debug tasks’ и ‘debug systhreads’.

Первый показывает двоичную кучу таймеров: наивысшее значение указывает на следующий таймаут и связанную с ним задачу; другие значения следуют за ним (хотя они сейчас отображаются как сырая двоичная куча; их можно отсортировать, если такая необходимость возникнет).

‘debug tasks’ показывает все задачи, зарегистрированные в демоне, с их статистикой и свойствами (сколько таких задач может выполняться параллельно; сколько может быть запланировано; сколько в данный момент выполняется, сколько времени потрачено на задачу, когда она была последний раз завершена, сколько раз она была выполнена, сколько было отклонено из-за перегрузки очереди и сколько сейчас находится в очереди).

Последний ‘debug systhreads’ отображает состояние рабочих потоков, такие как внутренний id, id потока, время последнего запуска, общее время ЦП, общее количество тактов и выполненных заданий, сколько времени заняло последнее задание и как долго рабочий находится в бездействии. Как упоминалось, бездействие в течение 10 минут приводит к остановке рабочего потока.

Обновитесь до Manticore 3.1.0 или более новой версии , чтобы воспользоваться этим изменением

Установить Manticore Search

Установить Manticore Search