Когда один и тот же облачный Mac одновременно выполняет сборку Xcode, тесты, сжатие журналов и проверку артефактов, проблема обычно заключается не в одном полностью вышедшем из-под контроля процессе. Чаще несколько вспомогательных задач, которые могут завершиться позже, конкурируют с критически важной сборкой за процессорное время, диск и пропускную способность памяти. В результате время компиляции становится нестабильным, тесты завершаются по тайм-ауту, а удалённый рабочий стол реагирует с задержкой. Поэтому сначала нужно отделить критический путь от фоновых задач, а не без разбора снижать приоритет всех процессов.
Сначала создайте сопоставимую базовую линию процессов
Выполните типичную сборку без других задач, а затем повторите её при одновременном сжатии, проверке и индексировании. В обоих случаях зафиксируйте продолжительность сборки, этап сбоя, а также загрузку CPU, потребление памяти, состояние и значение nice основных процессов.
mkdir -p "$HOME/ci-observe"
ps -axo pid,ppid,user,%cpu,%mem,state,nice,etime,command \
| sort -k4 -nr \
| head -n 40 \
> "$HOME/ci-observe/processes.txt"
iostat -w 2 -c 15 > "$HOME/ci-observe/iostat.txt"
vm_stat 2 15 > "$HOME/ci-observe/vm-stat.txt"
Выборка должна охватывать реальное рабочее пространство и фактический объём зависимостей, но не следует включать первоначальную загрузку зависимостей в одну базовую линию со стабильной сборкой. Если сборка замедляется, iostat постоянно показывает высокую активность, а процесс сжатия заметно загружает CPU, есть основания скорректировать вспомогательные задачи. Если же основная проблема связана с нехваткой памяти или чрезмерным параллелизмом тестов, одного изменения приоритета обычно недостаточно.
Приоритет — это подсказка планировщику, а не ограничение ресурсов. Он помогает определить, какая задача должна выполняться раньше, но не заменяет управление параллелизмом и не гарантирует процессу фиксированную долю CPU.
Разделите задачи по их роли в критическом пути
Процессы CI можно разделить на три категории. К первой относятся компиляция, компоновка, тестирование и экспорт артефактов. Они определяют завершение конвейера и должны сохранять обычный приоритет. Вторая категория включает сжатие журналов, создание контрольных сумм и упорядочивание старых артефактов. Эти задачи необходимо завершить, но обычно им не нужно опережать сборку. К третьей категории относятся индексирование в редакторе, интерактивный анализ и временная диагностика — их следует запускать только при необходимости.
Не устанавливайте единое правило по имени инструмента
Один и тот же инструмент может относиться к разным путям. Например, tar, распаковывающий обязательную зависимость перед сборкой, выполняет критическую задачу, а при сжатии журналов после сборки — фоновую. Оценивать нужно по следующим критериям: блокирует ли задача следующий этап, должен ли её сбой останавливать конвейер и используется ли её результат текущей задачей немедленно.
Классификацию рекомендуется закреплять в конфигурации конвейера, а не поручать оператору выполнять её вручную. Тогда при проверке сценария сразу видно, для каких команд снижен приоритет, а новые задачи не будут по умолчанию наследовать неподходящую политику.
Оборачивайте фоновые задачи с помощью nice и taskpolicy
nice изменяет значение планировочного приоритета процесса: положительное значение означает, что процесс охотнее уступает возможность выполнения. Обычный пользователь может понизить приоритет запущенного им процесса, но не может произвольно повысить его. В macOS команда taskpolicy -b переводит процесс в фоновый режим планирования, причём её действие не ограничивается CPU. Поэтому применять её следует только к задачам, которые точно не блокируют критический путь.
Следующий сценарий запускает проверку контрольных сумм в фоновом режиме с низким приоритетом, сохраняет обычную политику для сборки Xcode и отдельно получает коды завершения обеих задач:
#!/bin/zsh
set -u
mkdir -p Artifacts Logs
taskpolicy -b nice -n 10 /bin/sh -c \
'find Artifacts -type f -print0 | xargs -0 shasum -a 256 > Logs/checksums.txt' &
audit_pid=$!
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
build > Logs/xcodebuild.log 2>&1
build_rc=$?
wait "$audit_pid"
audit_rc=$?
if (( build_rc != 0 )); then
exit "$build_rc"
fi
exit "$audit_rc"
Не применяйте taskpolicy -b ко всему CI Runner. Иначе вместе с ним будет снижен приоритет компилятора, тестов и дочерних процессов. Весь конвейер замедлится, а определить конкретную задачу, вызвавшую проблему, будет сложно. Обёртку следует размещать как можно ближе к отдельной фоновой команде.
Правильно используйте QoS в коде приложения
Если вспомогательную работу выполняет собственный инструмент на Swift, задавайте QoS очереди непосредственно в коде, а не пытайтесь определить назначение процесса после его запуска. Для инициированной пользователем работы, результата которой он ожидает, подходит .userInitiated. Для несрочной обработки журналов, инвентаризации кеша и подобных операций можно использовать .utility. Класс .background предназначен только для задач, завершения которых пользователь не ожидает и задержка которых не влияет на текущий результат.
let queue = DispatchQueue(
label: "com.surevm.ci.artifact-audit",
qos: .utility
)
queue.async {
auditArtifacts()
}
Не назначайте всем очередям .userInteractive в надежде ускорить работу. Слишком высокий QoS заставляет вспомогательные задачи конкурировать с критической работой за ресурсы планировщика и может привести к инверсии приоритетов: высокоприоритетная задача будет ожидать блокировку, удерживаемую очередью с низким приоритетом. При проверке следует учитывать общие блокировки, последовательные очереди и синхронные вызовы.
Проверьте результат и задайте условия отката
После изменения настроек повторите сборку с одинаковыми входными данными не менее трёх раз и сравните медианное время, место сбоя и показатели ресурсов. Одного ускорившегося запуска недостаточно, чтобы признать политику эффективной. Также убедитесь, что фоновые задачи, включая сжатие журналов и создание контрольных сумм, полностью завершились: успешная критическая сборка не должна скрывать сбой вспомогательной задачи.
Во время приёмки последовательно проверьте следующее:
- Процессы компиляции, компоновки и тестирования не наследуют фоновую политику.
- При сбое фоновой задачи возвращается ненулевой код завершения.
- Количество конвейеров, одновременно выполняемых Runner, не превышает возможности узла.
- До и после изменения используются одинаковый код, файл блокировки зависимостей и версия Xcode.
- Работа удалённого рабочего стола не служит единственным критерием: необходимо сохранять выборки процессов и операций ввода-вывода.
- После перезагрузки узла обёртки приоритетов по-прежнему автоматически применяются сценарием конвейера.
Если после снижения приоритета вспомогательные задачи не успевают завершиться до следующей сборки, проблема уже сместилась от конкуренции за ресурсы к недостаточной пропускной способности. В этом случае следует сократить количество фоновых задач, сузить область обработки или перенести их в отдельный этап после завершения сборки, а не продолжать увеличивать значение nice. Конкретные доступные конфигурации узлов SureVM следует проверять в панели управления. Однако независимо от выбранной конфигурации сначала определить критический путь и лишь затем менять политику планирования обычно проще как для проверки, так и для отката.
Часто задаваемые вопросы
Ограничивают ли nice и taskpolicy максимальную загрузку CPU?
Нет. Они меняют предпочтения планировщика и режим фоновой работы, но не задают жёсткую квоту. Для контроля нагрузки также ограничивают параллельные сборки, тестовые сегменты и фоновые задания.
Какие задания CI безопасно переводить в фоновый режим?
Обычно это упаковка журналов, вычисление контрольных сумм и очистка старых артефактов. Компиляцию, линковку, тесты и их прямые зависимости понижать не следует.
Выбирайте выделенные физические узлы Mac для сборки, разработки и экспериментов
Сравните две конфигурации Mac mini M4, четыре срока аренды и пять доступных узлов, а затем выберите вариант под свой рабочий процесс.