NVMe-диски обычно «быстрые из коробки», но на реальной серверной нагрузке (БД, очереди, логи, кэш, контейнеры) упираются не в «скорость чтения в МБ/с», а в latency (задержки) и стабильность IOPS. В Linux на это чаще всего влияют три группы настроек:
- планировщик ввода-вывода (scheduler):
none,mq-deadlineи др.; - read-ahead: как агрессивно ядро читает наперёд;
- discard/TRIM: онлайн через mount option
discardили пакетно черезfstrim.
Ниже — практичный чек-лист: как посмотреть текущие значения, как менять безопасно и какие комбинации обычно дают лучший баланс для NVMe в продакшене.
0) Сначала определите, что у вас за NVMe и где узкое место
На VDS NVMe часто «видится» как /dev/nvme0n1, но фактическая производительность может ограничиваться слоем виртуализации, политиками I/O на узле или соседями. Перед тюнингом полезно зафиксировать базовую линию: какие сейчас задержки и как ведёт себя очередь.
Проверяем устройство, ФС и параметры очереди
lsblk -o NAME,TYPE,SIZE,ROTA,DISC-GRAN,DISC-MAX,MOUNTPOINT,FSTYPE
nvme list 2>/dev/null
cat /sys/block/nvme0n1/queue/scheduler
cat /sys/block/nvme0n1/queue/nr_requests
cat /sys/block/nvme0n1/queue/read_ahead_kb
На что смотреть в первую очередь:
ROTAдолжен быть0(не HDD).DISC-GRAN/DISC-MAX</code показывают, поддерживается ли discard/TRIM на уровне блочного устройства.</li> <li>Текущий scheduler отмечается в квадратных скобках, например: <code>none [mq-deadline].
Смотрим latency на живой системе
iostat -x 1
В выводе интереснее всего:
r_await,w_await— средние задержки чтения/записи;aqu-sz— средняя глубина очереди;%util— признак «упора» (в виртуализации интерпретируйте осторожно).
Если цель — «быстрее отклик» веба и БД, оптимизируйте не MB/s, а хвосты задержек. NVMe может показывать высокий throughput, но при неудачных настройках scheduler/read-ahead получить «пилу» latency.
1) Scheduler для NVMe: none или mq-deadline
Для NVMe в современных ядрах чаще всего выбор сводится к двум адекватным вариантам:
none— минимум накладных расходов, ядро почти не «рулит» очередями, отдавая это устройству/драйверу;mq-deadline— добавляет дедлайны и упорядочивание, часто улучшает предсказуемость latency при смешанной нагрузке.
Практическая логика выбора
Выбирайте none, если:
- нагрузка в основном случайная и параллельная (БД, key-value, очереди);
- важны максимальные IOPS и минимальная средняя latency;
- ниже по стеку нет «хитрых» ограничений, требующих сглаживания.
Выбирайте mq-deadline, если:
- есть смешанный профиль (и последовательные, и случайные операции);
- наблюдаете скачки задержек под нагрузкой (особенно на записи);
- нужна более стабильная latency, пусть иногда ценой небольшого падения пиковых IOPS.
Переключить scheduler на лету (до перезагрузки)
cat /sys/block/nvme0n1/queue/scheduler
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
cat /sys/block/nvme0n1/queue/scheduler
Или так:
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
cat /sys/block/nvme0n1/queue/scheduler
Сделать scheduler постоянным (udev правило)
Один из самых переносимых вариантов — через udev, чтобы применялось именно к NVMe:
sudo tee /etc/udev/rules.d/60-nvme-scheduler.rules >/dev/null <<'EOF'
ACTION=="add|change", KERNEL=="nvme*n*", ATTR{queue/scheduler}="none"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger --type=devices --action=change
Если хотите глубже разобраться в диагностике дисковой подсистемы и верификации тюнинга, держите под рукой статью про практику замеров и инструменты: iostat/iotop/fio и выбор scheduler.
Если вы подбираете сервер под БД или высокие IOPS, удобнее всего тестировать такие настройки на отдельной машине с гарантированными ресурсами. Для этого подойдёт VDS, где можно быстро менять ядро/дистрибутив, разносить роли по дискам и валидировать результаты под своей нагрузкой.

