ZFS 基础知识:存储和性能

ZFS 基础知识:存储和性能

今年春天我们已经讨论了一些介绍性主题,例如, 如何检查驱动器的速度 и 什么是RAID。 在第二个中,我们甚至承诺继续研究 ZFS 中各种多磁盘拓扑的性能。 这是下一代文件系统,现已在各地实施: AppleUbuntu.

好吧,今天是了解 ZFS 的最佳日子,好奇的读者们。 只要知道 OpenZFS 开发人员 Matt Ahrens 的拙见,“这真的很难”。

但在我们了解数字之前(我保证它们会的),对于八磁盘 ZFS 配置的所有选项,我们需要讨论一下 一般来说,ZFS 将数据存储在磁盘上。

Zpool、vdev 和设备

ZFS 基础知识:存储和性能
此完整池图包括三个辅助 vdev,每个类一个,四个用于 RAIDz2

ZFS 基础知识:存储和性能
通常没有理由创建不匹配的 vdev 类型和大小的池 - 但如果您愿意,没有什么可以阻止您这样做。

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

水池

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

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

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

有一种常见的误解,认为 ZFS“数据条带”是跨整个池写入的。 这不是真的。 Zpool 一点也不有趣 RAID0,它相当有趣 JBOD 具有复杂的变量分配机制。

大多数情况下,条目根据可用空间分布在可用虚拟设备中,因此理论上它们都会同时被填满。 在 ZFS 的更高版本中,会考虑当前的 vdev 使用情况(利用率) - 如果一个虚拟设备比另一个虚拟设备显着繁忙(例如,由于读取负载),则尽管具有最高的可用空间,但仍会暂时跳过该虚拟设备进行写入空间比例。

现代 ZFS 写入分配方法中内置的利用率检测机制可以在异常高负载期间减少延迟并增加吞吐量 - 但事实并非如此 全权委托 关于在一个池中非自愿混合慢速 HDD 和快速 SSD 的问题。 这样的不平等池仍将以最慢设备的速度运行,也就是说,就好像它完全由此类设备组成。

虚拟设备

每个存储池由一个或多个虚拟设备(virtual device,vdev)组成。 反过来,每个 vdev 又包含一个或多个真实设备。 大多数虚拟设备用于简单的数据存储,但有几个 vdev 帮助程序类,包括 CACHE、LOG 和 SPECIAL。 这些 vdev 类型中的每一种都可以具有五种拓扑之一:单设备(单设备)、RAIDz1、RAIDz2、RAIDz3 或镜像(镜像)。

RAIDz1、RAIDz2 和 RAIDz3 是老人们所说的双(对角)奇偶校验 RAID 的特殊变体。 1、2、3是指为每个数据条分配多少个奇偶校验块。 RAIDz 虚拟设备不是使用单独的磁盘进行奇偶校验,而是在磁盘之间半均匀地分配此奇偶校验。 RAIDz 阵列可以丢失与奇偶校验块一样多的磁盘; 如果再丢失一个,它就会崩溃并带走存储池。

在镜像虚拟设备(镜像vdev)中,每个块都存储在vdev中的每个设备上。 虽然两宽镜像是最常见的,但镜像中可以包含任意数量的设备 - 三重镜像通常用于大型安装,以提高读取性能和容错能力。 只要 vdev 中至少有一个设备继续运行,vdev 镜像就可以承受任何故障。

单个虚拟开发人员本质上是危险的。 这样的虚拟设备无法在单一故障中幸存 - 如果用作存储或特殊的虚拟设备,那么它的故障将导致整个池的破坏。 这里要非常非常小心。

可以使用上述任何拓扑创建 CACHE、LOG 和 SPECIAL VA - 但请记住,SPECIAL VA 的丢失意味着池的丢失,因此强烈建议使用冗余拓扑。

设备

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

磁盘(无论是磁性磁盘还是固态磁盘)是最常见的块设备,用作 vdev 的构建块。 但是,任何在 /dev 中具有描述符的设备都可以,因此整个硬件 RAID 阵列可以用作单独的设备。

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

ZFS 基础知识:存储和性能
您可以在短短几秒钟内从稀疏文件创建一个测试池 - 但不要忘记随后删除整个池及其组件

假设您想要构建一个具有 10 个磁盘的服务器并计划使用 9300 TB 磁盘 (~2 GiB) - 但您不确定哪种拓扑最适合您的需求。 在上面的示例中,我们在几秒钟内从稀疏文件构建了一个测试池 - 现在我们知道八个 10 TB 磁盘的 RAIDz50 vdev 提供了 XNUMX TiB 的可用容量。

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

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

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

