Твёрдотельное кэширование
всей системы посредством
dm-cache и initramfs

Linux 3.9+

Переход на SSD неминуем, как ни крути, однако по сей день удельная стоимость твёрдотельного бита всё ещё весьма высока, поэтому, переходя на SSD, приходится либо затягивать потуже пространственные аппетиты (что, впрочем, в нашу облачную эпоху в целом ряде случаев уже не такая проблема), либо отдавать в распоряжение новой технологии лишь часть файловой системы, либо же — рассматривать её как дополнительное звено кэша перед традиционным HDD-накопителем, совмещая тем самым скорость SSD и ёмкость HDD.

В данной статье мы рассмотрим применение технологии dm-cache для кэширования корневой файловой системы Linux. Разделы, монтируемые после "/" настраиваются относительно тривиально (без модификации initramfs), поэтому их конфигурация значительно проще, смотри, например, ssd-caching-using-dmcache-tutorial. Большинство инструкций по настройке SSD кэширования часто и останавливаются на хомяке или других вторичных разделах, однако "/" от кэша выигрывает не меньше.

Вообще

Вообще, Linux из коробки предлагает уже две технологии для организации SSD-кэширования:

Обе технологии сравнимы по всем характеристикам, но имеют существенные отличия в настройке. Bcache в этом смысле к сожалению проигрывает, так как кэширует раздел со специальным суперблоком, поэтому его затруднительно подключать к вовсю имеющейся файловой системе, не говоря уже о корневом разделе, а тем более, на горячей системе. Dm-cache в этом плане удобнее, хотя и у него есть своя заморочка с разделом метаданных, о чём позже.

Не повторяйте это дома

Dm-cache всё ещё (Linux 3.12) является EXPERIMENTAL фьючей ядра, поэтому смело перекомпилируйте ядро для включения этой опции, если она у вас отключена! Вся процедура создания SSD кэша посредством dm-cache сводится к команде вида:

# dmsetup create <имя_кэша>
  --table '0 <число_блоков> cache
  /dev/<раздел_метаданных>
  /dev/<кэш_устройство>
  /dev/<исходное_устройство>
  512 1 writeback default 0'

(Одиночные кавычки обязательны.) Например,

# dmsetup create sdb2c
  --table '0 391168 cache /dev/sda4 /dev/sda3
  /dev/sdb2 512 1 writeback default 0'

Подробнее о параметрах читайте в документации. Скажу лишь, что число_блоков следует находить через blockdev --getsz /dev/XXX — не перепутайте с размером раздела в байтах!

До выполнения этой операции нам необходимо иметь обычный раздел исходное_устройство, которое мы будем кэшировать (вообще говоря, это может быть любое блочное устройство), а также подготовить два раздела на SSD: для кэша и для метаданных.

Размер раздела для метаданных может быть рассчитан по недокументированной на момент написания статьи формуле 4 Мб + 16 байт * число_блоков, однако я, например, просто создал раздел заведомо большего размера, чтобы дополнительно зарезервировать им место на SSD (что, как говорят, полезно для жизни и производительности флеш-памяти). Данный раздел можно и нужно TRIMировать командой

# fstrim -v /mnt/disk

Или другим удобным способом (ldparm? blkdiscard?), например, не требующим создания на разделе файловой системы, однако мне такового быстро отыскать не удалось. Далее необходимо очистить его суперблок, чтобы dm-cache не начал выдавать невнятные ошибки, пытаясь поднять ещё не созданные метаданные:

# umount /mnt/disk
# dd if=/dev/zero of=/dev/<метаданные> bs=4k count=1

Теперь, после выполнения dmsetup create в директории /dev/mapper появится устройство с именем имя_кэша, которое можно будет монтировать.

Корень

Всё бы хорошо, но настройка кэширования корневой системы осложняется тем, что весь доступ к ней должен идти через кэш, так как при SSD-кэшировании, в отличие от буферов в оперативной памяти, так называемые dirty (грязные, изменённые) блоки переживают перезагрузку и отключение питания. Так что любой доступ минуя кэш для не ro-устройства будет некорректным. Поэтому мы должны поднимать кэш одновременно с основной файловой системой. В этой статье мы покажем, как это можно сделать с помощью образа initramfs (для mkinitcpio я где-то в интернетах видел примеры тоже, кстати).

Initramfs — это по существу миниатюрная, купированная версия нашей ОС, включающая в себя ядро и необходимые для начальной загрузки модули. Нам в качестве таковых следует включить dm_mod, dm_cache и dm_cache_mq в /etc/initramfs-tools/modules. Затем следует создать скрипт активизации кэша в /etc/initramfs-tools/scripts/init-premount/ вроде

#!/bin/sh
dmsetup create sdb6c --table '0 62498816 cache \
/dev/sda3 /dev/sda1 /dev/sdb6 512 1 writeback \
mq 0'

Теперь нужно выполнить стандартную сборку образа initrd. Initrd — образ, собираемый подсистемой initramfs; валяется в папках наподобие /boot и получает управление перед загрузкой основной системы. Собирать его можно примерно так:

