15%

Збережіть 15% на всі хостинг-послуги

Перевірте свої навички і отримайте Знижку на будь-який план хостингу

Використовуй код:

Skills
Почати
12.12.2023

Голодування процесів в операційних системах: причини, механізми та рішення виробничого рівня

Голодування процесу виникає, коли процес безстроково позбавляється процесорного часу, пам’яті або пропускної здатності I/O, необхідних для виконання — не тому, що ресурси відсутні, а тому, що політика планування постійно надає перевагу іншим процесам. На відміну від взаємного блокування, де всі конкуруючі процеси заблоковані, голодування дозволяє системі виглядати функціональною, тоді як певні робочі навантаження непомітно деградують або зупиняються.

Ця відмінність має операційне значення: голодуючий процес не генерує помилок на рівні ядра, не створює дампів аварійного завершення і може не спрацьовувати стандартні порогові значення сповіщень — що робить його одним із найнебезпечніших порушень продуктивності в багатоорендних середовищах і серверних середовищах з високим рівнем паралелізму.

Що насправді означає голодування на рівні ядра

Термін запозичений з ресурсної екології: процес «голодує», коли він постійно програє конкуренцію за обмежений ресурс. У сучасних операційних системах Linux Completely Fair Scheduler (CFS), черги пріоритетів Windows NT та планувальник BSD ULE реалізують механізми запобігання голодуванню — проте воно все одно виникає у виробничих середовищах за певних умов.

На рівні ядра голодування проявляється як процес, чий віртуальний час виконання (у термінології CFS) або час очікування зростає необмежено, так і не будучи вибраним для виконання. Процес залишається у стані TASK_RUNNING — він готовий і має право на виконання — але планувальник ніколи не надає йому квант CPU, оскільки завдання з вищим пріоритетом або ті, що запускаються частіше, завжди його витісняють.

Ключова технічна відмінність:

  • Взаємне блокування: Два або більше процесів взаємно заблоковані, кожен очікує ресурс, утримуваний іншим. Система не робить жодного прогресу в цих завданнях.
  • Голодування: Один або більше процесів постійно обходяться планувальником. Інші процеси продовжують виконуватися нормально.
  • Активне блокування: Процеси не заблоковані, але безперервно змінюють стан у відповідь один на одного, не досягаючи реального прогресу.

Першопричини голодування процесів

Розуміння голодування вимагає вивчення конкретних механізмів, що його спричиняють, а не просто переліку «обмежених ресурсів» як причини.

1. Статична інверсія пріоритетів без старіння

Більшість планувальників на основі пріоритетів призначають кожному процесу фіксований або напівфіксований пріоритет. Якщо процес з низьким пріоритетом постійно витісняється потоком завдань із середнім і високим пріоритетом, він ніколи не виконується. Критичним режимом відмови тут є відсутність старіння — техніки, за якої ефективний пріоритет процесу поступово підвищується зі збільшенням часу очікування. Без старіння фонове завдання з низьким пріоритетом на завантаженому сервері може чекати нескінченно.

У Linux діапазон значень nice (від -20 до +19) та пріоритети реального часу (SCHED_FIFO, SCHED_RR) створюють саме такий ризик. Процес, що виконується під SCHED_FIFO з пріоритетом 99, витіснятиме кожен процес SCHED_OTHER на тому самому ядрі CPU, доки добровільно не поступиться або не заблокується.

2. Несправедливе чергування в планувальниках I/O

Голодування CPU добре задокументоване, але голодування I/O є не менш руйнівним і часто ігнорується. Планувальник I/O Linux (історично CFQ, тепер BFQ або mq-deadline залежно від версії ядра та типу сховища) керує порядком обслуговування запитів до блокових пристроїв. При інтенсивних послідовних операціях запису — характерних для серверів баз даних і додатків із великою кількістю журналів — планувальник I/O може знижувати пріоритет запитів випадкового читання від інших процесів, фактично позбавляючи їх доступу до диска.

Це часта проблема в середовищах VPS Hosting, де кілька орендарів спільно використовують базову інфраструктуру зберігання даних, а конкуренція за I/O є реальною операційною проблемою.