数据集、块和扇区

在我们的 ZFS 之旅中要理解的下一组构建块不是关于硬件,而是更多关于数据本身如何组织和存储。 我们在这里跳过一些级别 - 例如元实验室 - 以免弄乱细节,同时保持对整体结构的理解。

数据集(数据集)

ZFS 基础知识:存储和性能
当我们第一次创建数据集时,它会显示所有可用的池空间。 然后我们设置配额 - 并更改安装点。 魔法!

ZFS 基础知识:存储和性能
Zvol 在很大程度上只是一个剥离了文件系统层的数据集,我们在这里将其替换为完全正常的 ext4 文件系统。

ZFS 数据集与标准安装的文件系统大致相同。 与常规文件系统一样,乍一看它看起来就像“只是另一个文件夹”。 但就像常规的可安装文件系统一样,每个 ZFS 数据集都有自己的一组基本属性。

首先,数据集可以有分配的配额。 如果设置 zfs set quota=100G poolname/datasetname,那么您将无法写入已安装的文件夹 /poolname/datasetname 超过 100 GiB。

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

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

如果我们指定 zfs set mountpoint=/lol pool/parent/child,那么数据集 pool/parent/child 安装在系统上作为 /lol.

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

ZFS 基础知识:存储和性能
文件由一个或多个块表示。 每个块都存储在一个虚拟设备上。 块大小通常等于参数 记录大小,但可以简化为 2^移位如果它包含元数据或小文件。

ZFS 基础知识:存储和性能
我们真的 如果您设置的偏移量太小,请不要拿巨大的性能损失开玩笑

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

除非另有说明,当前默认记录大小为 128 KiB。 这是一种棘手的权衡,性能并不完美,但在大多数情况下也并不糟糕。 Recordsize 可以设置为从 4K 到 1M 的任意值(使用高级设置 recordsize 您可以安装更多,但这并不是一个好主意)。

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

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

zvols 没有属性 recordsize — 相反,它们具有同等的属性 volblocksize.

部门

最后一个最基本的组成部分是部门。 它是可以写入底层设备或从底层设备读取的最小物理单元。 几十年来,大多数磁盘都使用 512 字节扇区。 最近,大多数磁盘都配置为 4 KiB 扇区,有些磁盘(尤其是 SSD)具有 8 KiB 扇区甚至更多。

ZFS 系统有一个属性允许您手动设置扇区大小。 这个性质 ashift。 有点令人困惑的是,ashift 是 XNUMX 的幂。 例如, ashift=9 表示扇区大小为 2^9,即 512 字节。

当将每个块设备添加到新的 vdev 时,ZFS 会向操作系统查询有关每个块设备的详细信息,并且理论上会根据该信息自动正确安装 ashift。 不幸的是,许多驱动器谎报其扇区大小,以保持与 Windows XP 的兼容性(Windows XP 无法识别具有其他扇区大小的驱动器)。

这意味着强烈建议 ZFS 管理员了解其设备的实际扇区大小并手动设置 ashift。 如果ashift设置得太小,那么读/写操作的数量就会急剧增加。 因此,将 512 字节“扇区”写入真正的 4 KiB 扇区意味着必须写入第一个“扇区”,然后读取 4 KiB 扇区,用第二个 512 字节“扇区”修改它,将其写回新扇区每个条目 4 KiB 扇区,依此类推。

在现实世界中,三星 EVO SSD 会受到这样的惩罚,为此 ashift=13,但这些 SSD 对其扇区大小撒谎,因此默认设置为 ashift=9。 如果有经验的系统管理员不更改此设置,则此 SSD 可以正常工作 传统磁性硬盘。

为了比较,尺寸太大 ashift 实际上没有任何处罚。 没有真正的性能损失,并且未使用空间的增加是无穷小的(或者启用压缩时为零)。 因此,我们强烈建议即使那些使用 512 字节扇区的驱动器也安装 ashift=12 илидаже ashift=13充满信心地面对未来。

属性 ashift 为每个 vdev 虚拟设备设置,并且 不适合游泳池,正如许多人错误地认为的那样 - 并且在安装后不会改变。 如果不小心撞到 ashift 当您将新的 vdev 添加到池中时,您就已经用低性能设备无可挽回地污染了该池,通常别无选择,只能销毁该池并重新开始。 即使删除 vdev 也无法避免配置损坏 ashift!

写时复制机制