# rm /boot/initrd.img-`uname-r`
# update-initramfs -c -k `uname -r`

Update-initramfs предупреждает о недоступности устройства, но это не страшно — образ всё равно будет собран. Также не забудьте перед перезагрузкой обновить свой fstab, заменив кэшируемое устройство на /dev/mapper/<имя_кэша>.

Чтобы проверить, что необходимые модули и скрипты внедрились в образ initrd, можно воспользоваться командой lsinitramfs initrd.img-`uname -r` XXX.

Нюанс здесь связан с тем, что т.к. мы кэшируем root и весь ввод-вывод должен адресоваться к кэшу, а не к оригинальному устройству, то нам нужно также обновить настройки загрузчика. Я не нашёл, как заставить update-grub поменять root, поэтому я один раз загружаюсь вручную, меняя рут в меню grub'а на устройство /dev/mapper/XXX, а затем уже в загруженной таким образом системе делаю update-grub, который, благодаря своему osprober'у уже генерирует правильное меню для grub. Правда одну и ту же систему он, естественно, находит дважды, так как считает /dev/mapper/XXX обычным диском. В результате этого, кстати, в системе будут присутствовать два диска с одним UUID, но лишь один из них будет примонтирован, если ваши grub и fstab железно настроены на /dev/mapper/XXX. Чтобы исключить загрузку по UUID, рекомендую добавить GRUB_DISABLE_LINUX_UUID=true в настройки grub.

Некоторую отладку initramfs можно производить в busybox консоли, встроенной по умолчанию в него. Как нормально переходить в консоль, я, честно говоря, не знаю, но если в качестве root передать в ядро заведомо не существующее устройство, то система после rootdelay автоматически переключает в busybox. В любом случае, крайне рекомендуется иметь на машине несколько вариантов загрузки с разными initrd, чтобы не получить ненароком не загружающуюся систему.

Другой момент связан с механизмом работы самого dm-cache. Дело в том, что для оптимизации и организации кэша он ведёт статистику по всем блокам устройства. Однако при перезагрузке, если не были выполнены команды dmsetup suspend и dmsetup remove, то метаданные (собственно, статистика) будут считаться грязными и сбрасываться при подключении. В случае обычных разделов выполнять данные команды после отмонтирования не составляет большого труда, но для корневого раздела настроить это мне не удалось, так как корневой раздел, насколько я выяснил, в Debian вообще не отмонтируется — в рамках initramfs по крайней мере. Соответственно и произвести его валидное завершение через dmsetup невозможно. Вероятный путь к обходу проблемы лежит в модификации последних команд initramfs, где происходит switch_root. Я же надеюсь лишь на то, что практически не выключаю и не перезагружаю систему, поэтому большую часть времени статистика актуальна. На валидность самих закэшированных блоков потеря статистики не сказывается. Т.е. сам кэш будет сохранён даже при отключении питания, в отличие от метаданных.

Отключение кэша

Отключить кэш может понадобиться при любой переорганизации системы или съёме накопителей. Сделать это можно примерно так:

$ sudo umount /dev/mapper/cached
$ sudo dmsetup table cached
0 1048576000 cache 252:3 252:4 252:0 512 1 \
writeback default 0
$ sudo dmsetup suspend cached
$ sudo dmsetup reload cached \
--table '0 1048576000 cache 252:3 252:4 252:0 512 0 \
cleaner 0'
$ sudo dmsetup resume cached
$ sudo dmsetup wait cached

Суть процесса состоит в переключении политики с default/mq на cleaner и ожидании сброса dirty-блоков. Однако ясно, что откэшировать корневую систему на горячую не получится. Здесь возможны несколько вариантов. Во-первых, можно сделать специальный initramfs-образ и соответствующую запись для загрузчика, который будет выполнять данные инструкции сразу после подключения кэша. Менее красивый вариант — это воспользоваться любым LiveCD с ядром не ниже 3.9 и выполнить команды вручную. Совсем хардкорный способ состоит в изменении параметра root ядра на реальное (кэшируемое) устройство, в этом случае кэш не сможет стартовать, так как кэшируемое устройство будет занято и мы получим наш любимый busybox на initramfs.

Кстати, в ядрах до 3.11 есть баг, связанный с тем, что если откэшировать раздел, а потом опять подключить, то он будет показывать ненулевое количество dirty блоков. Но этот баг чисто информационный, на самом деле все грязные блоки добросовестно записываются на исходное устройство.

Заключение

Как пишут сами разработчики, настоящая архитектура dm-cache не совершенна и может быть улучшена в случае интеграции с менеджером виртуальной памяти. Автор bcache комментирует, что dm-cache вообще-то не совсем и кэш, так как он кэширует фиксированные по размеру блоки, а bcache — переменные. Также есть мнение, что кэширование на уровне файловой системы могло бы позволить использовать больше информации для оптимизации процесса.

Так или иначе, тестирования производительности я не проводил, зато прочёл многое из списка рассылки и склонен к оптимизму]

Ссылки:

В качестве альтернативных технологий см. также flashcache и EnhancedIO.

 

shitpoet@gmail.com

 



 

free hit counters