ZFS基础知识:存储和性能



今年春天,我们已经讨论了一些入门主题,例如如何检查磁盘速度以及RAID是什么在它们的第二个中,我们甚至承诺继续研究ZFS中各种多磁盘拓扑的性能。这是下一代文件系统,从AppleUbuntu,到处都有实现

好吧,今天是了解ZFS的最佳日子,好奇的读者。请注意,根据OpenZFS开发人员Matt Arens的保守评估,“它确实很复杂”。

但是,在得出数字之前,我保证,对于所有变种vosmidiskovoy ZFS配置,您都需要讨论如何执行ZFS在磁盘上存储数据。

Zpool,vdev和设备



这个完整的池图包括三个辅助vdev,每个类一个,四个RAIDz2,


通常没有理由创建不适当的vdev 类型和大小的池-但是,如果您愿意,没有什么可以阻止您这样做的。

要真正了解ZFS文件系统,您需要仔细查看其实际结构。首先,ZFS将传统级别的卷管理和文件系统结合在一起。其次,它在写入时使用事务复制机制。这些功能意味着该系统在结构上与普通文件系统和RAID阵列有很大不同。需要了解的第一组基本构建块:存储池(zpool),虚拟设备(vdev)和实际设备(设备)。

zpool


zpool存储池是最顶层的ZFS结构。每个池包含一个或多个虚拟设备。反过来,它们每个都包含一个或多个实际设备(设备)。虚拟池是自治块。一台物理计算机可能包含两个或多个单独的池,但是每个池完全独立于其他池。池不能共享虚拟设备。

ZFS的冗余是在虚拟设备级别,而不是在池级别。在池级别上,绝对没有冗余-如果丢失了任何vdev驱动器或特殊的vdev,则整个池也会随之丢失。

现代存储池可以在丢失高速缓存或虚拟设备日志的情况下幸免-尽管如果在断电或系统崩溃期间丢失vdev日志,它们可能会丢失少量脏数据。

常见的误解是ZFS的“数据带”(条带)记录在整个池中。这不是真的。 Zpool根本不是一个有趣的RAID0,它是一个有趣的JBOD,具有复杂的可变分发机制。

在大多数情况下,条目是根据可用空间在可用虚拟设备之间分配的,因此从理论上讲,它们将同时填充。在更高版本的ZFS中,考虑了vdev的当前使用(利用率)-如果一个虚拟设备的负载比另一个虚拟设备的负载大得多(例如,由于读取负载),尽管存在最高可用空间系数,但仍会暂时跳过该虚拟设备的写入操作。

内置于现代ZFS记录分发方法中的回收检测机制可以减少异常高负载期间的等待时间并提高吞吐量-但这不是万能的在一个池中非自愿地混合慢速硬盘和快速固态硬盘。这样的不平等池仍将以最慢的设备的速度工作,也就是说,好像它完全由此类设备组成。

vdev


每个存储池均包含一个或多个虚拟设备(虚拟设备,vdev)。每个vdev依次包含一个或多个实际设备。大多数虚拟设备用于轻松存储数据,但是有多个辅助vdev类,包括CACHE,LOG和SPECIAL。这些vdev类型中的每一种都可以具有五种拓扑之一:单设备,RAIDz1,RAIDz2,RAIDz3或镜像。

RAIDz1,RAIDz2和RAIDz3是旧时所说的RAID双(对角)奇偶校验的特殊变体。 1、2和3表示为每个数据段分配了多少个奇偶校验块。虚拟RAIDz设备将奇偶校验均匀分布在各个磁盘上,而不是使用单独的磁盘进行奇偶校验。 RAIDz阵列可能丢失具有奇偶校验块的磁盘;如果他输了另一个,他将失败并带走存储池。

在镜像的虚拟设备(镜像vdev)中,每个块都存储在vdev中的每个设备上。尽管最常见的两幅宽镜面,但镜中可以有任意数量的设备-在大型安装中,通常使用三层镜来提高读取性能和容错能力。当vdev中的至少一台设备继续工作时,vdev镜像可以承受任何故障。

单个vdev本质上是危险的。这样的虚拟设备将无法幸免单个故障-如果将其用作存储设备或特殊的vdev,则其故障将导致整个池的破坏。在这里要非常非常小心。

可以使用上述任何一种拓扑来创建CACHE,LOG和SPECIAL虚拟设备-但请记住,丢失SPECIAL虚拟设备意味着丢失池,因此强烈建议使用过多的拓扑。

