Накопитель NVMe редко хранит данные одним куском: контроллер видит его как набор namespace, отдельных логических пространств, у каждого из которых свой размер, формат блоков и набор атрибутов. От того, как выставлены эти атрибуты, зависит и объем, доступный системе, и размер сектора, который покажет lsblk, и даже скорость на случайных операциях. Утилита nvme-cli открывает к настройкам прямой доступ: команда Identify Namespace возвращает структуру с полями nsze, ncap, nuse, flbas, mc, dps, а команды управления создают, подключают и удаляют пространства. Потребительские модели SSD обычно отдают один namespace на весь объем, зато серверные и enterprise-накопители позволяют делить диск на несколько частей с разными параметрами. Дальше разбирается, как читать эти атрибуты, пересчитывать шестнадцатеричные значения в байты, менять формат блока с 512 на 4096 и чего стоят 8 байт метаданных на каждый сектор.

Поля nsze, ncap и nuse в выводе Identify Namespace и их пересчет в байты

Базовая команда для знакомства с накопителем выглядит так:

nvme id ns /dev/nvme0n1

Утилита отправляет контроллеру команду Identify Namespace и печатает структуру с десятками полей, из которых первые три описывают геометрию пространства. nsze задает полный размер, ncap ограничивает число блоков, которые контроллер физически выделяет под данные, nuse показывает занятость прямо сейчас. Все три значения измеряются в логических блоках, а не в байтах. Типичная картина для потребительского накопителя на 500 ГБ выглядит так:

nsze    : 0x3a380000
ncap    : 0x3a380000
nuse    : 0x1d9c0000

Пересчет в байты делается умножением. Для nsze 0x3a380000 в десятичной записи получается 976 748 544 блока, умножение на 512 байт дает 500 095 254 528 байт, то есть заявленные 500 ГБ или около 466 ГиБ. Если работать через символьное устройство контроллера, номер пространства задается ключом: команда nvme id ns /dev/nvme0 -n 1 вернет структуру для namespace 1, а специальное значение 0xffffffff попросит контроллер описать общие для всех пространств возможности. Для автоматизации удобен вывод в json: флаг -o json печатает те же поля в виде структуры, которую любой скрипт читает без разбора текста. Равные nsze и ncap означают полное выделение пространства. Когда ncap меньше nsze, накопитель переходит в режим тонкого выделения: система видит весь объем, физически же контроллер отдает блоки по мере записи. Такой режим экономит место в больших хранилищах, но платой становится риск упереться в границу и получить ошибку 0x87 Thin Provisioning Out Of Resources, если свободного запаса на устройстве не останется.

Формат логического блока FLBAS и таблица lbaf с размерами данных и метаданных

Ниже в том же выводе идут поле nlbaf, поле flbas и таблица lbaf. Значение nlbaf задано как старший индекс, поэтому реальное число строк в таблице на единицу больше. Каждая строка описывает один поддерживаемый формат: ds указывает размер данных на блок в виде степени двойки, ms отвечает за размер метаданных, rp описывает относительную производительность, где ноль означает лучший вариант, единица - хороший, двойка - допустимый. Пример для типичного потребительского накопителя:

nlbaf   : 1
flbas   : 0
lbaf  0 : ds:9  ms:0 rp:0 (in use)
lbaf  1 : ds:12 ms:0 rp:0

Строка lbaf 0 с ds 9 означает размер блока 2 в девятой степени, то есть 512 байт, а ds 12 дает 4096 байт. Поле flbas называет активный формат, ноль в нем говорит о 512-байтовой разметке, и потребительские модели почти всегда идут именно такими. Enterprise-устройства нередко добавляют форматы с метаданными, например 512 байт данных с 8 байтами под контрольную сумму или 4096 байт с 64 байтами для защиты PI. Активный формат меняется командой format с указанием индекса строки, и к нему стоит вернуться после знакомства с таблицей. На enterprise-моделях в поле flbas встречаются и составные значения: flbas 0x10 означает, что выбран индекс 0 и метаданные передаются вместе с данными, а flbas 0x21 - индекс 1 с метаданными в отдельном буфере. Относительная производительность rp подсказывает, какие форматы контроллер обрабатывает без потерь скорости: на форматах с rp 2 он может тратить больше тактов на блок, поэтому под интенсивную запись их стараются не выбирать.

