⚠️ Эта страница автоматически переведена, и перевод может быть несовершенным.

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

author image
View as markdown

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

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

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

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

При таком подходе нет множества выделенных сервисных потоков, а лишь один поток-таймер. И он, в свою очередь, не просыпается каждые 20 мс, а использует бинарную кучу с тайм-аутами и просыпается только в момент, указанный самым ранним таймером в очереди. Пул потоков, в свою очередь, может запускать до 32 потоков параллельно, но на практике одновременно работает лишь один, иногда два. У каждого потока есть заданный период простоя (10 мин), после которого он просто завершается, поэтому при отсутствии задач ничего вхолостую не работает (даже поток-таймер инициализируется «лениво», т. е. запускается, только когда нужно запланировать реальную задачу). В случае очень редких задач (период более 10 минут) пул рабочих потоков тоже закрывается, и рабочие потоки создаются, только когда есть работа. Таким образом, все сервисные потоки удалены и больше не дёргают CPU сотни раз в секунду.

Самым перспективным изменением такого подхода стало новое поведение задачи «ping». Раньше мы собирали все хосты-агенты и на каждом интервале пинга отправляли команду «ping» им сразу группой. Поэтому, если один хост был медленным, вся группа замедлялась. Кроме того, это не имело отношения к реальному состоянию хостов — например, когда вы загружаете хост запросами, нет необходимости отдельно пинговать его, так как сами запросы предоставляют полную статистику о состоянии хоста. Теперь пинг конкретен: он планируется для каждого хоста отдельно и привязан к реальному времени last_answer_time каждого из них. Если хост медленный — только его задача ping будет ждать, остальные будут работать нормально в своё время. Если хост перегружен — его last_answer_time будет монотонно обновляться, поэтому реальный ping не произойдёт, если с момента last_query_time уже был выполнен запрос и прошёл интервал ping_interval.

Ещё одна новая возможность — задачи могут работать параллельно. Например, сброс индексов может выполняться для любого количества индексов одновременно, а не последовательно. Сейчас это число установлено в 2 задачи, но это лишь вопрос настройки. Кроме того, когда запланировано несколько похожих задач, теперь мы можем ограничить их количество. Например, «malloc_trim» не имеет смысла планировать более одного раза, поэтому это своего рода «singleton» — если одна задача уже запланирована, попытка запланировать ещё одну будет отклонена.

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

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

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

Последняя команда «debug systhreads» отображает состояние рабочих потоков: внутренний идентификатор, идентификатор потока, время последнего запуска, общее время CPU, общее количество тиков и выполненных задач, сколько времени заняла последняя задача и как долго поток находится в простое. Как уже упоминалось, простой в течение 10 минут приводит к остановке рабочего потока.

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

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

Установите Manticore Search одной командой в Linux или macOS:

curl https://manticoresearch.com | sh

Для расширенных вариантов установки см. полное руководство по установке и документацию .

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