3. Тиск пам’яті та OOM Killer

Коли фізична RAM вичерпана, механізм Out-Of-Memory (OOM) killer ядра Linux вибирає процес для завершення на основі oom_score. Хоча технічно це завершення, а не голодування, попередній стан — коли процес неодноразово вивантажується на диск і ніколи не отримує достатньо резидентної пам’яті для ефективного виконання — є голодуванням пам’яті. Процес технічно виконується, але майже не прогресує через постійні помилки сторінок і операції підкачки.

4. Конкуренція за блокування та голодування м’ютексів

У багатопотокових додатках голодування виникає на рівні примітивів синхронізації. Якщо м’ютекс або семафор використовує несправедливу політику захоплення (останній прийшов — перший обслуговується або випадковий вибір серед потоків, що очікують), певний потік може постійно обходитися, навіть якщо блокування часто звільняється. Це відрізняється від планування на рівні ОС і відбувається повністю в просторі користувача або підсистемі синхронізації ядра.

5. Голодування пропускної здатності мережі

У контейнеризованих і віртуалізованих середовищах процес або контейнер, що споживає всю доступну пропускну здатність мережі, може позбавити інші процеси мережевого I/O. Без формування трафіку за допомогою tc (traffic control) та cgroups один некерований процес може монополізувати пропускну здатність NIC.

Голодування проти взаємного блокування проти активного блокування: технічне порівняння

ВластивістьГолодуванняВзаємне блокуванняАктивне блокування
Прогрес системиТак (інші процеси виконуються)Ні (заблоковані процеси зупиняються)Удаваний (реального прогресу немає)
Заблокований станНі (процес може виконуватися)Так (процес очікує ресурс)Ні (процес активний)
Утримуваний ресурсНіТак (кругове утримання та очікування)Ні
Самостійне вирішенняІноді (зі старінням)Ніколи (потрібне втручання)Рідко
Складність виявленняВисока (немає явної помилки)Середня (виявлення циклу)Висока (виглядає як активність)
Основна причинаНесправедлива політика плануванняКругова залежність ресурсівРеактивні цикли зміни стану
Сигнал ядра LinuxВідсутнійВідсутній (можливе м’яке зависання)Відсутній

Як сучасні планувальники вирішують проблему голодування

Linux Completely Fair Scheduler (CFS)

CFS, представлений у ядрі Linux 2.6.23, вирішує проблему голодування шляхом відстеження віртуального часу виконання (vruntime) для кожного процесу. Планувальник завжди вибирає процес з найменшим vruntime — тобто процеси, що отримали менше процесорного часу, систематично отримують пріоритет. Такий підхід робить чисте голодування CPU майже неможливим у CFS для процесів SCHED_OTHER.

Однак CFS не захищає від голодування з боку процесів реального часу. Будь-який процес, запланований під SCHED_FIFO або SCHED_RR, витісняє всі завдання SCHED_OTHER. Параметр ядра /proc/sys/kernel/sched_rt_runtime_us (за замовчуванням: 950 000 мікросекунд на секунду) резервує 5% процесорного часу для завдань, що не є завданнями реального часу, саме для запобігання цьому.

Старіння пріоритетів

Класичні алгоритми старіння збільшують ефективний пріоритет процесу на фіксовану величину за кожен цикл планування, проведений в очікуванні. Як тільки ефективний пріоритет досягає найвищого рівня, процесу гарантується виконання. Після виконання його пріоритет скидається до базового значення. Це класичне рішення для голодування на основі пріоритетів, реалізоване в різних формах у Windows NT, Solaris та старіших планувальниках Linux.

Справедливе чергування та зважене справедливе чергування (WFQ)

Для мережевих ресурсів та ресурсів I/O Weighted Fair Queuing призначає кожному потоку або процесу частку пропускної здатності пропорційно до його ваги. Навіть якщо потік з високою вагою генерує більше трафіку, потокам з низькою вагою гарантується мінімальна швидкість обслуговування. Linux реалізує це через дисципліни Hierarchical Token Bucket (HTB) та Stochastic Fair Queuing (SFQ) у підсистемі tc.