Способы передачи метаданных и защита PI в полях mc, dps и nmic

Три коротких поля определяют возможности контроллера по метаданным и защите. Поле mc описывает способы передачи: бит 0 разрешает передавать метаданные вместе с данными в одном буфере, бит 1 - отдельным буфером после блока данных. Значение mc 1, типичное для потребительских моделей, означает, что доступен только расширенный вариант, где метаданные идут в каждом блоке. Поле dps управляет защитой данных PI: ноль выключает ее, значения 1, 2 и 3 включают типы 1, 2 и 3, а биты 5-6 выбирают расположение контрольной суммы в начале или в конце блока. Поле nmic касается совместного использования: бит 0 разрешает одному namespace обслуживаться двумя контроллерами сразу, что встречается на двухпортовых enterprise-накопителях.

Каждая запись PI занимает 8 байт и состоит из трех частей: 16-битной контрольной суммы guard, 16-битного application tag и 32-битного reference tag, которые драйвер или СУБД заполняет своими значениями и проверяет при чтении. Защита PI проверяет контрольную сумму каждого блока на всем пути от хоста до флеш-памяти и возвращает ошибку при расхождении. Для сервера под веб и контейнеры слой избыточен, для СУБД на enterprise-оборудовании он служит страховкой от тихого искажения данных. Включается защита на этапе создания пространства или форматирования: выбирается формат lbaf с ms больше нуля и в поле dps выставляется тип и расположение контрольной суммы.

Создание namespace командой ns create с нужным размером и форматом блока

Управление пространствами доступно, только если контроллер поддерживает Namespace Management, признак этого сидит в поле oacs структуры Identify Controller, бит 3. Проверка перед любыми изменениями выглядит так:

nvme id ctrl /dev/nvme0 | grep -i oacs

Если бит 3 установлен, пространство создается тремя командами. Порядок такой:

  1. команда ns create выделяет диапазон блоков и возвращает идентификатор нового namespace;
  2. команда ns attach подключает его к контроллеру, без этого шага блочное устройство не появится;
  3. команда ns-rescan заставляет ядро перечитать список пространств и создать узел в каталоге /dev.

Пример: на накопителе объемом 1 ТБ выделяется второе пространство размером 100 ГиБ. В блоках по 512 байт это 209 715 200 единиц, число получается делением 107 374 182 400 байт на 512:

nvme ns create /dev/nvme0 --nsze=209715200 --ncap=209715200 --flbas=0
nvme ns attach /dev/nvme0 --namespace-id=2 --controllers=0
nvme ns-rescan /dev/nvme0

Идентификатор пространства контроллер назначает сам, выбрать его нельзя, и после создания он возвращается в ответе команды. В системе появляется новое блочное устройство, что видно через lsblk:

nvme0n1     259:0    0  931.5G  0 disk
nvme0n2     259:1    0    100G  0 disk

Раздел nvme0n2 сформирован из только что выделенного пространства, и с ним работают обычные утилиты разметки. Параметр flbas выбирает строку из таблицы lbaf, поэтому до создания стоит посмотреть таблицу командой id ns и решить, какой размер блока нужен новому пространству. Флаги --nsze-si и --ncap-si позволяют задавать размер в привычных единицах вроде 100G без пересчета блоков вручную, а поддержка набора команд ZNS включается опцией --csi=2 на накопителях с соответствующей прошивкой. На потребительских моделях без Namespace Management первая же команда вернет ошибку Invalid command opcode, это нормальное поведение. Уже созданное пространство расширить стандартными средствами нельзя, приходится удалять и создавать заново, поэтому ncap разумно закладывать с запасом. Удаление идет в обратном порядке: сначала ns detach отключает пространство от контроллера, затем ns delete освобождает диапазон блоков.

Смена размера блока с 512 на 4096 байт командой format и пересоздание разделов

Переход на 4K выполняется форматированием с индексом строки lbaf:

nvme format /dev/nvme0n1 --lbaf=1

