Коли важке копіювання, резервне копіювання або індексація "забивають" диск, вся система починає гальмувати. Добра новина: у Linux можна акуратно притиснути I/O конкретних процесів, щоб робочий стіл і серверні сервіси залишались плавними. Сьогодні покажу, як це зробити через термінал Linux за допомогою ionice, cgroups v2 та systemd. Буде і трішки Linux моніторинг, і лайфхаки для реальних задач ✨

Що таке обмеження I/O і коли воно потрібне

I/O (input/output) — це читання та запис на диск. Якщо одна задача з'їдає все використання диска, інші програми страждають: менеджер вікон підфрізує, база повільно відповідає, а відео рветься. Обмеження I/O дозволяє:

  • ставити пріоритети (важливе швидко, фонове — м'яко);
  • задавати вагу (weight) для справедливого розподілу;
  • встановлювати жорсткі ліміти на пропускну здатність (MB/s) або IOPS.

Для щоденної оптимізації продуктивності цього більш ніж достатньо.

Швидкий старт: ionice для однієї команди

ionice змінює I/O-пріоритет процесу. Найпростіше зробити фонову дію "лагідною" до системи:

# Запустити копіювання в класі idle (тільки коли диск вільний)
sudo ionice -c3 rsync -a /data/ /backup/

# Слабший, але не повний idle: best-effort з найнижчим пріоритетом
sudo ionice -c2 -n7 tar -czf archive.tgz /bigdir

# Перевірити пріоритет для PID
sudo ionice -p <PID>

Пам'ятайте: ionice працює лише з планувальниками I/O, які підтримують пріоритети (наприклад, BFQ). На NVMe-дисках за замовчуванням часто стоїть none або mq-deadline, і тоді ефекту може не бути — у такому разі переходьте до cgroups v2 + systemd нижче.

Точний контроль: cgroups v2 + systemd

Сучасний і надійний спосіб — використовувати контролер io у cgroups v2 через налаштування systemd. Так можна задати вагу, ліміти на MB/s або IOPS для будь-якого сервісу чи одноразового запуску.

Перевірка, що cgroups v2 увімкнені

mount | grep cgroup2
# Має з'явитися точка монтування /sys/fs/cgroup типу cgroup2

Разовий запуск з лімітами через systemd-run

Припустимо, ви хочете стиснути швидкість запису до 10 MB/s і читання до 20 MB/s на NVMe-диску для важкої команди:

sudo systemd-run --scope \
  -p IOWeight=50 \
  -p IOReadBandwidthMax=/dev/nvme0n1:20M \
  -p IOWriteBandwidthMax=/dev/nvme0n1:10M \
  dd if=/dev/zero of=/bigfile bs=1M count=20000 oflag=direct

Пояснення:

  • IOWeight=50 — вага (1–10000). Менше — скромніше I/O.
  • IOReadBandwidthMax / IOWriteBandwidthMax — жорсткі ліміти для пристрою.
  • --scope — об'єднає процес у тимчасову cgroup з цими правилами.

Замість пропускної здатності можна лімітувати IOPS:

sudo systemd-run --scope \
  -p IOReadIOPSMax=/dev/nvme0n1:200 \
  -p IOWriteIOPSMax=/dev/nvme0n1:100 \
  fio --name=test --rw=readwrite --bs=4k --size=2G --filename=/bigfile

Постійний сервіс із обмеженнями

Створимо unit, щоб кожен запуск мав ліміти автоматично.

sudo tee /etc/systemd/system/rsync-heavy.service >/dev/null <<'EOF'
[Unit]
Description=Rsync з обмеженням I/O

[Service]
Type=simple
ExecStart=/usr/bin/rsync -a /data/ /backup/
# Вага і ліміти для конкретного блочного пристрою
IOWeight=50
IOReadBandwidthMax=/dev/sda:30M
IOWriteBandwidthMax=/dev/sda:10M

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now rsync-heavy.service

Тепер rsync не заважатиме іншим задачам і не забиватиме канал.

Альтернативні способи і корисні трюки

Робота з сирими файлами cgroup v2

Якщо вам треба скриптувати без systemd, можна напряму писати до io.max. Спершу знайдіть major:minor вашого пристрою і створіть cgroup:

DEV=/dev/nvme0n1
MM=$(cat /sys/class/block/$(basename "$DEV")/dev)   # напр., 259:0
CG=/sys/fs/cgroup/myio
sudo mkdir -p "$CG"
echo $$ | sudo tee "$CG/cgroup.procs"   # додамо поточний шелл

# Ліміт 15 MB/s на читання і 8 MB/s на запис
echo "$MM rbps=15728640 wbps=8388608" | sudo tee "$CG/io.max"

Далі запускаєте свої команди у цьому шеллі — ліміти застосуються.

Поєднуйте з CPU nice

sudo nice -n 10 ionice -c3 tar -czf backup.tgz /vm-images

Так процеси будуть "скромні" і за CPU, і за I/O.

Перемикач планувальника I/O (для HDD)

На класичних HDD увімкніть BFQ для кращої справедливості:

DEV=sda
cat /sys/block/$DEV/queue/scheduler
# Напр.: [mq-deadline] kyber bfq
echo bfq | sudo tee /sys/block/$DEV/queue/scheduler

Увага: на NVMe це зазвичай недоречно; для постійної зміни використовуйте параметри ядра/udev-правила.

GUI-спосіб через Cockpit (для серверів і робочих станцій)

Графічних інструментів саме для I/O пріоритетів мало, але у Cockpit зручно керувати systemd-сервісами і додавати drop-in з параметрами I/O.

# Встановлення Cockpit (Debian/Ubuntu)
sudo apt install cockpit -y
sudo systemctl enable --now cockpit.socket
# Далі відкрийте https://<IP>:9090 у браузері

У Cockpit відкрийте ваш сервіс, створіть drop-in і додайте рядки на кшталт IOWeight=50, IOReadBandwidthMax=/dev/sda:20M, IOWriteIOPSMax=/dev/sda:100. Це простий спосіб без ручного редагування файлів. 📀

Моніторинг: перевіряємо, що ліміти працюють

Для Linux моніторинг I/O підійдуть:

# поточне I/O по процесах (root)
sudo iotop -oPa

# по PID з інтервалом 1с
pidstat -d 1

# в htop увімкніть стовпці IO_Read/IO_Write (F2 -> Columns)
htop

Додатково перевіряйте файли cgroup (io.max, io.stat) для вашого unit/скоупу.

FAQ

ionice не працює на моєму NVMe. Чому?

Більшість NVMe-дисків використовують планувальник none/ mq-deadline, які ігнорують I/O-пріоритети процесів. Використовуйте cgroups v2 + systemd з IO*Max параметрами — вони працюють незалежно від планувальника.

Отримую помилку "Unknown lvalue 'IOReadBandwidthMax'" у unit-файлі

Ваш systemd надто старий або система не в unified cgroup v2 режимі. Оновіть systemd до сучасної версії та переконайтесь, що контролер io активний (перезавантаження з параметром ядра systemd.unified_cgroup_hierarchy=1 може допомогти на старих дистрибутивах).

Як вказати правильний пристрій для *BandwidthMax/*IOPSMax?

Зазвичай це блочний пристрій-носій (наприклад, /dev/sda, /dev/nvme0n1), а не розділ. Подивіться дерево дисків:

lsblk -o NAME,PATH,SIZE,TYPE,MOUNTPOINT

Для надійності використовуйте стабільні шляхи на кшталт /dev/disk/by-id/.../

Чи сповільнить це всю систему?

Ні, лише цільові процеси/сервіси отримають нижчий I/O. Інші навпаки стануть чуйнішими.

Як переконатися, що ліміт застосовано?

Дивіться systemd-cgls і systemd-cgtop, читайте io.max/io.stat для відповідної cgroup, а також спостерігайте швидкість у iotop чи pidstat -d. У htop та top вмикайте I/O-статистику.

Працюєте в Docker?

Запускайте контейнери з лімітами, наприклад:

docker run --rm \
  --device-read-bps /dev/sda:20mb \
  --device-write-bps /dev/sda:10mb \
  ubuntu dd if=/dev/zero of=/big bs=1M count=10000 oflag=direct

На системах з cgroups v2 ці прапорці мапляться на ті самі механізми io-контролера.

Порада від Kernelka

Зробіть маленький шорткат для важких речей. Наприклад, alias slow з вашими улюбленими параметрами systemd-run:

echo "alias slow='sudo systemd-run --scope -p IOWeight=50 -p IOWriteBandwidthMax=/dev/sda:10M'" | tee -a ~/.bashrc
source ~/.bashrc
slow rsync -a /data/ /backup/

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

Підсумок

  • ionice — швидкий спосіб знизити пріоритет I/O для однієї команди.
  • cgroups v2 + systemd — точні й стабільні ліміти на MB/s або IOPS для сервісів і скоупів.
  • Використовуйте Linux моніторинг інструменти (iotop, pidstat, htop) для перевірки.
  • Для HDD подумайте про планувальник BFQ; для NVMe — робіть ставку на cgroups v2.
  • Поєднуйте з nice і автоматизуйте запуск — це покращить оптимізація продуктивності системи.