设备


在ZFS中,这可能是最容易理解的术语-实际上,它是一个块随机访问设备。请记住,虚拟设备由单个设备组成,而池则由虚拟设备组成。

磁盘-磁性或固态磁盘-是用作vdev构建块的最常见的块设备。但是,任何在/ dev中带有句柄的设备都适用-因此,您可以将整个硬件RAID阵列用作单独的设备。

一个简单的原始文件是可以构建vdev的最重要的替代块设备之一。稀疏文件中的测试池 -一种非常方便的方式来检查池命令,并查看此拓扑的池或虚拟设备中有多少可用空间。


您可以在短短几秒钟内从稀疏文件创建测试池-但不要忘记稍后删除整个池及其组件。

假设您要将服务器放在八个磁盘上,并计划使用10 TB磁盘(〜9300 GiB)-但是您不确定拓扑最适合您的需求。在上面的示例中,在短短的几秒钟内,我们从稀疏文件中构建了一个测试池-现在我们知道,来自八个10 TB驱动器的RAIDz2 vdev提供了50 TiB的可用容量。

另一类特殊的设备是SPARE(备用)。与常规设备不同,热交换设备属于整个池,而不仅仅是一个虚拟设备。如果池中的某些vdev发生故障,并且备用设备已连接到池且可用,则它将自动加入受影响的vdev。

连接到受影响的vdev后,备用设备将开始接收丢失设备上应有的数据副本或重建数据。在传统RAID中,这称为重建,而在ZFS中,其称为“重新同步”。

重要的是要注意,替换设备不会永久替换故障设备。这只是临时替换,以减少观察到vdev降级的时间。管理员更换发生故障的vdev设备后,将冗余恢复到该永久设备,并且SPARE与vdev断开连接,并作为整个池的备用设备恢复工作。

数据集,块和扇区


在我们通过ZFS的过程中,您需要了解的下一组构建块不是太多的硬件,而是数据的组织和存储方式。我们在这里跳过了几个层次(例如metaslab),以免在保持对整体结构的理解的同时不堆积细节。

数据集



当我们第一次创建数据集时,它将显示所有可用的池空间。然后我们设置配额-并更改安装点。魔法!


Zvol在大多数情况下只是一个数据集,没有文件系统层,在这里我们将其替换为完全普通的ext4

文件系统,ZFS数据集与标准挂载文件系统大致相同。乍看之下,就像常规文件系统一样,它似乎只是“另一个文件夹”。而且,与常规挂载文件系统一样,每个ZFS数据集都有其自己的基本属性集。

首先,数据集可以具有分配的配额。如果已安装zfs set quota=100G poolname/datasetname,则您写入的文件夹不能/poolname/datasetname超过100 GiB。

注意每行开头是否存在斜杠?每个数据集在ZFS层次结构和系统安装层次结构中都有其自己的位置。 ZFS层次结构中没有斜杠-您从池的名称开始,然后是从一个数据集到下一个数据集的路径。例如,pool/parent/child对于在广告素材池中child父数据命名的数据集parent,其名称为Creative pool

默认情况下,数据集的安装点将等效于其名称在ZFS层次结构中,并在其开始处带有斜杠-名称为的池pool安装为/pool,数据集parent安装在/pool/parent,子数据集安装child/pool/parent/child。但是,可以更改数据集的系统安装点。

如果我们指出zfs set mountpoint=/lol pool/parent/child,然后将数据集作为pool/parent/child装入系统中/lol

除了数据集,我们还应该提到体积(zvols)。卷实际上与数据集大致相似,不同之处在于它实际上没有文件系统-它只是一个块设备。例如,您可以zvol使用name 创建mypool/myzvol,然后使用ext4文件系统对其进行格式化,然后挂载此文件系统-现在您有了ext4文件系统,但支持所有ZFS安全功能!这在一台计算机上看似很愚蠢,但在导出iSCSI设备时作为后端更有意义。

积木



一个文件由一个或多个块表示。每个块都存储在一个虚拟设备上。块大小通常等于recordsize参数,但如果它包含元数据或小文件,则可以减小到2 ^ ashift


我们真的,真的是不开玩笑了巨大的性能损害,如果你安装过小ashift

在ZFS池,所有的数据,包括元数据,存储在块。每个数据集的最大块大小在属性recordsize(记录大小)中定义。记录的大小可能会有所不同,但这不会更改已写入数据集的任何块的大小或位置-仅在写入新块时才有效。