ZFS 基础知识:存储和性能
如果常规文件系统需要覆盖数据,它会更改其所在的每个块

ZFS 基础知识:存储和性能
写时复制文件系统写入新的块版本,然后解锁旧版本

ZFS 基础知识:存储和性能
抽象地说,如果我们忽略块的实际物理位置,那么我们的“数据彗星”就会简化为“数据蠕虫”,在可用空间地图上从左向右移动

ZFS 基础知识:存储和性能
现在我们可以很好地了解写时复制快照是如何工作的——每个块可以由多个快照拥有,并且将持续存在,直到所有关联的快照都被销毁

写时复制 (CoW) 机制是 ZFS 如此出色的系统的基础。 基本概念很简单 - 如果您要求传统文件系统更改文件,它会完全按照您的要求执行。 如果您要求写时复制文件系统执行相同的操作,它会说“好的”,但对您撒谎。

相反,写时复制文件系统会写入已修改块的新版本,然后更新文件的元数据以取消链接旧块并将刚刚写入的新块关联起来。

分离旧块并链接新块是在一次操作中完成的,因此它不能被中断 - 如果发生这种情况后断电,您将拥有文件的新版本,如果您提前断电,您将拥有旧版本。 无论如何,文件系统不会发生冲突。

ZFS 中的写时复制不仅发生在文件系统级别,还发生在磁盘管理级别。 这意味着 ZFS 不受空格影响(RAID 中的漏洞) - 这种现象是在系统崩溃之前条带只来得及部分记录,重新启动后阵列损坏。 这里条带是原子写入的,vdev 总是顺序的,并且 鲍勃是你叔叔.

ZIL:ZFS 意图日志

ZFS 基础知识:存储和性能
ZFS 系统以特殊方式处理同步写入 - 它会暂时但立即将它们存储在 ZIL 中,然后与异步写入一起永久写入。

ZFS 基础知识:存储和性能
通常,写入 ZIL 的数据永远不会被再次读取。 但系统崩溃后有可能

ZFS 基础知识:存储和性能
SLOG(或辅助 LOG 设备)只是一种特殊且最好非常快的 vdev,其中 ZIL 可以与主存储分开存储

ZFS 基础知识:存储和性能
崩溃后,ZIL 中的所有脏数据都会重播 - 在这种情况下,ZIL 位于 SLOG 上,因此从那里重播

写操作主要有两类——同步(sync)和异步(async)。 对于大多数工作负载来说,绝大多数写入都是异步的——文件系统允许它们被聚合并批量发出,减少碎片并大大提高吞吐量。

同步录音则完全不同。 当应用程序请求同步写入时,它会告诉文件系统:“您需要将其提交到非易失性内存 现在在那之前,我无能为力。” 因此,同步写入应立即提交到磁盘,如果这会增加碎片或降低吞吐量,那就这样吧。

ZFS 处理同步写入的方式与常规文件系统不同,ZFS 不是立即将它们提交到常规存储,而是将它们提交到称为 ZFS 意图日志 (ZFS Intent Log) 或 ZIL 的特殊存储区域。 诀窍是这些记录 保留在内存中,与正常的异步写入请求一起聚合,稍后作为完全正常的 TXG(事务组)刷新到存储中。

在正常操作中,ZIL 被写入并且不再被读取。 片刻之后,当 ZIL 中的记录从 RAM 提交到普通 TXG 中的主存储时,它们就会与 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 首先将新的块版本写入未使用的空间。 然后,它将旧版本的块从当前文件系统中分离出来。 但如果某个快照引用了旧块,它仍然保持不变。 在引用该块的所有快照都被销毁之前,旧块实际上不会恢复为可用空间!

复制

ZFS 基础知识:存储和性能
2015 年我的 Steam 库有 158 GiB,包含 126 个文件。 这非常接近 rsync 的最佳情况 - 通过网络进行 ZFS 复制“仅”快了 927%。

ZFS 基础知识:存储和性能
在同一网络上,复制单个 40GB Windows 7 虚拟机映像文件是完全不同的情况。 ZFS 复制比 rsync 快 289 倍,或者如果您足够聪明,可以使用 --inplace 调用 rsync,则“仅”快 161 倍。

ZFS 基础知识:存储和性能
当 VM 映像缩放时,rsync 问题也会随之缩放。 1,9 TiB 对于现代 VM 映像来说并不是那么大 - 但它足够大,即使使用 rsync 的 --inplace 参数,ZFS 复制也比 rsync 快 1148 倍