Діагностика голодування у виробничих системах Linux

Виявлення голодування вимагає одночасної кореляції кількох джерел даних.

Аналіз планування CPU

# Check per-process CPU wait time and scheduling statistics
cat /proc/<PID>/schedstat

# Monitor scheduler latency with perf
perf sched latency --sort max

# Identify processes with high voluntary/involuntary context switches
pidstat -w 1 10

# Check real-time process priorities that may be starving others
ps -eo pid,comm,cls,pri,ni --sort=-pri | head -20

Вивід schedstat надає сукупний час, проведений процесом в очікуванні в черзі виконання (run_delay у наносекундах) — прямий показник голодування планування.

Індикатори голодування пам’яті

# Check swap activity — high si/so values indicate memory starvation
vmstat 1 10

# Identify processes with high major page fault rates
pidstat -r 1 10

# Check OOM kill history
dmesg | grep -i "oom|killed process"

# Inspect per-process memory pressure
cat /proc/<PID>/status | grep -E "VmRSS|VmSwap|VmPeak"

Виявлення голодування I/O

# Per-process I/O wait statistics
iotop -b -n 5

# Block device queue depth and wait times
iostat -x 1 5

# Check I/O scheduler in use for each block device
cat /sys/block/sda/queue/scheduler

# Identify processes blocked on I/O
ps aux | awk '$8 ~ /D/ {print}'

Процеси у стані D (неперериваний сон) заблоковані на I/O. Стійка наявність процесів у стані D є вагомим індикатором голодування I/O або насичення підсистеми зберігання.

Виробничі рішення та стратегії пом’якшення

Впровадження cgroups v2 для ізоляції ресурсів

Control Groups (cgroups v2) забезпечують найбільш надійний механізм запобігання голодуванню в багатопроцесних і контейнеризованих середовищах. Призначаючи явні квоти CPU, пам’яті та I/O групам процесів, ви гарантуєте мінімальний розподіл ресурсів незалежно від навантаження на систему.

# Create a cgroup with CPU weight (higher weight = more CPU share)
mkdir /sys/fs/cgroup/my_service
echo "100" > /sys/fs/cgroup/my_service/cpu.weight

# Set memory limit to prevent memory starvation of other groups
echo "2G" > /sys/fs/cgroup/my_service/memory.max

# Assign process to cgroup
echo <PID> > /sys/fs/cgroup/my_service/cgroup.procs

Вага CPU у cgroups v2 використовує діапазон від 1 до 10000, де за замовчуванням встановлено 100. Група процесів з вагою 200 отримує вдвічі більшу частку CPU порівняно з групою з вагою 100 при конкуренції.

Налаштування планувальника Linux для вашого робочого навантаження

# Increase scheduler migration cost to reduce cache thrashing (latency-sensitive workloads)
echo 500000 > /proc/sys/kernel/sched_migration_cost_ns

# Reduce scheduler granularity for more frequent preemption (throughput workloads)
echo 1000000 > /proc/sys/kernel/sched_min_granularity_ns

# Ensure real-time tasks cannot starve normal tasks
echo 950000 > /proc/sys/kernel/sched_rt_runtime_us

Застосування відповідних політик планування для кожного процесу

# Set a process to batch scheduling (explicitly low-priority, won't starve interactive tasks)
chrt -b -p 0 <PID>

# Set a CPU-intensive background job to idle scheduling class
chrt -i -p 0 <PID>

# Adjust nice value for a running process
renice -n 10 -p <PID>

# Run a new command with reduced priority
nice -n 15 ./my_background_script.sh

Клас SCHED_IDLE (chrt -i) є правильним інструментом для справді фонових завдань — він виконується лише тоді, коли немає жодного іншого процесу, готового до виконання, що повністю виключає його здатність голодувати інші робочі навантаження.

Вибір планувальника I/O

# For NVMe SSDs (low-latency, no rotational penalty): use none or mq-deadline
echo "mq-deadline" > /sys/block/nvme0n1/queue/scheduler

