В данном документе представлены результаты исследований исходников ZFSonLinux-0.6.5.4. В некоторых случаях используются иллюстрации из презентаций и документов Oracle, OpenSolaris, FreeBSD. Это используется в тех случаях, когда документация из других реализаций ZFS не конфликтует с ZoL.
Обратите внимание, к примеру, на разницу между zfs и ZFS:
ZFS - под этим термином понимается вся система;
zfs - только файловая система в ZFS (которая опирается на ZPL);
ZPL - (ZFS POSIX Layer) подсистема, предоставляющая API для работы файловой системы zfs;
ZVOL - подсистема блочных устройств. В некоторых случаях является синонимом фразы "объект типа DMU_OT_ZVOL";
zvol - блочное устройство, выделяемое из пула в виде датасета.
vdev (VDEV) - узел дерева, из которого состоит пул. Если узел является листовым, то это реальное устройство, если нет - виртуальное (mirror, raidz etc);
SPA - (Storage Pool Allocator) - подсистема, отвечающая за управление занятым/свободным местом в пуле. Оперирует указателями на блоки (blkptr);
blkptr (BP,БП) - указатель на область/области диска. Может быть пустым (HOLE) и заполненным (FILL);
ZIO - (ZFS I/O) - подсистема ввода/вывода. Управляет процессом записи/чтения данных с дисков;
ARC - (Adaptive Replacement Cache) - подсистема, отвечающая за кэширование данных. Через неё проходят все запросы к ZIO;
arc, l2arc - кэш на чтение первого (память) и второго уровня (диски).
DMU - (Data Managment Unit) - подсистема, оперирующая объектами ZFS, которые в свою очередь опираются на blkptr'ы. Все объекты описываются при помощи dnode-стуртуры.
dnode (DN) - (data node) - структура, описывающая некий объект ZFS.
*_phys
- суффикс, который соответствует OnDisk-формату. Например, dnode_phys
, objset_phys
;
OnDisk-формат - формат хранения данных непосредственно на физических носителях);
DSL - (Datasets and Snapshots Layer) - подсистема, оперирующая датасетами и снапшотами. Опирается на DMU.
ZIL - (ZFS Intent Log) - подсистема, необходимая для обработки синхронных запросов на запись. Может быть представлена в виде устройства SLOG (LogZilla, кэш на запись).
ZAP - (ZFS Attibute Processor) - подсистема, необходимая для хранения свойств (аттрибутов) различных объектов ZFS, например, свойства файлов или датасетов.
TXG - (Transaction Group) - группа транзакций. Используется в DMU.
DBUF - (DMU Buffer) - подсистема, отвечающая за транзакционную запись и COW-механизм.
Uberblock - блок данных на диске, характеризующий состояние системы на определённый момент времени (точнее - для определённой транзакционной группы).
ZFS Label - метка на диске/разделе, по которой определяется принадлежность к ZFS.
Metaslab, Metaslab Group, Metaslab Class
- объекты, отвечающие за выделение места на в пуле. Хранят информацию о свободном/занятом месте. Имеют OnDisk-формат. Относятся к SPA.
Indirect blocks - косвенные блоки;
DVA - (Device Virtual Address) - 128-битный адрес, определяющий область на vdev'е, расположенную по заданному смещению и имеющую заданный размер;
Gang block - набор блоков небольшого размера, которые предствляются как единый блок. Используются в случае сильной фрагментации;
BIO (Block IO) - подсистема блочного ввода/выовда ядра Linux;
NVPair (NVList) - списки пар имя-значение;
guid - GUID;
shift - логарифм по основанию 2 для некоторого значения. Например, утверждение "block shift = 9" эквивалентно утверждению "block size = 512b".
P2PHASE, P2ROUNDUP, P2ALIGN - битовые операции (округление вниз, округление вверх, выравнивание по степеням двойки);
MOS (Meta Object Set) - объект содержащий информацию о пуле. TODO: уточнить.
MetaNode
- метаобъект в датасете или пуле. Содержит в себе дерево, листья которого указывают на объекты датасета/пула. Всегда имеет номер 0;
Часть из предствленных термиров изображена на рис. 1.
В данном разделе описаны особенности меток ZFS, и свзязанных с ними процессов.
Метки ZFS присутствуют на физическом носителе, если на него производится запись данных (обычные диски в raidz, mirros и slog). Для повышения надёжности на диске присутствуют 4 метки - две в начале диска и две в конце (по номерам, соответственно, 1, 2 и 3, 4). Кроме того, при обновлении данных в метках вначале обновляются первая и третья, а потом - вторая и четвёртая. Это также повышает отказоустойчивость: если произошла ошибка при записи одной из пар, то может использоваться другая пара.
Рассмотрим структуры меток (рис. 2). В каждой метке присутствуют NVList с описанием конфигурации пула и массив уберблоков. Перед первой парой меток дополнительно отступается свободное пространство (8k) под EFI-метки и место под Boot Block Header
(8k). В текущей реализации Boot Block Header
просто заполняется нулями с контрольной суммой от этих нулей при создании меток.
Тип vdev | Описание | Тип узла | Тип метки |
---|---|---|---|
disk | физическое устройство, на котором хранятся данные | leaf | self |
file | файл, на котором хранятся данные | leaf | self |
log | кэш на запись | leaf/internal | property |
cache | кэш на чтение | aux | aux |
mirror | RAID-массив | internal | self/children |
raidz | RAID-массив | internal | self/children |
missing | устройство, не найденное во время импорта | leaf | None |
root | корневой vdev пула | root | all |
spare | устройство для горячей замены | aux | aux |
В табл. 1 представлено описание виртуальных устройств, из которых может состоять пул. Если исходить из практических соображений (возможностей команды zpool
), то дерево vdev'ов в предельном случае может состоять из:
корень root
(root vdev);
промежуточные узлы internal
(mirror, raidz);
листовые узлы leaf
(disk, file);
aux
-устройства (spare, cache);
Причём, промежуточные узлы могут располагаться на нескольких уровнях. К примеру, программа ztest
при проведении тестирования может создавать "RAID610"
- страйп из зеркал, каждое из которых состоит из нескольких raidz1
. Однако, на практике, не существует способа создания таких "ветвистых" пулов. Поэтому примем следующую терминологию (и в дальнейшем будем ей пользоваться):
leaf - листовые узлы (disk, file);
top-level vdev - промежуточные узлы (mirror, raidz);
root - корневой vdev пула;
aux - устройства, не хранящие данные (cache и spare);
log - листовой или промежуточный узел, у которого выставлен флаг is_log
;
Рассмотрим рис. 3. В нём представлен RAID10 пул с RAID1-кэшем на запись. Выделенная штриховкой область - vdev'ы, состояние которых будет отражено в метках устройств sdq1
и sdq2
. Желтым выделен vdev, у которого установлен флаг is_log
. Если бы лог-устройство было бы не зеркалом, а обычными дисками или страйпами, то соответсвующий флаг был бы установлен у них.
Таким образом, в метках, хранящихся на дисках/файлах отражено состояние данного "листового" vdev'a, его "братьев" и родителя. А также индекс родителя в массиве root.vdev_tree
(где root - корневой vdev пула).
Пусть мы создаём следующий пул (см. рис 3):
zpool create -f tank \
mirror /dev/sdq1 /dev/sdq2 \
mirror /dev/sdq3 /dev/sdq4 \
log mirror /dev/sdq5 /dev/sdq6 \
cache /dev/sdq7 /dev/sdq8 \
spare /dev/sdq9
zpool status
# pool: tank
# state: ONLINE
# scan: none requested
#config:
#
# NAME STATE READ WRITE CKSUM
# tank ONLINE 0 0 0
# mirror-0 ONLINE 0 0 0
# sdq1 ONLINE 0 0 0
# sdq2 ONLINE 0 0 0
# mirror-1 ONLINE 0 0 0
# sdq3 ONLINE 0 0 0
# sdq4 ONLINE 0 0 0
# logs
# mirror-2 ONLINE 0 0 0
# sdq5 ONLINE 0 0 0
# sdq6 ONLINE 0 0 0
# cache
# sdq7 ONLINE 0 0 0
# sdq8 ONLINE 0 0 0
# spares
# sdq9 AVAIL
#
#errors: No known data errors
Информация о структуре пула будет отражена только в метках дисков sdq[1-6]
. О дисках sdq[7-9]
на этих метках не будет никакой информации, т.к. эти диски относятся к типу aux
vdev
. aux
-устройства хранят в своих метках только 3 составляющие: версия пула, тип устройства и guid устройства. Каким же образом ZFS узнаёт, где их искать при импорте? Ответ на этот вопрос - в метаданных пула. Именно там и хранятся пути к этим устройствам. Таким образом, при импорте, на этапе анализа меток aux-устройства не участвуют в "сборе" дерева пула. Это происходит уже после получения uberblock
'a и чтения метаданных (MOS). В дополнение к этому, ZFS спокойно относится к созданию пулов в которых присутствуют устройства с aux
-метками (нет необходимости в ключе -f
для команды zpool
). Более того, при импорте на этих устройствах затираются данные - ZFS пишет туда свои метки и ей безразлично, хранится ли там информация.
В NVList хранится информация о пуле и части соседних с текущим vdev'ов. Как было раннее указано, хранится информация только о важных (с точки зрения целостности данных) vdev'ах - диски с данными и slog. Другие виды vdev'ов (l2cache, spare) хранятся вне меток (непосредственно внутри пула). "Соседние" vdev'ы - те устройства (виртуальные и реальные) с которыми граничит данное устройство в дереве vdev'ов пула. Именно о них и хранится информация в метке диска/файла. Одна из составляющих метки - список конфигурационных параметров пула. NVList конфигурации пула включает в себя следующие элементы:
версия OnDisk-формата;
имя пула;
состояние пула (активный, экспортированный, уничтоженный);
номер транзакционной группы, в которой была записана метка;
guid пула;
guid vdev'а верхнего уровня;
дерево соседних vdev'ов;
Дерево vdev'ов содержит элементы - массив, состоящий из элементов:
type: имя типа (диск, файл, зеркало и т.п.);
id: уникальный индекс vdev'а в родительском vdev'е;
guid vdev'a;
path: путь к устройству (только для "листовых" vdev'ов - диски, файлы)
metaslab_array: номер объекта (внутри пула), который содержит массив номеров объектов, каждый из которых соответствует объекту, описывающему metaslab;
metaslab_shift: log2 от размера метаслаба;
ashift (allocatable shift): log2 от размера минимальной области, которая может быть выделена в top-level vdev'е. Например, если ashift=9, то в данном vdev'е области будут выделяться не меньшие, чем 512 байт;
asize (allocatable size): количество байт, которое можно выделить в данном vdev'е. Грубо говоря, общий объем всех дочерних vdev'ов;
children: список дочерних vdev'ов;
Следующий элемент метки - уберблоки (Uberblocks). В текущей реализации используется массив из 128 уберблоков. Размер уберблока - 1k. ZFS работает с уберблоками как с кольцевым буффером: при обновлении перезаписывается самый старый уберблок. Таким образом, теоретически, мы в любой момент времени имеем до 128 readonly состояний файловой системы (на самом деле меньше - см. раздел метаслабы). При поиске уберблока (во время операции импорта) выбирается уберблок, соответствующий следующим критериям:
верная контрольная сумма;
наибольшее значение временной метки;
наибольшее значение номера транзакционной группы;
В случае, если существуют проблемы при импорте пула, ZFS позволяет производить операцию отмотки (REWIND), которая соответствует опциям -F
и -X
команды zpool import
. Отмотка позволяет "откатиться" в одно из предыдущих состояний файловой системы посредством выбора более "молодого" уберблока (своего рода перемещение во времени в рамках последних 128 транзакций). Кроме того, существует опция -T
, которая позволяет задать конкретный номер транзакции, к которой нужно откатиться.
Уберблок состоит из следующих элементов:
ub_magic: уникальное "магическое" число (magic number), которое позволяет не только определить, что область на диске содержит уберблок, но определить порядок байт, определённый в пуле (ZFS пулы переносимы между системами с различными endianness
):
ub_version: версия OnDisk-формата (см. версию в NVList)
ub_txg: номер транзакционной группы, в которой был записан данный уберблок. Должен быть не меньше, чем параметр txg
в метке;
ub_guid_sum: сумма guid всех листовых vdev'ов. Используется для проверки доступности всех vdev'ов пула. Если это поле не совпадает с вычисленной при импорте суммой, значит было потеряно устройство;
ub_timestamp: временная метка на момент записи уберблока;
ub_rootbp: blktr, указывающий на расположение MOS (Meta Object Set) данного пула;
Для просмотра содержимого меток пула можно воспользоваться командой zdb <pool-name>
. Или zdb <pool-name> -e -p <dir>
если пул был экспортирован и файлы/диски, из которых состоит пул находятся в директории <dir>
.
Данные передаются между памятью и диском в единицах, которые называются блоками. Указатель на блок (структура blkptr_t) - это 128-битная структура, используемая для описания физического расположения блока, его верификации. Она описывает блоки данных на диске. Структура BP представлена на рис. 4.
На рис. 5 представлена структура DVA (Device Virtual Address). Она состоит из следующих элементов:
asize (allocated size): размер данных (в единицах ashift vdev'а верхнего уровня), на которые указывает данный BP. Стоит отметить, что данный параметр имеет наибольший размер (в битах) по отношению к LSIZE и PSIZE (см. поля BP), т.к. дополнительно может понадобиться место для хранения данных GANG-блоков, RAIDZ и 3-х DVA на один BP.
grid: зарезервировано для raidz;
vdev: уникальный номер vdev'а в пуле;
offset: смещение в единицах ashift, которое характеризует расположение блока в пуле (physaddr = (offset ≪ ashift)+Labelsize, где Labelsize = 4MB = 0.5МБ отступ первых 2-х меток + 3.5МБ свободного места)
G (Gang): флаг, характиризующий, является ли блок Gang-блоком (0 - нет, 1 - да).
Как было отмечено выше, в BP может содержаться до 3-х DVA. Это значит, что один BP может ссылаться на данные в 3-х различных местах пула. Это сделано для повышения надёжности хранения данных, в особенности - метаданных. Ведь потеря метаданных (например, косвенного блока или dnode'ы) может оказать существенное влияние на целостность данных в пуле. К примеру, если мы "потеряем" данные, на которые указывает ub_rootbp
уберблока, то мы потеряем все данные. Чтобы не допустить это, используется избыточность для метаданных. Количество избыточных DVA зависит от вида BP (данные/метаданные) и типа метаданных (чем выше по дереву - тем большая избыточность). На рис. 6-7 изображены примеры количества и расположения блоков на различных видах пулов.