Твердотельный накопитель редко выходит из строя внезапно: он медленно расходует запас ячеек и до последнего дня числится исправным, а потом начинает терять данные без предупреждения. У NVMe есть штатный механизм против неожиданности, лог SMART Health, и Linux читает его одной командой из пакета nvme-cli. По нему видно, сколько данных накопитель принял от системы, сколько резерва осталось внутри, насколько израсходован паспортный ресурс и не поднял ли контроллер тревогу. Отдельный лог endurance group дает материал для расчета write amplification factor, коэффициента, показывающего, во сколько раз объем записи в ячейки превышает объем данных от операционной системы. Ниже разобраны оба лога, числовые примеры пересчета, критические биты, пороги температуры и настройка автоматических предупреждений на сервере.

Счетчики износа в SMART Health log и команда nvme smart-log

Лог с идентификатором 0x02 возвращает сводку о здоровье контроллера и носителя. Для накопителя целиком команда выглядит так:

nvme smart-log /dev/nvme0

Вывод сокращен до самых говорящих строк:

critical_warning                    : 0
temperature                         : 38 C
available_spare                     : 100 %
available_spare_threshold           : 10 %
percentage_used                     : 51 %
data_units_read                     : 4,612,508
data_units_written                  : 9,415,223
host_read_commands                  : 42,019,874
host_write_commands                 : 71,655,401
controller_busy_time                : 1,204
power_cycles                        : 311
power_on_hours                      : 5,842
unsafe_shutdowns                    : 67
media_errors                        : 0
num_err_log_entries                 : 12

Ключ к счетчикам записи спрятан в единицах измерения: один data unit равен 1000 x 32 x 1024 байта, то есть 32 768 000 байт. Пересчет простой: 9 415 223 единицы, умноженные на 32 768 000, дают около 308 ТБ записанных данных за весь срок службы. Поле data_units_read устроено так же, только считает чтения, и для расчета ресурса оно не важно: флеш-память изнашивается от записи. Наработка power_on_hours составляет 5 842 часа, и если поделить объем записи на время, выходит порядка 53 ГБ в час, солидно для настольной машины и незаметно для сервера под базой данных. Поле unsafe_shutdowns при этом насчитывает 67 незавершенных отключений питания, и каждое из них контроллер пережил за счет конденсатора или удачи. На сервере с несколькими накопителями путь начинается со списка устройств с серийными номерами и прошивками:

nvme list

Для скриптов и систем мониторинга удобнее формат JSON:

nvme smart-log /dev/nvme0 --output-format=json

Резерв ячеек available_spare и процент износа percentage_used

Резерв работает как запасной склад чистых ячеек: изношенные блоки контроллер подменяет свежими, и накопитель продолжает держать заявленную емкость. Поле available_spare показывает, сколько процентов от заводского запаса осталось, а available_spare_threshold задает нижнюю границу, обычно 10 процентов. Заводской запас сам по себе невелик: если производитель заложил 8 процентов ячеек про запас, значение available_spare 50 означает, что свободных блоков осталось 4 процента от площади носителя, а порог будет достигнут, когда на складе останется меньше 0,8 процента.

percentage_used считается от паспортного ресурса: контроллер делит фактический объем записи на расчетный и умножает на 100. В примере выше накопитель с ресурсом 600 TBW записал 308 ТБ, и счетчик показывает 51 процент, цифры сходятся. Значение может перевалить за 100: это не приказ выбрасывать накопитель, а сообщение о том, что ресурс официально исчерпан и впереди жизнь по остаткам резерва. Полезно следить за скоростью роста: если за месяц счетчик прирастает на 3 процента, от 51 до сотни остается 16 месяцев, и срок службы прогнозируется без всякой телепатии.

От процентного износа легко перейти к квоте записи, которую серверу полезно знать заранее. Ресурс 600 TBW при желаемом сроке службы 5 лет дает 120 ТБ в год, около 330 ГБ в сутки, и если нагрузка приложения упирается в такую цифру, лучше видеть это по счетчикам, чем по тикетам о деградации. Гарантийный объем записи производитель публикует в паспорте модели, и он заметно различается: потребительские накопители на 1 ТБ обычно несут от 300 до 600 TBW, серверные с тем же объемом от 1000 TBW и выше.

Критические предупреждения critical_warning и реакция ядра на деградацию

