MAATRIX / Блог / Governor стоял в powersave: полгода сервер работал на половине частоты

Governor стоял в powersave: полгода сервер работал на половине частоты

MAATRIX

Есть класс инцидентов, которые не роняют сервис, а просто медленно съедают ваше время: всё работает, ошибок нет, алерты молчат, а люди вокруг жалуются, что стало «как-то тормознее». Именно так выглядела история с governor'ом CPU, который полгода простоял в режиме powersave, пока команда искала причину в коде, базе и сети — везде, кроме одного файла в /sys.

С чего всё началось: жалобы без инцидента

Первые сигналы появились не в мониторинге, а в чатах поддержки: клиенты писали, что отчёты стали формироваться дольше, а API «подвисает» на пиках. Формальных инцидентов не было — SLA по доступности не нарушался, 5xx не росли, алерты по загрузке CPU молчали. Именно поэтому проблему списали на рост нагрузки: трафик за полгода действительно вырос процентов на двадцать, и первая гипотеза — «мы просто выросли из текущего железа» — выглядела разумной и никого не тревожила.

Задача была передана в бэклог с пометкой «посмотреть при следующем апгрейде». Проблема в том, что «следующий апгрейд» откладывался, а жалобы продолжали копиться — медленно, по одной-две в неделю, без всплесков, поэтому ни один алерт по аномалиям на них бы не сработал, даже если бы он был настроен.

Переломный момент наступил, когда ночной ETL, который стабильно укладывался в 40 минут, стал занимать больше полутора часов — и это уже задевало окно перед утренней выгрузкой для смежной команды. Вот тут задача получила приоритет, и её начали разбирать по-настоящему.

Что показывали метрики — и что мы отбросили

Первым делом посмотрели на очевидное — загрузку CPU в Grafana (node_exporter, node_cpu_seconds_total). Загрузка была в норме: 35–55% в среднем, без длительных полок под 100%. Это само по себе было странно: если сервер «не успевает», обычно ждёшь высокую утилизацию, а не среднюю.

Дальше по очереди проверили и отбросили несколько гипотез:

  • Диск. iostat -x 1 показывал низкий %util и адекватный await на NVMe — узкого места по I/O не было.
  • Сеть. Пинг в норме, ss -s не показывал накопления соединений, ретрансмиты по netstat -s в пределах обычного фона.
  • Шумный сосед. Сервер физически выделенный, но по старой памяти проверили vmstat 1 на предмет steal time — он был нулевым. У тех, кто арендует VPS, этот пункт стоит проверять в первую очередь — механику мы разбирали в статье про steal time и «соседа», который ест ваш CPU.
  • GC-паузы в JVM. Включили -Xlog:gc* на сутки — длинных пауз не нашли, распределение осталось таким же, как раньше.
  • Деградация плана запросов в БД. Прогнали проблемные запросы через EXPLAIN (ANALYZE, BUFFERS) — планы не изменились.
  • Термотроттлинг. Проверили dmesg на предмет mce, посмотрели датчики через sensors — температура в норме.

Ни одна из этих версий не объясняла главного: почему при умеренной загрузке CPU растёт именно *время выполнения* работы, а не количество работы в очереди. Это наблюдение и стало зацепкой для следующего шага.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Улика, которую все проверяют последней: реальная частота

Загрузка CPU в процентах — это доля времени, которое ядро было занято, а не то, сколько реальных вычислений оно успело сделать за это время. Если ядро работает на пониженной частоте, оно может быть «занято на 100%» и при этом выполнять вдвое меньше инструкций в секунду, чем на номинальной частоте. Проценты утилизации эту разницу не показывают вообще — именно поэтому дашборд с CPU% выглядел спокойным, пока реальная производительность просела.

Кто-то из команды вспомнил похожий класс проблем — троттлинг из-за отключённого турбобуста после переезда на новое железо — и просто из любопытства запустил команду, которую обычно смотрят при подозрении на такие вещи:

grep -E "^model name|^cpu MHz" /proc/cpuinfo | sort -u | head -20

Вывод удивил: у процессора с базовой частотой в районе 2,4–2,5 ГГц и турбо-частотой заметно выше, все ядра стабильно показывали текущую частоту около минимальной — то есть чуть больше 1 ГГц, независимо от того, сколько запросов обрабатывалось в этот момент. Частота не «плавала» под нагрузкой, как это должно быть с адаптивным управлением, а была прибита к нижней границе.

Дальше — прямая проверка через cpupower:

