Корпоративный накопитель под нагруженной базой данных изнашивается быстрее, чем обещает паспорт, хотя файловая система пишет ровно то, что просит приложение. Причина не в объеме работы, а в том, как контроллер раскладывает страницы по флеш-памяти: данные с разным сроком жизни перемешиваются, и служебной переработки становится вдвое больше полезной записи. Техническое предложение TP4146 добавило в протокол NVMe механизм Flexible Data Placement, при котором хост подсказывает накопителю, какие записи похожи по времени жизни. Ядро Linux поддерживает механизм начиная с версии 6.12, а утилита nvme-cli прилагает к нему команды наблюдения. В статье разобраны модель reclaim groups и reclaim unit handles, включение через файлы sysfs, контроль командой nvme fdp и числовая оценка выигрыша.
Проблема усиления записи и идея технического предложения TP4146
Флеш-память устроена асимметрично. Запись идет страницами порядка 4-16 КБ, а стирание возможно только блоками размером от десятков до сотен мегабайт. Контроллер не может стереть кусочек блока, в котором еще живут полезные данные, поэтому перед стиранием живые страницы копируются в другое место. Чем сильнее перемешаны данные с разным сроком жизни, тем больше лишних копирований. Отношение объема записи на носитель к объему записи от хоста называют усилением записи и обозначают латиницей WAF, write amplification factor. У корпоративного накопителя на случайной перезаписи типичное значение WAF лежит между 2 и 3: при 500 ГБ записей от приложения на флеш-память ложится от 1 до 1,5 ТБ.
Корень проблемы в том, что накопитель не знает, какие данные останутся актуальными годами, а какие устареют через секунду. Наглядный пример показывает масштаб потерь. В блок стирания на 64 МБ контроллер уложил страницы журнала предзаписи, устаревающие через минуты, вперемешку со страницами таблиц, нужными годами. Через час журнал уже бесполезен, но таблицы живы, и стереть блок нельзя: при доле живых данных 30% освобождение блока требует переноса порядка 19 МБ. Таких блоков тысячи, и служебная запись легко удваивает нагрузку на носитель. Файловая система и приложения информацией о сроках жизни располагают, но стандарт NVMe долго не давал способа ее передать. Техническое предложение TP4146, принятое организацией NVM Express в 2022 году, закрыло пробел: хост размечает потоки записей, накопитель держит похожие по сроку жизни данные вместе и перерабатывает их одной группой. Подсказки носят рекомендательный характер, устройство вправе их игнорировать, однако при разумной разметке объем служебных копирований сокращается в разы. Похожую задачу раньше решал механизм Streams, он с FDP несовместим и на практике прижился слабо, потому что давал слишком грубое управление.
Модель reclaim groups, reclaim units и reclaim unit handles
Модель размещения строится на трех сущностях. Reclaim group, группа переработки, самая крупная единица управления: накопитель собирает и перерабатывает мусор именно по группам. Внутри группы выделены reclaim units, единицы переработки, размером порядка десятков и сотен мегабайт, близко к границам блоков флеш-памяти. Видимая хосту часть модели, reclaim unit handles, дескрипторы переработки: каждый дескриптор в каждый момент связан с одной единицей переработки, и записи, помеченные им, попадают в связанную единицу.
Параметры размещения накопитель описывает в дескрипторе конфигурации FDP: число групп переработки, число дескрипторов и размер единиц. У реальных корпоративных моделей число групп измеряется десятками, дескрипторов типично от 8 до 64, а конфигураций у одного устройства бывает несколько на выбор администратора. Если накопитель объявляет 32 группы по 256 единиц размером 64 МБ, одна группа покрывает 16 ГБ медиа. Идентификатор дескриптора передается в служебном поле каждого запроса записи, а записи без подсказки обслуживает дескриптор с номером 0. Смысл разметки прост: журнал предзаписи СУБД, переписываемый каждые секунды, помечается одним дескриптором, архивы другим, и контроллер больше не смешивает их в одном блоке стирания. Когда в единице переработки почти не остается живых страниц, она стирается без лишних копирований, и выигрыш достается и ресурсу, и задержкам.
Проверка поддержки FDP в накопителе и подготовка ядра Linux
Механизм требует поддержки с обеих сторон. В ядре она появилась в версии 6.12, вышедшей в конце 2024 года: драйвер nvme научился читать дескрипторы конфигурации и отдавать их в sysfs и в passthrough-интерфейс. В накопителях FDP встречается пока только в корпоративных моделях последних поколений форматов U.2 и E1.S, потребительские диски его не объявляют. Утилита nvme-cli нужна свежая, из актуальной ветки 2.x.
Проверка начинается с трех коротких команд:
uname -r
nvme version
ls /sys/class/nvme-subsystem/nvme-subsys0/nvme0n1/fdp/
Если каталог fdp у тома существует, ядро увидело поддержку размещения, а его файлы описывают выбранную конфигурацию и состояние дескрипторов. Отсутствие каталога означает, что не совпало что-то одно: версия ядра, прошивка или сам накопитель. Вторую сторону проверяют прямо из nvme-cli:
nvme fdp configs /dev/nvme0n1
На поддерживающем устройстве команда напечатает перечень конфигураций с числами групп и дескрипторов, на обычном диске вернет отказ. Ориентироваться разумно на оба способа сразу: прошивкам некоторых корпоративных моделей обновление нужно именно для того, чтобы объявить FDP, и до него каталог в sysfs не появится.
Включение размещения через sysfs и выбор конфигурации накопителя
Размещение включается на уровне тома. Основной путь в свежих ядрах лежит через sysfs: файл enable в каталоге fdp переключает механизм, а чтение файла показывает текущее состояние.
cat /sys/class/nvme-subsystem/nvme-subsys0/nvme0n1/fdp/enable
echo 1 | sudo tee /sys/class/nvme-subsystem/nvme-subsys0/nvme0n1/fdp/enable
Тот же результат дает признак FDP с номером 0x1d: младший разряд значения включает механизм, старшие разряды передают номер конфигурации.
nvme set-feature /dev/nvme0 -f 0x1d -v 0x10001
Значение 0x10001 включает размещение и одновременно выбирает конфигурацию номер 1 из списка, полученного командой nvme fdp configs. Выбор конфигурации стоит соотносить с нагрузкой: больше групп переработки означает более тонкую разметку, но и больше служебных таблиц в контроллере. Для начала разумно взять конфигурацию с 16-32 дескрипторами, типичный сервер они покрывают с запасом.
Текущее состояние признака читается обратной командой:
nvme get-feature /dev/nvme0 -f 0x1d
Она возвращает значение, из которого видны и сам факт включения, и выбранный номер конфигурации. Отключение выполняется симметрично записью нуля в файл enable или нулевого значения признака. Данные при этом остаются на месте: механизм не переносит пользовательские данные, а управляет только их раскладкой, поэтому переключение безопасно даже на работающем томе. После включения файл enable и вывод nvme fdp configs покажут, что том перешел в режим размещения и какой конфигурацией он живет.
Наблюдение за дескрипторами и статистикой командами nvme fdp
nvme-cli собирает все команды наблюдения в группу fdp, и каждая из них читает свой служебный журнал накопителя.
nvme fdp usage /dev/nvme0n1
nvme fdp status /dev/nvme0n1
nvme fdp stats /dev/nvme0n1
nvme fdp events /dev/nvme0n1
Команда usage показывает, сколько единиц переработки числится за каждым дескриптором прямо сейчас. Status раскрывает состояние дескрипторов: к какой единице они привязаны и сколько живых данных в них накопилось. Stats отдает счетчики записей от хоста и на носитель, из пары которых считается фактический WAF. Events представляет журнал событий: заполнение дескрипторов, завершение переработки, реакции на команды освобождения.
Отдельная активная команда update освобождает единицу переработки, привязанную к дескриптору:
nvme fdp update /dev/nvme0n1 -r 3
Ключ -r передает номер дескриптора. Прием полезен при ротации: когда журнал предзаписи переехал в новый файл, старые записи уже неактуальны, и ранний сигнал освобождения ускоряет переработку. Счетчики имеет смысл снимать регулярно, разница между соседними замерами точнее разовых значений. Для этого подходит простая строка в планировщике:
*/30 * * * * /usr/bin/nvme fdp stats /dev/nvme0n1 >> /var/log/fdp-stats.log 2>&1
Полчаса как шаг дают достаточное разрешение: журнал предзаписи успевает несколько раз смениться, и тренд WAF становится заметным на графике за сутки. Пример расчета по stats: за сутки накопитель принял от хоста 700 ГБ, а перенес на флеш-память 1470 ГБ, WAF равен 2,1. Через месяц после разметки потоков те же 700 ГБ от хоста превратились в 910 ГБ на носителе, WAF 1,3, и это уже измеримый результат, а не обещание производителя.
Разметка потоков приложений и роль io_uring
Выигрыш появляется только тогда, когда подсказки реально различаются по потокам, а записи без подсказок, уходящие под дескриптор 0, не обесценивают картину. Обычная запись через файловую систему и системные вызовы read и write подсказку не передает: ею управляет ядро, и данные попадают в дескриптор по умолчанию. Канал для подсказок открывает io_uring: с версии ядра 6.12 passthrough-запросы позволяют задавать дескриптор на каждый запрос записи, а движок io_uring_cmd в fio уже умеет такие тесты. Прикладному коду достаточно отправлять записи через io_uring с указанием нужного дескриптора, остальное делает драйвер.
Практичная разметка сервера с СУБД выглядит так:
- журнал предзаписи и контрольные точки живут секунды или минуты и получают отдельный дескриптор;
- файлы таблиц и индексы с долгим сроком жизни собираются в общий дескриптор;
- временные таблицы, сортировки и промежуточные результаты обслуживает собственный дескриптор;
- резервные копии и выгрузки идут под дескриптором последовательных потоковых записей;
- мелкие служебные записи файловой системы остаются без подсказок и попадают в дескриптор 0.
Число задействованных дескрипторов держат умеренным: 3-5 активных потоков достаточно, дробление на десятки без ясного срока жизни только усложняет контроль и не добавляет переработке точности. Разметке помогает и группировка фиксаций. Приложение, отправляющее записи одного потока крупными порциями и фиксирующее их одной операцией fsync, дает контроллеру более ровный поток данных внутри дескриптора, чем россыпь мелких перезаписей. Единица переработки заполняется равномернее, живая доля падает синхронно по всему дескриптору, и переработка упрощается без всяких дополнительных команд.
Оценка выигрыша в цифрах и практические рекомендации
Экономика механизма видна на ресурсе записи. Пусть корпоративный накопитель несет паспортный ресурс 2000 ТБ записи на флеш-память. При WAF 2,0 приложение сможет записать 1000 ТБ, при WAF 1,3 уже около 1540 ТБ, то есть срок службы под той же нагрузкой вырастает в полтора раза. Второй эффект заметен в задержках: меньше фоновых копирований означает меньше пауз, и верхние перцентили латентности на смешанной нагрузке выравниваются. Для базы данных с требованием отклика в единицы миллисекунд это нередко ценнее самого ресурса.
Внедрение стоит вести по правилам измерений. До включения снимают базовую линию нагрузкой fio, близкой к производственной, и повторяют тот же тест после разметки, сравнивая WAF и хвосты задержек. Изменения вносят по одному: сначала разметка журнала, затем следующего потока, иначе приписать эффект будет нечему. Состояние наблюдают командами nvme fdp по расписанию, журнал events просматривают после пиков нагрузки, а при ротации больших файлов подают сигналы освобождения. На потребительском диске или на старом ядре вся затея не даст ничего, и отсутствие каталога fdp в sysfs сообщит об этом в первые же минуты проверки.
Файловые системы и СУБД пока не передают подсказки размещения сами, но направление развивается быстро, и первым пользователям здесь достается наибольший запас ресурса. Администратор, который уже сегодня размечает потоки вручную через io_uring и держит WAF под контролем командами nvme-cli, встретит нативную поддержку во всеоружии.