Поле critical_warning сворачивает все аварии в один байт. Каждый бит закреплен за своей ситуацией:

  1. бит 0 сообщает, что available_spare опустился ниже available_spare_threshold;
  2. бит 1 поднимается при выходе температуры за порог предупреждения, вверх или вниз;
  3. бит 2 фиксирует деградацию надежности из-за ошибок носителя;
  4. бит 3 означает перевод накопителя в режим только чтения;
  5. бит 4 указывает на отказ цепи резервного питания, то есть конденсатора.

Старшие биты отведены под постоянную память и сводку по endurance group. При любом установленном бите контроллер посылает асинхронное событие, ядро печатает сообщение в кольцевой буфер с префиксом nvme, и его видно в журнале:

journalctl -k | grep -i nvme

Список последних ошибок носителя достается отдельным логом с идентификатором 0x01:

nvme error-log /dev/nvme0

Каждая запись несет счетчик команд, код ошибки и поле status_field, по которому расшифровывается причина, а для ошибок чтения и записи там же виден адрес LBA сбойного блока. Единичные записи при нулевом media_errors обычно означают сбойный сектор, отработанный резервом, а растущий журнал на фоне ползущего вниз available_spare говорит о приближении предела, и копирование данных в таком случае откладывать не стоит.

Лог endurance group и расчет write amplification factor

Начиная со спецификации NVMe 1.4 производители группируют флеш-память в endurance group, и по ней отчитывается отдельный лог с идентификатором 0x100:

nvme endurance-log /dev/nvme0
endurance_estimate                  : 18,310,690
data_units_read                     : 4,612,508
data_units_written                  : 9,415,223
media_units_written                 : 12,239,790
host_read_commands                  : 42,019,874
host_write_commands                 : 71,655,401

Поле endurance_estimate выражено в тех же единицах по 32 768 000 байт, и 18 310 690 единиц дают ровно 600 ТБ паспортного ресурса. Главная ценность лога в паре полей data_units_written и media_units_written: первое считает данные от хоста, второе реальную запись в ячейки после всех служебных перезаписей. Отношение второго к первому и есть write amplification factor, в примере 12 239 790 / 9 415 223 = 1,30. Коэффициент 1,1-1,5 типичен для смешанной нагрузки, значения от 3 и выше выдают случайные мелкие записи, журналы баз данных и шаблонные операции с множеством мелких файлов. Арифметика беспощадна: при WAF 1,3 система отдаст накопителю 600 ТБ данных, а ячейки примут 780 ТБ, и ресурс кончится раньше паспорта; при WAF 2,0 тот же запас растянется лишь на 300 ТБ хостовых данных. Коэффициент считается и на коротком отрезке, что удобно для поиска виновника: если за неделю дельта data_units_written составила 1 400 единиц, то есть около 46 ГБ, а дельта media_units_written 2 100 единиц, около 69 ГБ, недельный WAF равен 1,50, и нагрузку стоит пересмотреть до того, как привычка станет причиной износа. Если накопитель не умеет endurance group, в smart-log все равно есть поле endurance_group_critical_warning_summary, а коэффициент прикидывается по темпу роста percentage_used.

Снижение write amplification через fstrim, резерв пространства и характер нагрузки

Первый и самый дешевый прием, регулярный TRIM: он сообщает контроллеру, какие блоки освободились, и экономит работу сборщику мусора.

systemctl enable --now fstrim.timer
fstrim -av

Служба fstrim.timer из состава util-linux по умолчанию проходит по файловым системам раз в неделю, и на сервере с постоянной перезаписью это ощутимо сдерживает рост коэффициента. Второй прием, увеличение скрытого резерва. Накопитель на 1 ТБ несет 1 953 525 168 секторов по 512 байт, и если отдать файловой системе 90 процентов, оставив контроллеру оставшиеся 10, ему станет легче распределять износ:

nvme delete-ns /dev/nvme0 --namespace-id=1
nvme create-ns /dev/nvme0 --nsze=1758172651 --ncap=1758172651
nvme attach-ns /dev/nvme0 --namespace-id=1 --controllers=0

При пересоздании пространства данные утрачиваются безвозвратно, поэтому процедура требует поддержки управления пространствами имен и предварительной копии. Более мягкий вариант без всяких команд, просто не занимать разделами весь диск и держать файловую систему заполненной не выше девяноста процентов. Третий прием, сглаживание характера нагрузки: мелкие случайные записи собирают в пакеты, журналы баз выносят на отдельное пространство, своп переносят в zram, а лишнюю запись времени доступа отключают в fstab:

UUID=47d3b1a8-2f0e-4c5b-9a71-8e6d3f0b2c41  /mnt/data  ext4  noatime,commit=60  0 2

Параметр commit=60 растягивает интервал сброса журнала ext4 до минуты вместо пяти секунд по умолчанию, и частота мелких записей падает в разы.

Пороги температуры wctemp и cctemp и наблюдение через hwmon

Температура в SMART-логе нормирована в Кельвинах, а пороги контроллер объявляет в identify controller:

nvme id-ctrl /dev/nvme0 | grep -E "wctemp|cctemp"
wctemp   : 350
cctemp   : 355

350 К это 77 градусов по Цельсию, 355 К это 82 градуса. Время выше порога wctemp копится в поле warning_temp_time в минутах, выше cctemp минуты собирает critical_comp_time, и у исправного сервера оба поля нулевые. При пересечении cctemp контроллер принудительно снижает скорость: накопитель режет производительность, защищая себя от перегрева. Ядро выносит температуру в стандартный интерфейс hwmon, где temp1_max соответствует wctemp, а temp1_crit соответствует cctemp:

for d in /sys/class/hwmon/hwmon*; do
  if [ "$(cat $d/name)" = "nvme" ]; then
    echo $d; cat $d/temp1_input $d/temp1_max $d/temp1_crit
  fi
done

Значение temp1_input печатается в тысячных долях градуса, например 38000 это 38 градусов. Перегрев для флеш-памяти едва ли не опаснее износа: горячие ячейки теряют заряд быстрее, а контроллер при длительном троттлинге растягивает каждую операцию. Решение скучное и рабочее: радиатор, чистая продувка корпуса и расстояние от горячих соседей по плате.

Автоматический контроль износа через smartd, скрипт с jq и cron

Служба smartd из пакета smartmontools проверяет накопители по расписанию и работает с NVMe нативно. В /etc/smartd.conf достаточно строки:

DEVICESCAN -d nvme -H -l error -f -m root -M daily

Опция -H следит за общим состоянием, -l error реагирует на рост журнала ошибок, -f отслеживает изменения атрибутов, письма уходят корню, ручная проверка выполняется командой smartctl -a /dev/nvme0. Для собственных порогов удобнее маленький скрипт на jq, он сравнивает процент износа и резерв с границами и пишет в системный журнал:

#!/bin/bash
J=$(nvme smart-log /dev/nvme0 --output-format=json)
USAGE=$(echo "$J" | jq -r '.percent_used' | tr -d '%')
SPARE=$(echo "$J" | jq -r '.avail_spare' | tr -d '%')
WARN=$(echo "$J" | jq -r '.critical_warning')
USAGE=${USAGE%%.*}
SPARE=${SPARE%%.*}
if [ "$USAGE" -ge 80 ] || [ "$SPARE" -le 10 ] || [ "$WARN" != "0" ]; then
  logger -t nvme-health "износ ${USAGE}%, резерв ${SPARE}%, critical_warning=${WARN}"
fi

Скрипт сохраняется как /usr/local/sbin/nvme-wear-check.sh с правом выполнения, а запуск по расписанию задает строка в crontab:

30 6 * * * /usr/local/sbin/nvme-wear-check.sh

Приверженцы systemd собирают ту же схему из пары юнитов: таймер с расписанием и одношаговый сервис.

[Unit]
Description=Проверка износа NVMe
[Timer]
OnCalendar=*-*-* 06:30:00
Persistent=true
[Install]
WantedBy=timers.target

Таймер кладется в /etc/systemd/system/nvme-wear-check.timer, а в парном файле nvme-wear-check.service секция [Service] содержит строку ExecStart=/usr/local/sbin/nvme-wear-check.sh, после чего достаточно systemctl enable --now nvme-wear-check.timer.

Имена полей в JSON зависят от версии nvme-cli, поэтому при первом запуске их сверяют с выводом команды nvme smart-log /dev/nvme0 --output-format=json. Порог 80 процентов износа выбран не случайно: он оставляет месяцы на спокойную закупку замены, цены не растут от срочности. Вся затея умещается в три привычки: раз в неделю смотреть smart-log, раз в квартал пересчитывать WAF по endurance-log и держать письма от smartd включенными, тогда накопитель сообщит о своем состоянии раньше, чем файловая система заметит неладное.