# For HDDs with mixed workloads: use bfq for fairness
echo "bfq" > /sys/block/sda/queue/scheduler

# Make persistent across reboots (add to /etc/udev/rules.d/)
echo 'ACTION=="add|change", KERNEL=="sda", ATTR{queue/scheduler}="bfq"' 
  > /etc/udev/rules.d/60-scheduler.rules

BFQ (Budget Fair Queuing) спеціально розроблений для запобігання голодуванню I/O шляхом гарантування кожному процесу пропорційної частки пропускної здатності диска. Це рекомендований планувальник для середовищ спільного хостингу та серверів баз даних.

Контроль пропускної здатності мережі за допомогою tc

# Create a root HTB qdisc on the primary interface
tc qdisc add dev eth0 root handle 1: htb default 30

# Add a parent class with total bandwidth
tc class add dev eth0 parent 1: classid 1:1 htb rate 1gbit

# Add child classes with guaranteed minimums (prevents starvation)
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 100mbit ceil 1gbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 100mbit ceil 1gbit

# Add SFQ leaf to each class for per-flow fairness
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10

Ця конфігурація гарантує кожному класу мінімум 100 Mbps, дозволяючи пакетне використання до повної ємності каналу 1 Gbps, коли пропускна здатність доступна.

Налаштування надмірного виділення пам’яті та підкачки

# Reduce swappiness to minimize swap-induced memory starvation
echo 10 > /proc/sys/vm/swappiness

# Enable memory overcommit accounting (prevents OOM from surprising processes)
echo 2 > /proc/sys/vm/overcommit_memory

# Set overcommit ratio (total allocatable = RAM * ratio + swap)
echo 80 > /proc/sys/vm/overcommit_ratio

Встановлення vm.swappiness=10 вказує ядру надавати перевагу звільненню кешу сторінок замість вивантаження пам’яті процесів, що значно знижує ймовірність голодування пам’яті при помірному навантаженні.

Голодування у віртуалізованих і контейнеризованих середовищах

На Dedicated Servers під управлінням гіпервізорів (KVM, VMware ESXi, Hyper-V) голодування може виникати на двох різних рівнях:

Голодування на рівні гіпервізора: Віртуальній машині відмовляють у циклах CPU планувальником гіпервізора. KVM використовує CFS хост-ядра для планування vCPU, тобто VM з нижчою вагою частки CPU може голодувати через VM з вищими вагами при конкуренції. DRS (Distributed Resource Scheduler) VMware використовує частки, резервування та обмеження для контролю цього.

Голодування на рівні гостьової ОС: Усередині самої VM застосовуються ті самі динаміки планування на рівні ОС. Контейнеризоване робоче навантаження, що виконується під Docker або Kubernetes без явних обмежень ресурсів, може монополізувати CPU та пам’ять гостьової ОС, голодуючи спільно розміщені контейнери.

Для середовищ Kubernetes завжди визначайте як requests, так і limits у специфікаціях pod:

resources:
  requests:
    cpu: "250m"
    memory: "512Mi"
  limits:
    cpu: "1000m"
    memory: "1Gi"

Значення requests визначає розміщення при плануванні та вагу частки CPU cgroup. Без нього планувальник Kubernetes не має підстав для справедливого розміщення, а середовище виконання контейнерів призначає ваги за замовчуванням (рівні) — що все одно допускає голодування, якщо один контейнер постійно насичує свій ліміт CPU.

Голодування в серверах баз даних і додатків

Рушії баз даних реалізують власні внутрішні планувальники, незалежні від планувальника ОС. PostgreSQL використовує модель «один процес на з’єднання», де кожен фоновий процес конкурує за ресурси ОС у звичайному режимі, але конкуренція за блокування всередині бази даних (блокування на рівні рядків, консультативні блокування) може спричинити голодування на рівні додатка, коли певні запити нескінченно чекають на отримання блокування.

