Vdev, или Virtual Device — это базовая единица, на которой строится массив данных ZFS (zpool). Для работы ZFS необходим как минимум один vdev — виртуальное устройство, которое позволяет случайный доступ к информации на уровне блоков.
Обычно, в качестве таких блоков используются целые диски или iScsi/FC LUNы (raw-disk vdev), но можно также использовать разделы дисков или файлы. Целые диски предпочтительнее, так как для них ZFS использует onboard write cache, в результате чего производительность записи может значительно улучшиться. Я пока не буду объяснять разницу между logical vdev и leaf vdev — это топик для отдельной статьи про то как работает RaidzN и zfs mirror.
src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/fs/zfs/vdev_disk.c#314
if (vd->vdev_wholedisk == 1) { int wce = 1; /* * If we own the whole disk, try to enable disk write caching. * We ignore errors because it's OK if we can't do it. */ (void) ldi_ioctl(dvd->vd_lh, DKIOCSETWCE, (intptr_t)&wce, FKIOCTL, kcred, NULL); }
В описании структуры виртуального устройства можно посмотреть какие параметры используются для чего, но я не собираюсь давать объяснение каждому — цель статьи — описание собственно того как ZFS хранит и пишет данные.
Каждый vdev разделен на логические разделы (metaslab), которые создаются автоматически при добавлении диска в ZFS Pool. Количество таких разделов зависит от размера vdev — примерно 200 разделов, но реально может быть и <128, и >256.
src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/fs/zfs/vdev.c#1552
void vdev_metaslab_set_size(vdev_t *vd) { /* * Aim for roughly 200 metaslabs per vdev. */ vd->vdev_ms_shift = highbit(vd->vdev_asize / 200); vd->vdev_ms_shift = MAX(vd->vdev_ms_shift, SPA_MAXBLOCKSHIFT); }
Далее, внутри каждого metaslab'a есть так называемая карта места — space map. Она, как и большинство структур в ZFS, сделана на базе AVL-дерева, и позволяет эффективно работать с блоками занятого и свободного места независимо от размера блока и его места внутри metaslab'a.
Вот небольшой пример того, как ZFS видит эту структуру, используя zdb -mmm <poolname>
— тут показаны 20 сегментов первого metaslab'a первого vdev'a в моем массиве:
Metaslabs:
vdev 0
metaslabs 323 offset spacemap free
--------------- ------------------- --------------- -------------
metaslab 0 offset 0 spacemap 34 free 84.8M
segments 72 maxsize 24.9M freepct 16%
....
[ 73] A range: 001f00ba00-001ffffa00 size: ff4000
[ 74] A range: 001eff8200-001f00ae00 size: 012c00
[ 75] A range: 001b091200-001b130200 size: 09f000
[ 76] A range: 0012a84e00-0013a84e00 size: 1000000
[ 77] A range: 0013a84e00-00141bb800 size: 736a00
[ 78] A range: 0006b88000-0007707200 size: b7f200
[ 79] FREE: txg 9153385, pass 1
[ 80] F range: 0000282000-0000283200 size: 001200
[ 81] F range: 0000b47200-0000b48600 size: 001400
[ 82] ALLOC: txg 9252698, pass 1
[ 83] A range: 0000282000-0000283a00 size: 001a00
[ 84] A range: 0000b47200-0000b48e00 size: 001c00
[ 85] A range: 00008e6e00-00008e8a00 size: 001c00
[ 86] FREE: txg 9279297, pass 2
[ 87] F range: 00008e9000-00008e9c00 size: 000c00
[ 88] FREE: txg 9328122, pass 2
[ 89] F range: 0010a7be00-0010a7c800 size: 000a00
[ 90] FREE: txg 9444756, pass 1
[ 91] F range: 0009b14200-000ab14200 size: 1000000
[ 92] F range: 000ab14200-000b402000 size: 8ede00
....
Здесь заметно, что карта не сортирует сегменты по адресам блоков, размерам, и не пытается дефрагментировать свободное место на разделе. К сожалению, в ZFS полностью отсутствует возможность дефрагментации чего-либо вообще — это один из минусов использования бинарных деревьев и Copy-on-write принципа. Дефрагментировать ZFS — очень нетривиальная задача, и пока остается невозможной для большинства случаев.
Минимальный размер блока в space map — 512 байт (0х200), максимальный — 16МБ (0х1000000). Свободное место в space map не учитывается для блоков, которые никогда не были использованы, но учитывается для блоков которые когда-то были записаны, а потом удалены.
При записи в какой-либо конкретный metaslab составляется еще одна «карта» свободного места, free map, из которой берутся подходящие участки полностью свободного места, в которое можно записать данные последовательно, не тратя время на disk seek. В предыдущем примере видно, что всего у меня 84МБ «free» места в данном разделе, но самый большой непрерывный свободный участок — 24.9МБ «maxfree».
Один из последних шагов в ZFS write pipeline — это собственно определить, куда записать новые данные, что (надеюсь) будет топиком следующего поста про metaslab allocator.
Поскольку это первая статья в серии, приветствуются комментарии про состав — нужно ли больше примеров исходного кода для нетривиальных параграфов, как воспринимается сам текст, и т. д.
Автор: gatech