Переключить libaio на io_uring — ещё не починить задержки NVMe. Разбираем сценарий отказа: случайное чтение 4K, после переключения IOPS выросли, а приложение стало чаще нарушать SLO. Поверхностный диагноз — «диск не держит нагрузку». Первая улика против диагноза: сравнивали максимум пропускной способности, а обещали время ответа. Начинаем с воспроизводимого fio: один заранее подготовленный файл, direct=1, rw=randread, bs=4k, одинаковые numjobs и привязка CPU. Меняем только ioengine=libaio/io_uring, затем отдельно проходим iodepth=1,8,32,64. Проверяем фактическое распределение IO depths: заданная глубина ещё не доказательство достигнутой. Для libaio buffered I/O может не дать ожидаемой асинхронности. SQPOLL и IOPOLL выносим в отдельные испытания с учётом расхода CPU. Смотрим p99/p99.9 полной lat, отдельно slat и clat: перцентили складывать нельзя. iostat -x 1 показывает r_await и aqu-sz, но среднее не оправдывает хвост, а %util не измеряет предел параллельного NVMe. Сопоставляем block:b...