ZFS

В данном документе представлены результаты исследований исходников 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.

Figure 1. Диаграмма исходного кода ZFS
Figure 1. Диаграмма исходного кода ZFS

ZFS Label

В данном разделе описаны особенности меток ZFS, и свзязанных с ними процессов.

Метки ZFS присутствуют на физическом носителе, если на него производится запись данных (обычные диски в raidz, mirros и slog). Для повышения надёжности на диске присутствуют 4 метки - две в начале диска и две в конце (по номерам, соответственно, 1, 2 и 3, 4). Кроме того, при обновлении данных в метках вначале обновляются первая и третья, а потом - вторая и четвёртая. Это также повышает отказоустойчивость: если произошла ошибка при записи одной из пар, то может использоваться другая пара.

Рассмотрим структуры меток (рис. 2). В каждой метке присутствуют NVList с описанием конфигурации пула и массив уберблоков. Перед первой парой меток дополнительно отступается свободное пространство (8k) под EFI-метки и место под Boot Block Header (8k). В текущей реализации Boot Block Header просто заполняется нулями с контрольной суммой от этих нулей при создании меток.

Figure 2. Структура метки ZFS
Figure 2. Структура метки ZFS

Виртуальные устройства (VDEV) и метки на них

Table 1. Типы виртуальных устройств пула
Тип 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. Если бы лог-устройство было бы не зеркалом, а обычными дисками или страйпами, то соответсвующий флаг был бы установлен у них.

Figure 3. Гибридный пул RAID10 + log(RAID1)
Figure 3. Гибридный пул RAID10 + log(RAID1)

Таким образом, в метках, хранящихся на дисках/файлах отражено состояние данного "листового" 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 пишет туда свои метки и ей безразлично, хранится ли там информация.

Конфигурация пула (ZFS Label NVList)

В 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)

Следующий элемент метки - уберблоки (Uberblocks). В текущей реализации используется массив из 128 уберблоков. Размер уберблока - 1k. ZFS работает с уберблоками как с кольцевым буффером: при обновлении перезаписывается самый старый уберблок. Таким образом, теоретически, мы в любой момент времени имеем до 128 readonly состояний файловой системы (на самом деле меньше - см. раздел метаслабы). При поиске уберблока (во время операции импорта) выбирается уберблок, соответствующий следующим критериям:

  • верная контрольная сумма;

  • наибольшее значение временной метки;

  • наибольшее значение номера транзакционной группы;

В случае, если существуют проблемы при импорте пула, ZFS позволяет производить операцию отмотки (REWIND), которая соответствует опциям -F и -X команды zpool import. Отмотка позволяет "откатиться" в одно из предыдущих состояний файловой системы посредством выбора более "молодого" уберблока (своего рода перемещение во времени в рамках последних 128 транзакций). Кроме того, существует опция -T, которая позволяет задать конкретный номер транзакции, к которой нужно откатиться.

Уберблок состоит из следующих элементов:

  • ub_magic: уникальное "магическое" число (magic number), которое позволяет не только определить, что область на диске содержит уберблок, но определить порядок байт, определённый в пуле (ZFS пулы переносимы между системами с различными endianness):

    • Big-Endian: 0x00bab10c (oo-ba-block)
    • Little-Endian: 0xcb1ba00
  • 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>.

Указатели на блоки (Block Pointers)

Данные передаются между памятью и диском в единицах, которые называются блоками. Указатель на блок (структура blkptr_t) - это 128-битная структура, используемая для описания физического расположения блока, его верификации. Она описывает блоки данных на диске. Структура BP представлена на рис. 4.

Figure 4. Структура blkptr_t
Figure 4. Структура blkptr_t

На рис. 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 - да).

Структура DVA (5)
Структура DVA (5)

Как было отмечено выше, в BP может содержаться до 3-х DVA. Это значит, что один BP может ссылаться на данные в 3-х различных местах пула. Это сделано для повышения надёжности хранения данных, в особенности - метаданных. Ведь потеря метаданных (например, косвенного блока или dnode'ы) может оказать существенное влияние на целостность данных в пуле. К примеру, если мы "потеряем" данные, на которые указывает ub_rootbp уберблока, то мы потеряем все данные. Чтобы не допустить это, используется избыточность для метаданных. Количество избыточных DVA зависит от вида BP (данные/метаданные) и типа метаданных (чем выше по дереву - тем большая избыточность). На рис. 6-7 изображены примеры количества и расположения блоков на различных видах пулов.

Избыточность BP(6)
Избыточность BP(6)