除非另有说明,否则默认情况下,当前记录大小为128 KiB。这是一种困难的折衷,在这种折衷中,性能并不理想,但在大多数情况下并不可怕。Recordsize可以将其设置为4K到1M之间的任何值(通过其他设置,recordsize您可以设置更多值,但这不是一个好主意)。

任何块都仅指一个文件的数据-您不能将两个不同的文件压缩到一个块中。每个文件由一个或多个块组成,具体取决于大小。如果文件大小小于记录大小,则将文件保存在较小的块中-例如,具有2 KiB文件的块将仅占用磁盘上的一个4 KiB扇区。

如果文件足够大并且需要几个块,则该文件的所有记录都将具有大小recordsize -包括最后一条记录,该记录的主要部分可能是未使用的空间

Zvol卷没有属性recordsize -而是具有等效的属性volblocksize

部门


最后,最基本的组成部分是该部门。这是可以写入或读取基本单元的最小物理单元。几十年来,大多数磁盘使用512字节扇区。最近,大多数驱动器配置用于4 KiB扇区,在某些驱动器中,尤其是SSD,配置了8 KiB甚至更多扇区。

ZFS具有允许您手动设置扇区大小的属性。这是一个属性ashift。转移是二的幂,这有点令人困惑。例如,这ashift=9意味着扇区大小为2 ^ 9,即512字节。

当将每个块设备添加到新的vdev中时,ZFS会要求操作系统提供有关每个块设备的详细信息,并且理论上会根据该信息自动正确地设置移位。不幸的是,许多驱动器都依赖于其扇区大小,以保持与Windows XP的兼容性(Windows XP无法理解其他扇区大小的驱动器)。

这意味着强烈建议ZFS管理员知道其设备的实际扇区大小并手动安装ashift。如果设置的移位太小,则读/写操作的数量将急剧增加。因此,将512字节的“扇区”写入实际的4 KiB扇区意味着写入第一个“扇区”,然后读取4 KiB扇区,将其更改为第二个512字节的“扇区”,然后将其写回到新的4 KiB扇区,依此类推。对于每个条目。

在现实世界中,这种惩罚超过了三星EVO ashift=13SSD ,它必须为此采取行动,但是这些SSD取决于其扇区大小,因此默认情况下将其设置ashift=9。如果经验丰富的系统管理员没有更改此设置,则此SSD的速度将比常规磁性HDD

为了比较,尺寸太大ashift几乎没有罚款。生产力并没有真正的降低,并且未使用空间的增加是无限小的(在启用压缩的情况下等于零)。因此,我们强烈建议甚至安装那些真正使用512字节扇区的驱动器,ashift=12或者甚至ashift=13对未来充满信心。许多人错误地认为,

该属性是ashift为每个vdev虚拟设备而不是为池设置的,并且在安装后不会更改。如果您ashift在向池中添加新的vdev时不小心将其关闭,则您将使用性能低下的设备对该池进行了不可挽回的污染,通常,除了销毁池并重新开始之外,别无其他方法。即使删除vdev也不会使您摆脱损坏的设置ashift



 — ,


,


, , « » « »,


, — , ,

写时复制(CoW)是使ZFS如此出色的基本基础。基本概念很简单-如果您要求传统文件系统修改文件,它将按照您的要求进行操作。如果您要求在录制过程中进行复制的文件系统也这样做,它将说“好”-但这对您来说是骗人的。

相反,复制-写入文件系统将写入修改后的块的新版本,然后更新文件元数据以断开与旧块的链接,并将您刚刚写入其中的新块关联起来。

断开旧单元的连接并连接新单元是一次操作,因此它不会被打断-如果您在这种情况发生后重新设置电源,则文件具有新版本,而如果您较早地重新设置电源,则您具有旧版本。无论如何,文件系统都不会发生冲突。

写入ZFS时的复制不仅发生在文件系统级别,而且发生在磁盘管理级别。这意味着ZFS不受记录空间的限制(RAID中)-这种现象是当条带仅在系统崩溃之前设法部分记录时,重新启动后阵列会损坏。在这里,剥离是原子的,vdev始终是一致的,而Bob是您的叔叔

ZIL:ZFS意图日志



ZFS  — , ZIL,


, ZIL, .


SLOG, LOG-, —  — , ,  — vdev, ZIL


