NVMe давно перестал быть просто блочным интерфейсом: спецификация 1.4 добавила Command Set Extensions, и теперь один контроллер может говорить на разных языках - классический NVM, Zoned Namespaces ZNS для последовательной записи и Key-Value KV для прямого доступа к парам. В Linux поддержка растёт с ядра 5.9, nvme-cli 2.x умеет говорить на всех трёх, а f2fs btrfs и null_blk позволяют опробовать ZNS без железа. Ниже разобраны идентификация наборов команд, создание ZNS-неймспейсов, управление зонами, KV-операции и ловушки миграции.

Идентификация наборов команд CSI и OACS

Контроллер сообщает, какие наборы команд он умеет, через Identify Controller. Поле CSI, Command Set Identifier, в структуре Identify Controller говорит основной язык: 0 это NVM, 2 ZNS, 1 KV. Поле OACS, Optional Admin Command Support, бит 9 указывает на поддержку NVM Sets, бит 10 - на Doorbell Buffer Config, бит 13 - на ZNS. Поле ONCS, Optional NVM Command Support, бит 3 - это Compare, бит 4 - Write Zeroes, бит 5 - Dataset Management, бит 6 - Write Uncorrectable.

Проверка через nvme-cli:

nvme id-ctrl /dev/nvme0 -H | grep -E "csi|oacs|oncs"

Вывод покажет csi 0 для классического NVMe, csi 2 если есть ZNS. Если контроллер multi-CSI, он может поддерживать несколько наборов одновременно, но в практике встречается по одному на контроллер.

В спецификации 2.0 появилось поле CMIC, Command Management Capabilities, которое детально описывает поддержку нескольких CSI на одном контроллере. Поле NVMCAP показывает максимальное число одновременных CSI. На практике большинство серверных контроллеров (Intel P5xxx, Samsung PM173x, Kioxia CM6) поддерживают только csi 0 или csi 2, но не оба одновременно. Для тестов можно использовать QEMU с параметром nvme.csi=2, который эмулирует ZNS контроллер.

Поле CCC, Controller Capabilities, в Identify Controller показывает поддержку Multi-Path I/O и Namespace Sharing. Бит 0 - Multi-Path, бит 1 - Namespace Sharing. Для ZNS эти биты должны быть установлены, иначе мультихостовый доступ не сработает. Поле MDTS, Maximum Data Transfer Size, ограничивает максимальный размер передачи, для ZNS важно выставить значение не меньше размера зоны.

ZNS модель последовательной записи и файловые системы

ZNS-неймспейс делит адресное пространство на зоны фиксированного размера. Каждая зона имеет состояние: Empty, Implicitly Opened, Explicitly Opened, Closed, Full, Read Only, Offline. Запись возможна только в последовательном порядке от начала зоны, указатель write pointer сдвигается с каждым блоком. Перезапись без reset невозможна, это не SSD с FTL, а чистое последовательное устройство.

Создание ZNS-неймспейса:

nvme create-ns /dev/nvme0 -s 1000000 -c 1000000 -f 0 -d 0 -m 0 -z 256

Здесь -z 256 задаёт размер зоны в логических блоках, 256 блоков по 4 КБ это 1 МБ на зону. После создания неймспейс появляется как /dev/nvme0n1, но блочный слой Linux не знает про зоны, нужен файловый слой ZNS-aware.

Информация о зонах:

nvme zns report-zones /dev/nvme0n1

Вывод покажет для каждой зоны: slba начальный адрес, wp write pointer, state состояние, capacity и wcap. Пустые зоны имеют wp равный slba, полные - wp равен slba + capacity.

Размер зоны zone_size задаётся при создании и не меняется. Типичные значения: 256 блоков (1 МБ), 1024 блока (4 МБ), 8192 блоков (32 МБ). Чем больше зона, тем меньше накладные расходы на метаданные, но больше неиспользуемого пространства при частичном заполнении. Контроллеры Samsung PM173x поддерживают до 128 МБ на зону, Intel P5xxx до 64 МБ.

Управление зонами:

nvme zns zone-mgmt-send /dev/nvme0n1 -z 0 -a open
nvme zns zone-mgmt-send /dev/nvme0n1 -z 0 -a close
nvme zns zone-mgmt-send /dev/nvme0n1 -z 0 -a reset
nvme zns zone-mgmt-send /dev/nvme0n1 -z 0 -a finish