cpupower frequency-info
analyzing CPU 0:
  driver: acpi-cpufreq
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency: 10.0 us
  hardware limits: 1.00 GHz - 3.50 GHz
  available frequency steps: 3.50 GHz, 2.80 GHz, 1.00 GHz
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance, schedutil
  current policy: frequency should be within 1.00 GHz and 3.50 GHz.
                  The governor "powersave" may decide which speed to use
                  within this range.
  current CPU frequency: 1.00 GHz (asserted by call to hardware)

Governor стоял в powersave, а не в performance или schedutil, как ожидалось для продакшн-сервера. Для драйвера acpi-cpufreq (в отличие от intel_pstate с аппаратным управлением P-состояниями через HWP) governor powersave — это буквально «держать минимальную частоту из доступных, пока не появится явная причина повысить её», и в связке со старой моделью governor'ов ondemand/conservative он вёл себя предсказуемо плохо под резкими всплесками нагрузки: не успевал разгоняться, пока запрос уже не отработал на минимуме.

Как это устроено: governor, драйвер и почему цифры «в среднем нормальные»

Чтобы понимать, что чинить, стоит разложить систему управления частотой на три слоя.

Драйвер cpufreq — то, что реально умеет менять частоту ядра: acpi-cpufreq (универсальный, через ACPI P-states), intel_pstate (нативный для Intel, с аппаратным HWP) или amd-pstate (аналог для современных AMD EPYC/Ryzen). Драйвер определяется автоматически при загрузке ядра и виден в выводе cpupower frequency-info в поле driver.

Governor — политика, по которой драйвер выбирает частоту в разрешённом диапазоне:

GovernorПоведение
performanceВсегда держит максимальную доступную частоту
powersaveНа acpi-cpufreq — держит минимальную частоту; на intel_pstate/amd-pstate с HWP — по факту адаптивный, несмотря на название
ondemandРазгоняется резко при росте нагрузки, но с задержкой реакции и порогами
conservativeРазгоняется постепенно, ступенчато — мягче, но медленнее ondemand
schedutilСовременный вариант, встроен в планировщик задач ядра, реагирует быстрее ondemand

Здесь и кроется ловушка: слово «powersave» звучит безобидно и ассоциируется с ноутбуками, а не с потерей половины производительности сервера. На Intel-серверах с intel_pstate и включённым HWP governor с тем же именем ведёт себя вполне адаптивно — поэтому часть инженеров, кто раньше видел powersave только на таком железе, даже не заподозрили в нём проблему. Но здесь драйвером был acpi-cpufreq (так исторически собирался образ ОС), а для него powersave — это фиксация на минимуме, без адаптивности.

Третий слой — то, что должно принудительно выставлять нужный governor при загрузке: юнит systemd, скрипт в /etc/rc.local, пакет cpufrequtils/cpupower с конфигом по умолчанию, либо tuned с профилем вроде throughput-performance или latency-performance. Если этого слоя нет или он сломан — governor остаётся тем, что выставил драйвер или дистрибутив по умолчанию, и это не обязательно то, что вам нужно.

Как governor оказался в powersave: реконструкция

Восстановить точный момент по логам не вышло — событие было слишком старым, а journalctl хранил данные за меньший период. Но по совокупности косвенных следов (дата последнего изменения плейбука в Ansible-репозитории, дата последнего пересоздания образа) картина сложилась следующая.

Старый образ сервера явно фиксировал governor через /etc/rc.local:

for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
  echo performance > $cpu/cpufreq/scaling_governor
done

При очередном обновлении провижининг-пайплайна rc.local заменили на юнит systemd — решение само по себе правильное, но при переносе логики строчку с governor'ом потеряли: она не попала в новый Ansible-плейбук, потому что автор рефакторинга ориентировался на список systemd-юнитов, а не на построчный diff старого скрипта. Проверка «сервис поднялся, порт слушает» формально проходила, и рефакторинг замёржили без замечаний.

Дальше сервер прожил несколько перезагрузок (ядерные обновления, плановые работы), и governor каждый раз возвращался к значению по умолчанию для acpi-cpufreq — на этом дистрибутиве это powersave, а не более безопасный ondemand. Полгода — это сумма нескольких перезагрузок за период, каждая из которых молча возвращала governor в неправильное состояние, а проверить это было просто некому — такого чека не существовало.

Что мы изменили: фикс, персистентность и мониторинг

Немедленный фикс занял одну команду на каждый узел:

cpupower frequency-set -g performance

Или для управления через tuned (что для серверов обычно удобнее, потому что профиль переживает обновления пакетов):

tuned-adm list
tuned-adm profile throughput-performance
tuned-adm active

Но разовая команда не переживает перезагрузку, поэтому дальше — три вещи, которые закрывают проблему системно:

  1. Персистентность настройки. Вернули явную фиксацию governor'а, но уже через systemd-юнит, а не через rc.local, чтобы она попадала в тот же слой конфигурации, который сейчас реально ревьюят:
# /etc/systemd/system/cpu-governor.service
[Unit]
Description=Set CPU governor to performance
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/bin/cpupower frequency-set -g performance
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now cpu-governor.service

Для серверов, где governor должен быть управляем через tuned, вместо этого юнита проще держать нужный профиль в /etc/tuned/active_profile и проверять его тем же способом, каким проверяется любая другая критичная конфигурация — линтером в CI провижининг-репозитория.

  1. Проверка после каждого провижининга. В пайплайн добавили шаг, который после раскатки сервера явно читает scaling_governor на каждом ядре и падает, если значение отличается от ожидаемого:
for f in /sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_governor; do
  cat "$f"
done | sort -u

Если вывод — не единственная строка performance (или нужный профиль tuned), провижининг считается не прошедшим проверку.

  1. Метрика в мониторинге. Добавили в Prometheus сбор текущей частоты ядра через node_exporter (коллектор cpufreq, флаг --collector.cpufreq) и алерт на то, что node_cpu_scaling_frequency_hertz долго держится около минимума. Метрика не идеальна — на простаивающем сервере низкая частота бывает нормой, поэтому алерт учитывает ещё и загрузку CPU одновременно (низкая частота вместе с растущей очередью запросов — уже повод посмотреть). О том, какие метрики хостовой машины вообще стоит держать под наблюдением, мы разбирали в статье про мониторинг гипервизора.

После фикса ночной ETL вернулся в прежние временные рамки, а жалобы на «подвисания» на пиках прекратились — без единой строчки изменений в приложении.

Где это ещё стоит проверить

Governor в неправильном состоянии — не экзотика, а типовая проблема, которая тихо переживает миграции, обновления дистрибутива и смену провижининг-инструментов. Стоит явно проверить его в нескольких ситуациях:

  • После смены базового образа ОС или крупного рефакторинга Ansible/Terraform-плейбуков — именно в момент переноса логики между форматами конфигурации теряются строчки вроде той, что была в этой истории.
  • После обновления ядра, если драйвер cpufreq мог смениться (например, при смене режима виртуализации BIOS с intel_pstate=disable в загрузочных параметрах).
  • На виртуальных машинах governor часто вообще не влияет ни на что — реальное управление частотой находится на стороне гипервизора, а значения scaling_governor внутри гостевой ОС могут быть декоративными. Какие параметры CPU для виртуалок реально работают, мы разбирали в статье про настройку CPU для виртуальной машины. Если критично, чтобы governor и частота были ровно тем, что вы выставили, а не абстракцией поверх чужого гипервизора, — это один из аргументов в пользу выделенного сервера; сравнение мы делали в статье про выбор между VPS и выделенным сервером.
  • После замены железа поставщиком (плановая замена ноды, гарантийный ремонт) — новая машина не обязана унаследовать те же настройки, что и старая, если они не зафиксированы в конфигурации, а не «руками один раз».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Как быстро проверить, не стоит ли governor в powersave, если нет времени разбираться глубоко?

Выполните cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor — если хотя бы часть ядер показывает не то значение, которое вы ожидаете для продакшна (обычно performance или schedutil), и при этом драйвер (поле driver в cpupower frequency-info) — acpi-cpufreq, это повод присмотреться внимательнее.

Governor powersave — это всегда плохо для сервера?

Нет, это зависит от драйвера. На intel_pstate или amd-pstate с включённым аппаратным управлением частотой (HWP) governor powersave может вести себя вполне адаптивно и разгоняться под нагрузкой. Само название governor'а не говорит однозначно о его реальном поведении — смотреть нужно на пару «драйвер + governor», а не на governor отдельно.

Почему мониторинг загрузки CPU не показал проблему сразу?

Потому что процент утилизации ядра отражает долю времени, когда оно было занято, а не количество реально выполненной работы за это время. Ядро на пониженной частоте может быть «занято на 100%», выполняя меньше инструкций в секунду, — и с точки зрения графика загрузки это будет выглядеть как норма.

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

Отчасти — по росту времени выполнения одинаковых по объёму фоновых задач при стабильной загрузке CPU. Но без явного алерта на это систематически никто не смотрит, поэтому метрика текущей частоты ядра надёжнее, чем вывод аномалии из косвенных наблюдений.

Governor нужно проверять на каждом ядре отдельно или значения всегда одинаковые?

В норме на серверах с одним cpufreq-драйвером на все ядра значения совпадают, но после ручных вмешательств или частичных обновлений конфигурации встречаются случаи, когда часть ядер осталась на старом governor'е, а часть — уже на новом. Поэтому проверка через for по всем cpu[0-9]* надёжнее, чем чтение одного ядра и экстраполяция на все остальные.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →