fio давно стал де-факто стандартом для бенчмарков накопителей в Linux. Проблема не в запуске инструмента, а в интерпретации: большинство публикуемых цифр измеряют скорость кэша памяти или синтетический профиль, не имеющий отношения к вашей нагрузке. В этом материале разберём, как строить замеры под реальную задачу — базу данных, веб-сервер или бэкап, — как читать задержки и когда смена I/O планировщика действительно даёт эффект.
Установка и первый замер
В Ubuntu и Linux Mint пакет лежит в обычном репозитории, собирается стабильно и не требует сторонних источников. Для io_uring нужен fio 3.2 и новее, это легко проверить по версии.
sudo apt install fio
fio --version
fio --name=seqread --filename=/srv/testfile.bin --rw=read --bs=1M \
--size=1G --ioengine=io_uring --direct=1 --iodepth=16 \
--runtime=60 --time_based --output-format=json --output=seqread.json
Параметр direct=1 отключает буфер страниц: без него вы измеряете скорость памяти, а не диска. io_uring предпочтительнее libaio, который считается устаревшим; на ядрах до 5.1 придётся вернуться к libaio или psync. Важно: указание filename=/dev/nvme0n1 перезаписывает содержимое устройства, поэтому для безопасных тестов используйте обычный файл на отдельном разделе или каталог через —directory.
Профили под реальную нагрузку
Синтетические мегабайты в секунду редко совпадают с реальностью. СУБД, веб-сервер и бэкап читают диск по-разному, и один тест этого не покажет.
- база данных: случайное чтение блоками 4K с глубиной очереди 32 — базовый профиль;
- смешанная нагрузка веб-приложения: randrw с rwmixread=70, блоки 8K, глубина 8–16;
- журнал транзакций: последовательная запись с fsync показывает реальную скорость коммита.
fio --name=db --filename=/srv/testfile.bin --rw=randread --bs=4k \
--iodepth=32 --ioengine=io_uring --direct=1 --numjobs=4 \
--runtime=120 --time_based --group_reporting
fio --name=wal --filename=/srv/testfile.bin --rw=write --bs=4k \
--fsync=32 --ioengine=io_uring --direct=1 --runtime=60
numjobs запускает несколько параллельных потоков, а group_reporting сводит их в один отчёт. fsync заставляет fio синхронизировать запись каждые 32 операции: без этого данные уйдут в кэш, и вы получите красивые цифры, не имеющие отношения к производительности сервера.
Читать не среднее, а перцентили
В выводе есть IOPS, мегабайты и латентность, но средняя латентность обманчива: машина может отдавать сто тысяч IOPS при средних 200 микросекунд и при этом раз в минуту подвешиваться на секунду. Именно эта секунда становится жалобами пользователей.
- поля clat percentiles показывают 99-й и 99.9-й уровни, по ним и судят о просадках;
- флаг —percentile_list=50:99:99.9:99.99 добавляет нужные уровни в отчёт;
- slat отражает ожидание планировщика, а clat — время в очереди ядра, это разные диагнозы;
- сравнивать можно только одинаковые профили: последовательное чтение с 1M не сравнивается с randread 4k.
I/O планировщик: что выбрать
Планировщик решает, в каком порядке запросы уходят в устройство. Посмотрите, что доступно именно на вашем диске.
cat /sys/block/sda/queue/scheduler
cat /sys/block/nvme0n1/queue/scheduler
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
В выводе активный планировщик отмечен квадратными скобками. Типичная картина: для жёстких дисков bfq или mq-deadline, для SATA-SSD mq-deadline, для NVMe — none, поскольку у быстрого устройства собственная очередь эффективнее пользовательской дисциплины. Команда выше меняет планировщик на лету, но изменение не переживает перезагрузку. Постоянное правило задаётся udev-правилом в каталоге /etc/udev/rules.d.
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="mq-deadline"
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/scheduler}="none"
Отдельно стоит упомянуть десктоп: bfq на рабочей станции с мелкими записями браузера часто ощущается отзывчивее, чем none, хотя в бенчмарках выигрыш минимален. На сервере с несколькими десятками потоков картина обратная: лишняя обработка запросов планировщиком увеличивает латентность. Поэтому настройки для десктопа и для базы данных — это два разных профиля, и переносить их на другой класс машин без повторных замеров не стоит.
Разницу проверяйте на своих профилях fio, а не по рекомендациям из сети: на NVMe переключение между none и mq-deadline часто даёт меньше трёх процентов, тогда как на HDD с мелкими случайными чтениями планировщики различаются в разы.
Что реально важно помимо планировщика
- read_ahead_kb — предвыборка: для последовательных задач 128–512, для случайных 4–16;
- nr_requests — размер очереди, на глубоких профилях её рост снижает латентность;
- частота CPU: governor performance против schedutil даёт до 10 процентов на случайных операциях;
- iostat -x 1 показывает util, await и iowait, им ловят фоновую нагрузку до теста;
- отключите swap и убедитесь, что в фоне не крутится обновление пакетов — иначе результат исказится.
Как сравнивать результаты между дисками
Один бенчмарк ничего не доказывает: сравнимый замер должен повторять условия — тот же job-файл, тот же размер файла, та же длительность и одинаковое окружение. Удобно вынести параметры в отдельный файл и хранить его рядом со скриптами.
[db]
rw=randread
bs=4k
size=4G
direct=1
ioengine=io_uring
iodepth=32
numjobs=4
group_reporting=1
runtime=120
time_based=1
Запуск становится короче: fio db.fio —filename=/srv/testfile.bin —output-format=json > db.json. Дальше отчёт легко разбирать скриптом, например jq вытащит средние IOPS или перцентили латентности. Практика показывает, что коллекция таких JSON-файлов за полгода окупается при переезде на новый сервер: вы сразу видите, ускорился накопитель или изменилась нагрузка, а не гадаете по субъективным ощущениям.
Типичные проблемы при тестировании
Практика показывает одни и те же грабли. Ошибка «Operation not supported» при direct=1 означает, что файловая система или драйвер не умеют прямой доступ — переходите на direct=0 и помните про кэш. Тест на занятой файловой системе засоряют чужие записи, поэтому берите отдельный раздел. Новый SSD сразу после TRIM показывает завышенные цифры: прогоните подготовку записью полного объёма. И наконец, если файл меньше кэша памяти, замер отражает память, а не накопитель. Готовые сценарии удобно оформлять в job-файлы и запускать через fio db.fio —output-format=json, а обогащать вывод метриками — через jq.
Итог
Нет «лучшего планировщика» и нет «эталонной команды fio»: есть ваша нагрузка, корректно воспроизведённая профилем, честный прямой доступ к накопителю и перцентили вместо средних значений. Сначала измеряйте baseline одним и тем же профилем, потом меняйте по одному параметру — планировщик, глубину очереди, предвыборку — и сохраняйте JSON-отчёты, чтобы через полгода сравнить диски после обновления прошивки или переезда на другой сервер.