MySQL/InnoDB використовує пул потоків із налаштовуваними обмеженнями паралелізму (innodb_thread_concurrency). Занадто низьке значення спричиняє голодування запитів через чергу потоків, що очікують слотів виконання. Занадто високе значення спричиняє надмірне перемикання CPU. Рекомендоване початкове значення — 2 × number of CPU cores.

Для веб-серверів Nginx та Apache мають різні профілі голодування. Подієво-орієнтована модель Nginx за своєю природою стійка до голодування воркерів, але вичерпання пулу з’єднань до upstream (наприклад, до PHP-FPM або backend API) створює голодування на рівні додатка. MPM prefork Apache може вичерпати свій ліміт MaxRequestWorkers, через що нові з’єднання чекатимуть у черзі нескінченно — це форма голодування з’єднань.

Ці міркування безпосередньо стосуються налаштування VPS з cPanel для робочих навантажень спільного веб-хостингу, де кілька сайтів конкурують за пули воркерів PHP-FPM та ліміти з’єднань MySQL.

Інфраструктура моніторингу для запобігання голодуванню

Реактивна діагностика недостатня для виробничих систем. Проактивний стек моніторингу повинен включати:

Метрики Prometheus + Node Exporter для відстеження:

  • node_schedstat_waiting_seconds_total — сукупний час очікування в черзі виконання CPU на кожен CPU
  • node_vmstat_pgmajfault — основні помилки сторінок, що вказують на тиск пам’яті
  • node_disk_io_time_weighted_seconds_total — насичення черги I/O
  • node_pressure_cpu_waiting_seconds_total — тиск CPU за Linux PSI (Pressure Stall Information)
  • node_pressure_memory_full_seconds_total — час повного зупинення пам’яті за PSI

Linux PSI (доступний починаючи з ядра 4.20) є найбільш прямим індикатором голодування, доступним у ядрі. Він повідомляє відсоток часу, протягом якого завдання були заблоковані в очікуванні ресурсів CPU, пам’яті або I/O:

# Real-time PSI monitoring
cat /proc/pressure/cpu
cat /proc/pressure/memory
cat /proc/pressure/io

Формат виводу: some avg10=X.XX avg60=X.XX avg300=X.XX total=NNNN, де some вказує, що принаймні одне завдання було заблоковане. Значення вище 10–15% для avg60 потребують негайного розслідування.

Для команд, що керують VPS Control Panels або власними серверними стеками, інтеграція метрик PSI у дашборди Grafana забезпечує раннє попередження до того, як голодування погіршить продуктивність для кінцевих користувачів.

Практична матриця рішень: вибір правильного механізму захисту від голодування

СимптомТип ресурсуРекомендований інструментЦіль конфігурації
Фонові завдання ніколи не завершуютьсяCPUSCHED_IDLE або nice +19Усунути конкуренцію фонового CPU
Стрибки затримки інтерактивних операцій під навантаженнямCPUНалаштування CFS + вага CPU cgroups v2Гарантувати частку інтерактивного процесу
Тайм-аути запитів до бази данихCPU + блокуванняinnodb_thread_concurrency, тайм-аут блокуванняОбмежити час очікування блокування
Дискоємні завдання блокують веб-обслуговуванняI/OПланувальник BFQ + cgroups v2 io.weightПропорційний розподіл I/O
OOM kills контейнерів під навантаженнямПам’ятьcgroups v2 memory.min + vm.swappinessГарантувати мінімальну резидентну пам’ять
Мережево-інтенсивний процес голодує іншіМережаHTB + SFQ через tcГарантія пропускної здатності для кожного класу
VM голодує через гіпервізорvCPUРезервування/частки CPU гіпервізораЗарезервувати мінімальні цикли vCPU