2) read_ahead для NVMe: когда снижать, когда повышать
read_ahead задаёт, сколько килобайт ядро будет читать «на опережение» при последовательном чтении. На NVMe цена лишнего чтения ниже, чем на HDD, но в продакшене завышенный read-ahead всё равно может:
- раздувать page cache и вытеснять полезные данные;
- ухудшать latency на случайном чтении при конкуренции потоков;
- создавать лишний фон I/O и влиять на хвосты задержек.
Посмотреть текущий read-ahead
cat /sys/block/nvme0n1/queue/read_ahead_kb
lsblk -o NAME,RA,MOUNTPOINT
Рекомендации по значениям (стартовые точки)
- БД/очереди/образы VM (случайный доступ): часто лучше 128–256 KB.
- стриминг/бэкапы/большие файлы (последовательное): уместно 512–2048 KB.
- универсальный старт для NVMe на сервере: 256 KB.
Установить read-ahead на лету
sudo blockdev --setra 512 /dev/nvme0n1
sudo blockdev --getra /dev/nvme0n1
cat /sys/block/nvme0n1/queue/read_ahead_kb
Важно: blockdev --setra задаётся в секторах по 512 байт. То есть 512 = 256 KB. Проверяйте себя через read_ahead_kb в sysfs.
3) Discard/TRIM: mount option discard vs fstrim
На SSD/NVMe полезно возвращать неиспользуемые блоки устройству (TRIM), чтобы контроллеру было проще с garbage collection и чтобы запись держала стабильные IOPS. В Linux есть два подхода:
- онлайн discard — mount option
discard; - пакетный TRIM — периодический
fstrim(systemd timer или cron).
Почему discard нередко ухудшает latency
С включённым discard освобождение блоков может превращаться в дополнительные операции прямо во время работы (удаление файлов, освобождение extents). На части стэков хранения это добавляет задержки на запись, особенно при частых delete/overwrite (логирование, временные файлы, контейнерные слои).
Поэтому для серверов чаще выбирают практичную схему: не держать discard включённым постоянно, а использовать fstrim по расписанию.
Проверить поддержку discard на уровне блока
lsblk -D
Если DISC-GRAN и DISC-MAX не нули — TRIM поддерживается. Если нули — слой ниже, вероятно, его не пропускает, и ждать эффекта от discard/fstrim не стоит.
Запустить TRIM вручную
sudo fstrim -av
Флаг -v покажет, сколько было «обрезано». Если значения стабильно 0, обычно это означает либо регулярный TRIM уже работает, либо нижний слой discard игнорирует.
Включить регулярный fstrim (systemd)
systemctl status fstrim.timer
sudo systemctl enable --now fstrim.timer
systemctl list-timers | grep fstrim
На многих дистрибутивах таймер уже настроен на еженедельный запуск — для большинства сценариев этого достаточно. По теме TRIM, mount options и типичных ошибок можно дополнительно свериться со статьёй: fstrim vs discard и опции монтирования SSD.
4) Mount options для ext4 и xfs на NVMe: что реально влияет
Опции монтирования — тонкая настройка поведения файловой системы. Она может улучшить предсказуемость и снизить лишние записи, но может и навредить, если включить «ускоряющие» параметры без понимания рисков. Ниже — безопасные и практичные рекомендации для ext4 и xfs с фокусом на latency/IOPS.
Общие безопасные опции
relatimeобычно уже стоит по умолчанию и часто достаточно.noatimeуменьшает лишние записи метаданных при чтении и полезен там, где точно не нужен atime.- Не включайте
discard«по привычке», если ваша цель — минимальная latency на записи.
Проверить текущие опции монтирования:
findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS
ext4: рекомендации и нюансы
Для ext4 на NVMe чаще всего достаточно дефолтов плюс аккуратные изменения:
noatime(или оставитьrelatime, если сомневаетесь);- не включать
data=writebackради скорости: это компромисс по целостности; - с опцией
commit=не торопитесь: она меняет частоту коммитов журнала и требует понимания допустимой потери последних секунд данных при аварии питания/краше.
Пример строки в /etc/fstab (как ориентир):
UUID=xxxx-xxxx /data ext4 defaults,noatime 0 2
xfs: рекомендации и нюансы
XFS часто выбирают за предсказуемость на больших объёмах и хорошее поведение при параллельной нагрузке. Для NVMe:
noatime— по тем же причинам;- параметры вроде
logbsize=и другие — не трогайте без причины и измерений; discard— с теми же оговорками, что и для ext4.
Пример для /etc/fstab:
UUID=yyyy-yyyy /data xfs defaults,noatime 0 2