Индекс 1 здесь соответствует строке lbaf 1 с ds 12, то есть блокам 4096 байт. Ключ -s добавляет к форматированию безопасную очистку: значение 1 стирает пользовательские данные, значение 2 проводит криптографическую очистку на накопителях с шифрованием, например nvme format /dev/nvme0n1 --lbaf=1 -s 2. Для второго пространства указывается его номер ключом -n 2, иначе операция затронет только первый. Операция необратимо стирает данные на пространстве, поэтому перед ней снимают резервную копию, а после нее пересоздают разделы и файловые системы. Старая таблица разметки теряет смысл: границы, записанные в 512-байтовых секторах, на 4K-разметке укажут не туда. Файловые системы ext4 и XFS спокойно живут с 4K-блоками, обе исторически собраны под этот размер, а таблица GPT стартует разделы с 1 МБ, что кратно любому из форматов.

Объем пространства после смены формата остается прежним, меняется только единица измерения. Те же 976 748 544 блока по 512 байт превращаются в 122 093 568 блоков по 4096 байт, и lsblk покажет ту же емкость с новым размером сектора. После форматирования список устройств лучше перечитать командой ns-rescan, чтобы ядро обновило представление о секторе и не печатало устаревшие значения.

Влияние размера блока и метаданных на скорость и накладные расходы

Размер блока определяет, сколько команд система гоняет к накопителю. Лимит на одну команду задается полем mdts структуры Identify Controller и на типичных моделях составляет от 128 до 512 КиБ. Для последовательной записи 1 МиБ при 512-байтовых блоках ядру нужно собрать 2048 секторов, при 4096-байтовых всего 256. Меньше команд означает меньше расходов на сами транзакции и очереди, поэтому на больших файлах 4K-разметка обычно дает заметный выигрыш, особенно на моделях со слабым процессором в контроллере. Случайные мелкие операции тоже выигрывают от выравнивания: блок файловой системы точно ложится на блок накопителя без дробления.

Объем накладных расходов считается на всю емкость. Терабайт данных при формате 512+8 хранится в 2 147 483 648 блоках, умножение на 8 байт дает 17 179 869 184 байт, то есть 16 ГиБ служебных данных, которые проходят по шине вместе с полезной нагрузкой. Метаданные стоят дороже, чем кажется. Формат 512+8 добавляет к каждому сектору 8 байт, а 4096 с 64 байтами метаданных - те же 1,6 процента сверху, прибавка в обоих случаях одинаковая. Реальные расходы растут на этапе обработки: отдельный буфер метаданных требует от контроллера дополнительной работы на каждый блок, зато расширенный формат, где метаданные едут вместе с данными, экономит одну операцию на блок, поэтому бит 4 в flbas предпочтителен там, где метаданные вообще нужны. Для обычных нагрузок практичный выбор - простой формат 4096 байт с ms 0, если он есть в таблице lbaf. СУБД с собственными контрольными суммами защита PI ничего не добавляет, а вот серверы, где целостность данных важнее пары процентов скорости, получают от нее ощутимую страховку.

Проверка активных и выделенных пространств командой list-ns и контроль nuse

Состояние всех пространств накопителя показывает команда:

nvme list-ns /dev/nvme0 --all

Вывод представляет собой список идентификаторов:

[   1]:0x1
[   2]:0x1

Строка вида [2]:0x1 говорит о namespace с номером 2, а ключ --all добавляет выделенные, но еще не подключенные к контроллеру пространства, по умолчанию команда печатает только подключенные. Сводка по устройствам и размерам выходит командой nvme list, а в sysfs каждый namespace представлен каталогом вроде /sys/class/nvme/nvme0/nvme0n1 с файлом size, где размер хранится в 512-байтовых секторах.

За тонким выделением следят по полю nuse из вывода id ns: разница между ncap и nuse показывает, сколько блоков еще можно записать до исчерпания запаса. Быстрая проверка занятости из консоли выглядит так:

watch -n 60 "nvme id ns /dev/nvme0n1 | grep -E 'ncap|nuse'"

Команда обновляет показания раз в минуту, а чтение файла size из sysfs дает размер в секторах без вызова утилиты: cat /sys/class/nvme/nvme0/nvme0n1/size. Скрипт мониторинга может снимать значение раз в минуту и отправлять уведомление при заполнении 90 процентов, это дешевле, чем объяснять пользователям, почему запись внезапно падает с ошибкой 0x87. Список пространств, их размеры и формат блока разумно сохранять в заметки после каждой перекройки диска: восстановить конфигурацию по памяти после сбоя почти невозможно, а одна команда id ns на момент работ экономит часы разбирательств.