Ключові технічні висновки

  • Ніколи не покладайтеся на планування за замовчуванням для серверів зі змішаним навантаженням. Явно класифікуйте процеси за допомогою chrt, nice та cgroups v2 на основі їхньої чутливості до затримки та бізнес-пріоритету.
  • Увімкніть моніторинг PSI (/proc/pressure/*) на всіх виробничих системах Linux. Це найточніший індикатор голодування в реальному часі в ядрі з мінімальними накладними витратами.
  • Використовуйте BFQ для обертових дисків та будь-яких пристроїв NVMe, що обслуговують змішані випадкові/послідовні навантаження в багатоорендних середовищах. Гарантії справедливості варті незначних накладних витрат на пропускну здатність.
  • Встановлюйте запити ресурсів Kubernetes без винятків. Невстановлений requests.cpu — це не «необмежений» — це відповідальність при плануванні, що уможливлює голодування CPU на рівні контейнера.
  • Розрізняйте голодування та взаємне блокування перед втручанням. Завершення та перезапуск голодуючого процесу не усуває базовий дисбаланс планування; це лише тимчасово прибирає симптом.
  • Перевіряйте призначення пріоритетів реального часу (SCHED_FIFO/SCHED_RR) на будь-якій системі, де вони використовуються. Один неправильно налаштований процес реального часу може нескінченно голодувати всі робочі навантаження зі звичайним пріоритетом на ядрі CPU.
  • Для середовищ Shared Web Hosting застосовуйте квоти CPU та I/O для кожного облікового запису на рівні cgroup, а не покладайтеся виключно на обмеження швидкості на рівні додатка.

Часті запитання

У чому різниця між голодуванням і взаємним блокуванням в операційній системі?

Взаємне блокування виникає, коли два або більше процесів постійно заблоковані, кожен утримує ресурс, потрібний іншому — жоден процес не прогресує. Голодування виникає, коли процес постійно обходиться планувальником, незважаючи на готовність до виконання; інші процеси продовжують виконуватися нормально. Взаємне блокування вимагає розриву кругової залежності; голодування вимагає виправлення політики планування, як правило, шляхом впровадження старіння або справедливого чергування.

Як планувальник Linux CFS запобігає голодуванню CPU?

CFS відстежує віртуальний час виконання (vruntime) для кожного процесу і завжди вибирає для виконання процес з найменшим vruntime. Це гарантує систематичний пріоритет процесів, що отримують менше процесорного часу, роблячи нескінченне голодування CPU процесів SCHED_OTHER майже неможливим. Однак процеси реального часу (SCHED_FIFO, SCHED_RR) повністю обходять CFS і все одно можуть голодувати звичайні процеси, якщо параметр sched_rt_runtime_us не встановлено правильно.

Як визначити, чи голодує процес на сервері Linux?

Прочитайте /proc/<PID>/schedstat для перевірки сукупного часу очікування в черзі виконання. Відстежуйте /proc/pressure/cpu для метрик зупинення PSI. Використовуйте perf sched latency --sort max для виявлення процесів з аномально високою затримкою планування. Процеси в постійному стані D, видимі у виводі ps aux, вказують на голодування I/O, а не CPU.

Чи впливає голодування процесів на VPS і хмарні сервери інакше, ніж на фізичні сервери?

Так. На VPS голодування може виникати як на рівні гіпервізора (планувальник гіпервізора відмовляє вашій VM у часі vCPU), так і всередині гостьової ОС. Голодування на рівні гіпервізора невидиме для стандартних інструментів моніторингу ОС і потребує специфічних для гіпервізора метрик або помітного часу крадіжки (%st у виводі top). Високий час крадіжки — як правило, вище 5–10% стабільно — вказує на те, що гіпервізор не надає вашій VM належних циклів vCPU.

Який найшвидший спосіб запобігти голодуванню інших процесів конкретним процесом на завантаженому сервері?

Призначте його до класу планування SCHED_IDLE за допомогою chrt -i -p 0 <PID>. Цей клас виконується лише тоді, коли немає жодного іншого готового до виконання процесу, гарантуючи, що він не може голодувати жодне інше робоче навантаження. Для фонових процесів з інтенсивним I/O додатково встановіть їхній пріоритет I/O до класу idle: ionice -c 3 -p <PID>. Поєднання обох підходів усуває процес як джерело голодування CPU та I/O двома командами без жодних змін у додатку.

15%

Збережіть 15% на всі хостинг-послуги

Перевірте свої навички і отримайте Знижку на будь-який план хостингу

Використовуй код:

Skills
Почати