5) Мини-методика тюнинга: меняем по одному параметру и меряем
Чтобы не получить «кажется стало лучше», действуйте итеративно: одно изменение — замер — вывод.
Шаг 1: зафиксируйте базовую линию
iostat -x 1
Если есть возможность, прогоняйте тестовую нагрузку на отдельном разделе/файле и в тихое окно. Синтетика на проде легко мешает живым сервисам, особенно если вы тестируете запись.
Шаг 2: выберите scheduler
Начните с none. Если видите хвосты задержек и «пилу» на записи — попробуйте mq-deadline и сравните не только средние await, но и «ровность» работы приложения.
Шаг 3: настройте read-ahead под профиль
Для большинства серверных NVMe-сценариев рабочая стартовая точка — 256 KB. Для чистого sequential (бэкапы, раздача больших файлов) повышайте. Для случайного доступа и конкуренции потоков — понижайте.
Шаг 4: TRIM делайте пакетно
Оставьте discard выключенным и включите fstrim.timer. Исключение — только если вы измерениями подтвердили, что онлайн discard не добавляет задержек на вашем стеке хранения.
6) Частые ошибки, из-за которых NVMe «внезапно медленный»
- Тюнинг без измерений: поменяли scheduler/read-ahead и не зафиксировали latency до/после.
- Включили
discardвезде и получили микрофризы на записи при delete/rotate логов. - Слишком высокий read-ahead на случайной нагрузке: лишний I/O и вытеснение page cache.
- Смешали уровни оптимизаций (ФС, LVM, RAID, виртуализация) и потеряли понимание, где именно теряются IOPS.
7) Короткие рецепты по ролям нагрузки
Веб + PHP/Node + небольшая БД на одном NVMe
- scheduler:
none(если хвосты —mq-deadline); - read-ahead: 256 KB;
- TRIM:
fstrim.timer, безdiscard; - mount options:
noatime(или оставитьrelatime).
PostgreSQL/MySQL (latency и IOPS важнее throughput)
- scheduler: чаще
none, но при нестабильной записи пробуйтеmq-deadline; - read-ahead: 128–256 KB;
- TRIM: пакетно через
fstrim.
Бэкапы/архивы/статический контент (последовательное чтение)
- scheduler:
none; - read-ahead: 1024–2048 KB (проверяйте эффект замерами);
- TRIM:
fstrim.
Итог
Для NVMe в Linux чаще всего «выстреливает» простой набор: scheduler none или mq-deadline по результатам измерений, read_ahead около 256 KB под смешанную серверную нагрузку и отказ от постоянного discard в пользу регулярного fstrim. Это помогает сделать latency ровнее, а IOPS — стабильнее.
Если хотите довести тюнинг до «идеала», следующий шаг — профилировать именно вашу нагрузку (что читает/пишет, какими блоками, как выглядит очередь), и только после этого аккуратно править параметры, не смешивая сразу десяток изменений.


