Что такое блочный слой Linux и почему iostat мало
Когда программа делает read() или write() к файлу, запрос проходит длинный путь: файловая система -> page cache -> block layer -> драйвер -> само устройство. Блочный слой linux (block layer) - это прослойка ядра, которая принимает запросы ввода-вывода, складывает их в очередь, при необходимости объединяет соседние (merge), сортирует планировщиком и отдаёт драйверу. С ядра 5.0 в строю только multi-queue-планировщики: none (он же по умолчанию для NVMe), mq-deadline и bfq; старый single-queue cfq/deadline удалён. Посмотреть текущий можно так:
Код: Выделить всё
cat /sys/block/sda/queue/scheduler
# [mq-deadline] none bfq - в скобках активный
iostat смотрит на этот слой сверху и выдаёт агрегаты: средний await (с util-linux/sysstat последних лет это r_await и w_await отдельно для чтения и записи), утилизацию, IOPS. Это как средняя температура по больнице. А нам для расследования редких всплесков нужна детализация по каждому запросу: сколько он простоял в очереди ядра, сколько ехал до устройства, сколько устройство его реально обслуживало. Вот тут и нужен block layer trace.
Ключевая идея: задержку io в Linux можно разложить на стадии. Запрос проходит через события (action), которые ядро штампует временными метками:
- Q (Queue) - запрос только что попал в блочный слой;
- G (Get request) - под него выделена структура request;
- I (Insert) - вставлен в очередь планировщика;
- D (Dispatch) - отправлен драйверу и устройству;
- C (Complete) - устройство отчиталось, что закончило.