ZIL  — ZIL SLOG,

写入操作主要分为两类-同步(同步)和异步(异步)。对于大多数工作负载,绝大多数写操作都是异步的-文件系统使您可以聚合它们并分批交付它们,从而减少碎片并显着提高吞吐量。

同步录音是完全不同的事情。当应用程序请求同步写入,它告诉文件系统:“你需要提交这非易失性存储器,现在,在那之前我可以更什么都不做。”因此,同步记录应立即提交到磁盘-如果增加碎片或减少带宽,那就也是如此。

ZFS对同步记录的处理方式不同于常规文件系统-ZFS不会立即将它们上传到常规存储中,而是将它们记录在称为ZFS意向日志(ZFS意向日志或ZIL)的特殊存储区域中。诀窍在于,这些记录保留在内存中,并与常规的异步写入请求一起聚合,然后作为完全正常的TXG(事务组,事务组)转储到存储中。

在正常操作中,将记录ZIL,并且不再读取。片刻之后,如果将来自ZIL的记录固定在普通TXG中的RAM的主存储器中,则它们与ZIL断开连接。从ZIL读取内容时,唯一的事情就是导入池。

如果ZFS崩溃-操作系统崩溃或断电-当ZIL中有数据时,该数据将在下一次导入池时读取(例如,紧急系统重启时)。 ZIL中的所有内容都将被读取,合并为TXG组,提交到主存储,然后在导入过程中与ZIL断开连接。

vdev帮助程序类之一称为LOG或SLOG,即辅助LOG设备。他的任务是-为池提供一个单独的(最好更快),具有很高的写电阻的vdev设备,用于存储ZIL,而不是将ZIL存储在主vdev存储中。 ZIL本身的行为与存储位置无关,但是如果带LOG的vdev具有很高的写入性能,则同步写入会更快。

将具有LOG的vdev添加到池中不能提高异步写入性能-即使您使用强制对ZIL进行所有写入zfs set sync=always,它们仍将以与没有日志时相同的方式和速度与TXG中的主存储库绑定。唯一直接的性能改进是同步记录中的延迟(因为更高的日志速度可加快操作速度sync)。

但是,在已经需要大量同步写入的环境中,vdev LOG可以间接加速异步写入和未缓存的读取。将ZIL记录上传到单独的vdev LOG意味着在主存储中对IOPS的竞争较少,这在一定程度上提高了所有读取和写入操作的性能。

快照


写复制机制还是原子ZFS快照和增量异步复制的必要基础。活动文件系统具有一个指针树,该指针树用当前数据标记所有记录-拍摄快照时,您只需复制该指针树即可。

当活动文件系统中的记录被覆盖时,ZFS首先将块的新版本写入未使用的空间。然后,它将旧版本的块与当前文件系统分离。但是,如果某些快照引用了旧块,则它仍然保持不变。直到所有链接到该块的快照都被销毁为止,旧块才真正恢复为可用空间!

复写



Steam 2015 158  126 927 . rsync — ZFS « » 750% .


40- Windows 7 — . ZFS 289 , rsync — «» 161 , , rsync --inplace.


, rsync . 1,9  — , ZFS 1148 , rsync, rsync --inplace

一旦了解了快照的工作原理,就很容易掌握复制的本质。由于快照只是指向记录的指针的树,因此,如果我们创建zfs send快照,则将发送此树以及与其关联的所有记录。当我们通过此zfs sendzfs receive到目标对象,它写入的块的两端的实际内容和引用该块到目标数据指针集合树。

第二件事变得更加有趣zfs send。现在我们有两个系统,每个系统都包含poolname/datasetname@1,您可以拍摄一个新的快照poolname/datasetname@2。因此,在源池中有datasetname@1and datasetname@2,在目标池中到目前为止只有第一个快照datasetname@1

由于我们在源和目标之间有一个共同的快照datasetname@1,我们可以在此基础上进行增量 zfs send。当我们告诉系统时zfs send -i poolname/datasetname@1 poolname/datasetname@2,它会比较两个指针树。@2显然,仅存在于中的任何指针都引用新块-因此我们需要这些块的内容。

在远程系统上,增量处理send同样简单。首先,我们记录流中包含的所有新条目send,然后将指针添加到这些块。瞧,在我们的@2新系统中!