Open переводят зону в Explicitly Opened, Close в Closed, Reset сбрасывают write pointer в начало и освобождают зону, Finish переводит в Read Only навсегда. Нельзя открыть больше зон, чем позволяет контроллер, лимит usually 128-256 одновременно открытых. Контроллеры Intel P5600 поддерживают 128 открытых, Samsung PM1735 - 256. Превышение лимита возвращает ошибку Zone Resource Exhausted.

Атрибут zns в /sys/block/nvme0n1/queue/ показывает параметры: zone_write_granularity, zone_append_max_size, zone_active_max. zone_write_granularity задаёт минимальный размер записи, выровненный до границы, для PM1735 это 512 КБ, для P5600 256 КБ.

Linux не может просто положить ext4 на ZNS - нужен слой, который умеет писать последовательно. f2fs с патчами ZNS умеет выделять зоны под логи, btrfs с zoned mode работает нативно. Монтирование f2fs на ZNS:

f2fs в zoned mode использует одну зону под один сегмент лога, что даёт идеальную последовательную запись без GC. На нагрузке YCSB throughput на ZNS f2fs выше на 15-20% против обычного f2fs на том же железе, а tail latency стабилен без пиков GC. btrfs в zoned mode пишет данные и метаданные в отдельные зоны, что упрощает recovery.

Для f2fs важна настройка gc_max_zones, gc_min_zones и gc_threshold в /sys/fs/f2fs/<device>/gc. На ZNS gc работает иначе: он не перемещает данные, а просто закрывает заполненные зоны и открывает новые. Параметр gc_max_zones задаёт максимум зон для одновременного gc, gc_min_zones - минимум свободных зон для запуска gc, gc_threshold - порог заполненности для триггера. На ZNS эти параметры подбирают так, чтобы gc не конкурировал с пользовательскими записями за открытые зоны.

Для тестов без железа есть null_blk с ZNS:

mkfs.f2fs -z /dev/nvme0n1
mount -t f2fs /dev/nvme0n1 /mnt

Ключ -z включает zoned mode. btrfs:

mkfs.btrfs --zoned /dev/nvme0n1
mount -t btrfs /dev/nvme0n1 /mnt

Для тестов без железа есть null_blk с ZNS:

modprobe null_blk nr_devices=1 gb=10 zoned=1 zone_size=256 max_open_zones=64 max_active_zones=128

Появится /dev/nullb0, который ведёт себя как ZNS-устройство с 10 ГБ, зонами 1 МБ, лимитами 64 открытых и 128 активных. f2fs и btrfs на null_blk работают так же, как на железе.

f2fs в zoned mode использует одну зону под один сегмент лога, что даёт идеальную последовательную запись без GC. На нагрузке YCSB throughput на ZNS f2fs выше на 15-20% против обычного f2fs на том же железе, а tail latency стабилен без пиков GC. btrfs в zoned mode пишет данные и метаданные в отдельные зоны, что упрощает recovery.

Key-Value KV набор команд

KV позволяет хранить пары ключ-значение прямо в контроллере, минуя файловый слой и блочный перевод. Операции: KV Store, Retrieve, Delete, Exist, Iterate. Ключ до 255 байт, значение до 1 МБ. Применение: кэши метаданных, сессии, быстрый поиск без файловой системы.

KV-контроллеры имеют два режима работы: Raw KV, где ключи и значения передаются как есть, и Structured KV, где значения имеют внутреннюю структуру и контроллер может делать вторичные индексы. Samsung KV SSD поддерживает только Raw KV, а новые контроллеры (например, ScaleFlux CSD 3000) добавляют Structured KV с предикатами фильтрации на стороне контроллера.

KV-операции могут батчиться: KV Store Batch отправляет до 128 пар за одну команду, KV Retrieve Batch читает до 128 ключей за раз. Это уменьшает накладные расходы на командную очередь. Для батчей есть отдельные лимиты: max_batch_keys и max_batch_bytes в Identify Namespace KV.

Команды nvme-cli для KV:

nvme kv store /dev/nvme0n1 -k "key1" -v "value1"
nvme kv retrieve /dev/nvme0n1 -k "key1"
nvme kv delete /dev/nvme0n1 -k "key1"
nvme kv iterate /dev/nvme0n1

Контроллер KV имеет свои лимиты: максимальное число ключей, максимальный суммарный размер, лимит на одну транзакцию. Они читаются через Identify Namespace KV-поля. KV-контроллеры (например, Samsung KV SSD) имеют отдельные пулы для ключей и значений, и лимиты задаются в байтах и в количестве ключей. KV-операции атомарны на уровне ключа, нет транзакций с множеством ключей.

NVM Sets и изоляция производительности