一旦了解了快照的工作原理,就应该很容易掌握复制的本质。 由于快照只是一个指向记录的指针树,因此如果我们这样做 zfs send 快照,然后我们发送该树以及与其关联的所有记录。 当我们发送这个 zfs send в zfs receive 在目标上,它将块的实际内容和引用目标数据集的块的指针树写入。

第二个事情变得更加有趣 zfs send。 我们现在有两个系统,每个系统都包含 poolname/datasetname@1,然后您拍摄一张新快照 poolname/datasetname@2。 因此,在原始池中你有 datasetname@1 и 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 等一兆字节的零开始,那么很容易将其压缩到磁盘上的一个扇区。 但是,如果我们用 256 兆字节的不可压缩数据(如 JPEG 或伪随机噪声)替换 4 兆字节的零,会发生什么? 出乎意料的是,这一兆字节的数据需要的不是一个,而是 XNUMX 个 XNUMX KiB 扇区,而磁盘上的这个地方只保留了一个扇区。

ZFS 不存在这个问题,因为修改的记录总是写入未使用的空间 - 原始块仅占用一个 4 KiB 扇区,而新记录将占用 256 个扇区,但这不是问题 - 来自“的最近修改的片段”无论文件大小是否改变,文件的“middle”都会被写入未使用的空间,因此对于ZFS来说这是很常见的情况。

默认情况下,本机 ZFS 压缩处于禁用状态,并且系统提供可插入算法 — 目前为 LZ4、gzip (1-9)、LZJB 和 ZLE。

  • LZ4 是一种流算法,可为大多数用例提供极快的压缩和解压缩以及性能优势 - 即使在相当慢的 CPU 上也是如此。
  • GZIP 是所有 Unix 用户都知道并喜爱的古老算法。 它可以使用压缩级别 1-9 来实现,随着接近级别 9,压缩率和 CPU 使用率会增加。该算法非常适合所有文本(或其他高度可压缩)用例,但通常会导致 CPU 问题 - 使用它小心谨慎,尤其是在较高级别时。
  • Z 是ZFS中的原始算法。 它已经过时,不应该再使用,LZ4 在各个方面都超越了它。
  • ZLE - 零级编码,零级编码。 它根本不接触普通数据,而是压缩大的零序列。 对于完全不可压缩的数据集(例如 JPEG、MP4 或其他已压缩的格式)非常有用,因为它会忽略不可压缩的数据,但会压缩结果记录中未使用的空间。

我们建议几乎所有用例使用 LZ4 压缩; 遇到不可压缩数据时的性能损失非常小,并且 发展 典型数据的性能非常重要。 复制虚拟机映像以用于新安装的 Windows 操作系统(新安装的操作系统,内部还没有数据) compression=lz4 通过速度比之前快 27% compression=none2015年的这个测试.

ARC-- 自适应替换缓存

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

尽管本机缓存并非没有问题 - ZFS 无法像内核一样快速响应新的内存分配请求,因此新的挑战 malloc() 如果需要 ARC 当前占用的 RAM,则内存分配可能会失败。 但是有充分的理由使用您自己的缓存,至少目前是这样。

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

该算法通常工作得很好,但在具有大型工作数据集的系统上,LRU 很容易导致抖动 - 驱逐经常需要的块,为永远不会再次从缓存中读取的块腾出空间。

ARC 是一种不太幼稚的算法,可以被视为“加权”缓存。 每次读取缓存的块时,它都会变得有点“重”并且更难驱逐 - 即使在驱逐块之后 被跟踪的 在一定时间内。 已被驱逐但随后需要读回缓存的块也会变得“更重”。

所有这一切的最终结果是缓存具有更高的命中率,即缓存命中(从缓存执行的读取)与缓存未命中(从磁盘读取)之间的比率。 这是一个极其重要的统计数据 - 不仅缓存命中本身的服务速度要快几个数量级,缓存未命中的处理速度也可以更快,因为缓存命中越多,并发磁盘请求越少,并且必须处理的剩余未命中的延迟也越低。与磁盘一起提供。

结论

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

在下一部分中,我们将比较具有镜像 vdev 和 RAIDz 的池的实际性能,以及它们之间的对比,以及与我们探索过的传统 Linux 内核 RAID 拓扑的对比。 早期.

起初,我们只想介绍基础知识 - ZFS 拓扑本身 - 但之后 这样的 让我们准备好讨论 ZFS 的更高级设置和调整,包括辅助 vdev 类型的使用,例如 L2ARC、SLOG 和特殊分配。

来源: habr.com

添加评论