ZFS异步增量复制是对rsync之类的早期非快照方法的巨大改进。在这两种情况下,仅传输更改的数据-但必须先读取 rsync从磁盘两侧的所有数据中检查数据量并进行比较。相比之下,ZFS复制只读取指针树-以及常规快照中未表示的任何块。

内联压缩


写入时复制机制还简化了内置压缩系统。在传统的文件系统中,压缩存在问题-更改后的数据的旧版本和新版本都位于同一空间中。

如果您考虑一个文件中间的一条数据,该数据以0x00000000的兆字节零开始存储,依此类推-将其压缩到磁盘上的一个扇区非常容易。但是,如果用兆字节不可压缩的数据(例如JPEG或伪随机噪声)替换此兆字节的零,会发生什么情况呢?突然,这一兆字节的数据将不需要一个,而是需要256个4 KiB的扇区,并且在磁盘上的此位置仅保留了一个扇区。

ZFS不会出现这样的问题,因为更改后的记录始终会写入未使用的空间-原始块仅占用一个4 KiB扇区,而一条新记录将占用256,但这不是问题-最近更改的文件“中间”部分将被写入未使用的空间不管其大小是否已更改,因此对于ZFS来说都是正常情况。

默认情况下,内置ZFS压缩是禁用的,并且系统提供了插件算法-现在包括LZ4,gzip(1-9),LZJB和ZLE。

  • LZ4是一种流算法,即使在相当慢的CPU上,也能在大多数情况下提供极快的压缩和解压缩性能,并获得性能提升。
  • GZIP — , Unix-. 1-9, CPU 9. ( ) ,   c CPU — , .
  • LZJB — ZFS. , LZ4 .
  • ZLE-零级编码,零级编码。它根本不涉及正常数据,但是压缩大的零序列。对于完全不可压缩的数据集(例如JPEG,MP4或其他已经压缩的格式)很有用,因为它会忽略不可压缩的数据,但会压缩结果记录中未使用的空间。

我们建议几乎所有用例都使用LZ4压缩。遇到不可压缩数据的性能损失非常小,并且典型数据性能增益显着。此2015年测试中为新安装的Windows操作系统(新安装的OS,尚无数据)复制虚拟机映像的compression=lz4速度比此方法快27%compression=none

ARC-自适应替换缓存


ZFS是我们已知的唯一现代文件系统,它使用自己的读取缓存机制,并且不依赖操作系统页面缓存将最近读取的块的副本存储在RAM中。

尽管其自身的缓存并非没有问题-ZFS不能像内核一样快地响应新的内存分配请求,所以如果新的malloc()内存分配调用需要ARC当前占用的RAM,则它可能会失败。但是至少在目前,有充分的理由使用自己的缓存。

所有著名的现代操作系统,包括MacOS,Windows,Linux和BSD,都使用LRU算法(最近最少使用)来实现页面缓存。这是一种原始算法,它在每次读取后将高速缓存的块“上移”,并根据需要将“下行队列”的块推入以增加新的高速缓存未命中(应该从磁盘读取而不是从高速缓存中读取的块)。

通常,该算法可以正常工作,但是在具有大量工作数据集的系统上,LRU容易导致抖动-挤出经常需要的块以腾出空间,以容纳不再从缓存中再次读取的块。

 -天真得多的算法,可以将其视为“加权”缓存。每次读取缓存的块后,它都会变得“重”一点,并且变得更难挤出-甚至在挤出一定时间后仍会跟踪该块。已被挤出但随后需要读回高速缓存的块也将变得“更重”。

所有这一切的最终结果是命中率高得多的缓存-命中率(从缓存中读取)与未命中(从磁盘中读取)之间的比率。这是非常重要的统计信息-不仅缓存命中本身的速度要快几个数量级,而且缓存命中的速度也可以更快,因为缓存命中次数越多,并发磁盘请求越少,应该处理的其余未命中的延迟就越少驾驶。

结论


在研究了ZFS的基本语义(写时如何复制以及存储池,虚拟设备,块,扇区和文件之间的关系)之后,我们准备讨论具有实数的实际性能。

在下一部分中,我们将比较带镜像的vdev和RAIDz的池的实际性能,以及彼此之间的比较,以及与我们之前检查过的传统Linux内核RAID拓扑的比较

最初,我们只想考虑基础知识-ZFS拓扑本身-但在之后,我们将准备讨论更高级的ZFS调优和调优,包括使用辅助vdev类型,例如L2ARC,SLOG和特殊分配。

All Articles