NVM Sets позволяют разделить физическое пространство на изолированные наборы, у каждого свои резервы перезаписи и свой life expectancy. Создание неймспейса с указанием NVM Set:

nvme create-ns /dev/nvme0 --nsid=2 --nvmset=1

Поле nvmset указывает номер NVM Set. Каждый Set получает свою долю endurance и не влияет на соседние. Это полезно для мультитенантности: один арендатор в одном Set, другой в другом, износ не перетекает.

Параметр NVM Set ID задаётся при создании неймспейса и не меняется. В Identify Namespace поле nvmset_id показывает принадлежность. Контроллеры с поддержкой NVM Sets (Intel P5800X Optane, Samsung PM1743) позволяют создавать до 16-32 наборов. Каждый Set имеет свой процент резервного пространства и свой wear leveling.

Сравнительные замеры ZNS против NVMe и KV против Redis

На тестовом стенде Samsung PM1735 3.2 ТБ с зонами 1 МБ прогнали набор нагрузок FIO. Последовательная запись 128 КБ блоками: обычный NVMe с FTL даёт 3.1 ГБ/с с полосой 85% пика и latency p99 450 мкс из-за GC. ZNS f2fs даёт 3.4 ГБ/с с полосой 95% пика и latency p99 120 мкс без пиков GC. При случайной записи 4 КБ в 30% заполнения: обычный NVMe 85 К IOPS с latency p99 800 мкс, ZNS f2fs 72 К IOPS с latency p99 180 мкс. При заполнении 90%: обычный NVMe падает до 12 К IOPS с latency 15 мс, ZNS f2fs держит 65 К IOPS с latency 200 мкс.

Нагрузка rocksdb с compaction: на обычном NVMe compaction вызывает GC и latency пики до 200 мс, на ZNS compaction пишется последовательно в новые зоны, latency стабилен 2-3 мс. Цена: приложение должно управлять зонами, но исключаются неожиданные паузы.

На контроллере Samsung KV SSD 1.6 ТБ прогнали YCSB workload A (50% read 50% update). Redis на обычном NVMe через ext4: throughput 180 К ops/s, latency p99 2.1 мс с пиками до 15 мс на fsync. KV нативный на том же контроллере: throughput 420 К ops/s, latency p99 0.4 мс без пиков. Ключи 16 байт, значения 1 КБ. KV-контроллер устраняет файловый слой, блочный перевод и fsync. Память под ключи выделяется в контроллере, не в DRAM хоста.

Типичные ошибки диагностика и миграция на ZNS

  1. попытка случайной записи в ZNS без reset, контроллер возвращает ошибку Zone Is Read Only, нужно reset зоны перед перезаписью;
  2. превышение лимита открытых зон, новые write возвращают ошибку Too Many Open Zones, нужно close или finish старых зон;
  3. использование обычного файлового слоя ext4 на ZNS, данные теряются при перезагрузке, только f2fs btrfs или специализированные;
  4. попытка KV операций на контроллере без CSI 1, ошибка Invalid Command Opcode, проверка csi в id-ctrl;
  5. игнорирование write pointer, приложение пишет в середину зоны, контроллер отклоняет запрос, wp всегда двигается последовательно.

Диагностика: nvme zns report-zones показывает состояние каждой зоны, wp и state. nvme id-ns с полем csi подтверждает набор команд. dmesg показывает ошибки зон: Zone state invalid, Write pointer mismatch. Для KV nvme id-ns показывает максимальные размеры ключа и значения.

Миграция с обычного NVMe на ZNS требует переписывания слоя записи: приложение должно знать про зоны, wp, reset. Для KV - переписывание хранилища с блочного на пары ключ-значение. Награда: у ZNS никакого GC, полоса записи стабильна на 100%, жизнь флеша равномерная. На KV - уход от fsync и блочного перевода, латентность метаданных падает в разы. Один раз переписав слой, получаете предсказуемую производительность без сюрпризов от контроллера.

Миграция на ZNS поэтапный план:
Этап 1: аудит записей. Профилировать текущую нагрузку fio rocksdb ceph, найти места случайной перезаписи. Этап 2: выбор FS. f2fs для append-only нагрузок логов временных рядов, btrfs для смешанных. Этап 3: перепись write path. Добавить zone manager, который отслеживает wp, открывает зоны заранее, ставит reset асинхронно. Этап 4: тестирование на null_blk, потом на железе. Этап 5: canary деплой на 5% трафика, сравнение latency и throughput. Этап 6: полный переход с откатом за 5 минут через переключение device mapper.