Практика: blktrace, blkparse и btt по шагам
Сначала классика. blktrace собирает сырые события через debugfs, blkparse превращает их в читаемый вид, btt считает статистику по стадиям. Все три лежат в одном пакете blktrace (Debian/Ubuntu - apt install blktrace, RHEL/Fedora - dnf install blktrace; в Astra Linux и RED OS пакет тоже называется blktrace). Нужен смонтированный debugfs (обычно уже есть в /sys/kernel/debug).
Запускаем трассировку устройства (не раздела, а целого диска, например /dev/sda) на несколько секунд под нагрузкой:
Код: Выделить всё
sudo blktrace -d /dev/sda -w 10 -o sda
# -w 10 - писать 10 секунд
# -o sda - префикс файлов: появятся sda.blktrace.0, sda.blktrace.1 ...
Код: Выделить всё
blkparse -i sda -o sda.parsed | head
8,0 1 1 0.000000000 3421 Q R 12345678 + 8 [mysqld]
8,0 1 2 0.000001500 3421 G R 12345678 + 8 [mysqld]
8,0 1 3 0.000004200 3421 D R 12345678 + 8 [mysqld]
8,0 1 4 0.000231000 0 C R 12345678 + 8 [0]
Руками вычитать метки из тысяч строк нереально, поэтому статистику считает btt (берёт бинарь, который сначала собирают через blkparse -d):
Код: Выделить всё
blkparse -i sda -d sda.bin >/dev/null
btt -i sda.bin
==================== All Devices ====================
ALL MIN AVG MAX N
--------------- ------------- ------------- ------------- -----------
Q2Q 0.000000512 0.000041 0.004821 24117
Q2D 0.000000821 0.000009 0.001204 24118
D2C 0.000031002 0.000198 0.038122 24118
Q2C 0.000033140 0.000208 0.038901 24118
Современная альтернатива: biolatency и biosnoop на eBPF (актуально на 2026)
blktrace пишет на диск гигабайты сырья и сам создаёт нагрузку. На проде под пиком это иногда неприемлемо. Тут выручает biolatency - инструмент на eBPF, который считает гистограмму прямо в ядре в BPF-карте и почти не грузит систему. Накладные расходы пренебрежимы при разумном IOPS (ориентир < 10k IOPS на устройство); при очень высоком IOPS overhead стоит замерить заранее.
Важный нюанс 2026 года про экосистему. Исторически эти инструменты ставились из пакета bpfcc-tools (Debian/Ubuntu) или bcc-tools (RHEL/Fedora) - это Python-обёртки поверх bcc, и команда называется с суффиксом, например biolatency-bpfcc. Сейчас на проде предпочитают libbpf-tools (CO-RE/BTF): те же biolatency и biosnoop, но скомпилированные в маленький C-бинарь, который "compile once, run everywhere" - не тащит за собой Python, LLVM и kernel-headers и не пересобирает BPF на каждом хосте при запуске (память на порядок меньше, старт мгновенный). Если в дистрибутиве есть пакет libbpf-tools - бери его. Для интерактивного ковыряния удобен bpftrace: те же инструменты идут как biolatency.bt и biosnoop.bt. Для всего этого нужно ядро с eBPF (практически любое современное, 5.x и выше; для CO-RE - ядро с BTF, то есть CONFIG_DEBUG_INFO_BTF=y, что есть в свежих Ubuntu, RHEL 9+, Astra и RED OS на новых ядрах). Запускается от root.
biolatency показывает не средние, а полное распределение io латентности linux в виде гистограммы степеней двойки:
Код: Выделить всё
sudo biolatency 10 1
Tracing block device I/O... Hit Ctrl-C to end.
usecs : count distribution
128 -> 255 : 3210 |****************************************|
256 -> 511 : 1840 |********************** |
512 -> 1023 : 210 |** |
1024 -> 2047 : 12 | |
8192 -> 16383 : 4 | |
Важная тонкость, которую часто путают: по умолчанию biolatency меряет латентность от выдачи запроса устройству до завершения, то есть фактически это D2C (время устройства), а не полный путь запроса. Чтобы учесть и время в очереди ОС (получится Q2C-подобная картина), добавь флаг -Q. Другие полезные флаги: -m - гистограмма в миллисекундах вместо мкс; -D - отдельная гистограмма на каждое устройство; -F - разбивка по флагам io (отдельно чтение, запись, sync, metadata - очень помогает понять, кто медленный: чтения или флаши журнала).
Когда нашёл, что выбросы есть, но не знаешь, КТО их создаёт, берёшь biosnoop - он логирует каждый запрос отдельной строкой:
Код: Выделить всё
sudo biosnoop
TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
0.000000 mysqld 3421 sda R 12345678 4096 0.23
0.413200 jbd2/sda1-8 289 sda W 98765432 8192 14.81
0.413500 backup.sh 7711 sda R 55512300 131072 9.40
Типичные грабли и заблуждения
- Трассируют раздел (/dev/sda1) вместо всего устройства (/dev/sda). blktrace вешается на устройство целиком - указывай диск, иначе часть событий мимо.
- Забывают, что blktrace сам создаёт io, записывая трейс. Пиши трейс на ДРУГОЙ диск или направь в tmpfs/в сеть (blktrace -d ... -o - | blkparse -i -), не на тот диск, что исследуешь.
- Путают единицы. В выводе btt - СЕКУНДЫ, в biolatency по умолчанию - МИКРОсекунды, в biosnoop - МИЛЛИсекунды. 0.038 секунды это 38 мс, а не 38 мкс. Перепутаешь - сделаешь неверный вывод.
- Думают, что гистограмма biolatency - это полный путь запроса. По умолчанию это только время устройства (D2C). Хочешь учесть очередь ОС - флаг -Q.
- Смотрят только на AVG и пропускают MAX и хвост гистограммы. Расследование всплесков - это всегда про хвост распределения (p99/p999), а не про среднее.
- Ждут от biosnoop точного виновника. Поля COMM/PID для асинхронного io обманчивы: флаш страниц делает поток ядра (kworker), коммит журнала ext4 - jbd2, fsync чужой записи может прилететь от kswapd. Это подсказка, а не приговор - сверяйся с типом io и шаблоном доступа.
- Запускают Python-bcc (biolatency-bpfcc) на проде и удивляются паузе на старте и аппетиту к памяти. На 2026 для прода правильнее libbpf-tools (CO-RE): тот же результат без LLVM и заголовков ядра.
- Поставь пакеты: blktrace и (libbpf-tools либо bpfcc-tools/bcc-tools). Проверь имя команды: biolatency или biolatency-bpfcc.
- В одном терминале создай нагрузку на тестовый диск:
Код: Выделить всё
fio --name=t --filename=/tmp/testfile --rw=randread --bs=4k --size=512M --runtime=20 --time_based - Во втором сними гистограмму: - посмотри, в какие диапазоны попадает основная масса и есть ли хвост; затем повтори с -Q и сравни, насколько выросли цифры за счёт очереди.
Код: Выделить всё
sudo biolatency -D 5 3 - Сними пер-запросный лог: на пару секунд, найди строки с самым большим LAT, посмотри QUE против LAT и какой процесс их породил.
Код: Выделить всё
sudo biosnoop -Q - Для контраста сними классику: , затем
Код: Выделить всё
sudo blktrace -d /dev/sdX -w 5 -o lab. Сравни Q2D и D2C в колонке MAX - реши, кто виноват: ядро или устройство.Код: Выделить всё
blkparse -i lab -d lab.bin >/dev/null && btt -i lab.bin
- Чем D2C отличается от Q2D и о чём говорит большое значение каждого из них?
- В каком масштабе времени выводит цифры btt, а в каком biolatency по умолчанию? Сколько микросекунд в строке "8192 -> 16383"?
- Что именно меряет biolatency без флагов и какой флаг добавляет время очереди ОС?
- Почему на проде в 2026 предпочитают libbpf-tools вместо Python-bcc и чем eBPF безопаснее blktrace?
- Каким инструментом и по какому полю ты найдёшь процесс, создающий медленные io, и почему этому полю нельзя верить вслепую?
Когда iostat показывает "вроде норм", а подвисания есть - спускайся в блочный слой. Раскладывай задержку запроса на стадии Q -> D -> C: Q2D это ядро, очередь и планировщик, D2C это устройство и драйвер. Для разового глубокого разбора бери blktrace + blkparse + btt (секунды, статистика по стадиям, выгрузка пер-IO через -l/-z). Для прода и поиска редких выбросов почти без overhead - biolatency (гистограмма, по умолчанию мкс и время устройства, -Q добавляет очередь) и biosnoop (пер-запросный лог, мс, плюс имя и pid виновника и колонка очереди по -Q). На 2026 ставь их версию libbpf-tools (CO-RE/BTF), а не тяжёлый Python-bcc. И главное правило расследования латентности: среднее врёт - смотри на хвост распределения.