我们都喜欢快速、功能丰富和无限的存储。我们还没有达到无限的容量,但在良好的硬件上,ZFS的功能可以相当快。然而,即使是最好的硬件也会无缘无故地变慢。了解如何使用诊断工具可以帮助您了解系统的性能。也许你不能在手头有设备的情况下修复它,但至少你会理解出了什么问题,也许会将一些负载转移到其他地方。
FreeBSD包括用于检查通用磁盘和文件系统性能的工具,以及ZFS特定的工具。您可以通过 sysctl(8) 、vmstat(8) 和相关命令获得详细信息,但我们强烈建议安装附加包 zfs-stats ,以方便地解析和处理该信息。
一旦您了解了如何评估系统性能,我们将讨论几个ZFS性能功能,以及它们何时可能有用。
在深入评估ZFS性能之前,让我们谈谈一般性能。
第八章:性能什么是性能?ZFS 与性能zpool iostat当前和正在进行的池活动虚拟设备活动ZFS 预取Per-VDEV 预取Per-File 预取事务组调整txg 定时txg 大小txg 持续时间和内容写节流阀I/O 调度测量延迟和吞吐量I/O 队列Per-VDEV 请求调度大型 VDEVs异步写入和事务组大小限制写入擦洗(scrub)和重组(resilver)性能
性能可以描述为“系统管理工作负载的程度”。每个系统的工作负载略有不同,因此即使在执行看似相同的任务的相同硬件之间,性能也会有所不同。即使您非常努力地在另一个系统上复制硬件、软件安装和工作负载,也会有人发现它们之间的差异。这是基准测试如此烦人的部分原因。
系统管理员主要关心提高性能。这意味着识别和消除瓶颈。一般的计算机有四种基本资源:存储输入/输出、网络带宽、内存和CPU。如果您在系统上堆积如山,直到它无法处理更多的工作,那么您真正要做的是发现这四个资源中的哪一个首先饱和。该资源是您的瓶颈。
提高性能需要识别和转移瓶颈。你总是会遇到另一个瓶颈。如果CPU是当前的瓶颈,并且您增加了更多的处理能力,则计算机会加速,直到磁盘I/O或内存或网络饱和。您已经提高了性能,是的……达到了新瓶颈允许的限制。
而不同的工作负载可能具有完全不同的瓶颈。
许多系统管理需要将这四种资源中的一种交换为另一种。这就是为什么Lucas总是将“系统调优”称为“重新安排瓶颈(rearranging bottlenecks)”。
考虑一下ZFS压缩。ZFS压缩减少了系统写入磁盘和从磁盘中提取的数据量。压缩和解压缩块会消耗处理器时间。压缩为CPU时间交换存储I/O。然而,大多数计算机的处理器魅力远远超过它们可能使用的能力。我正在写这篇文章的笔记本电脑有一个四核处理器,但只有一个中等速度的磁盘,I/O非常有限。在这个系统上,启用压缩是一个明显的胜利。在磁盘I/O大于处理器功率的系统上,您可能会做出不同的决定。
存储越复杂,您就越能够调整和转移存储瓶颈。您的服务器有六个磁盘控制器,但所有I/O都将连接到其中一个?重新排列数据集,以在多个控制器上拆分负载。也许一个特定的磁盘是饱和的?将负载分开。也许您的池中充斥着写入或读取,或者两者兼而有之。添加正确配置的SLOG和L2ARC以提供帮助。
然而,在进行任何更改之前,请调查瓶颈在哪里。如果系统的特定工作负载受到CPU或内存的限制,则购买更快的磁盘系统将无济于事。如果磁盘读取限制了服务器的性能,则为ZIL添加快速SSD不会提高性能。
系统管理中许多长期存在的问题之一是:是什么占用了我的磁盘带宽?ZFS池清理可能会影响其他操作,但在日常使用中,ZFS不会为该问题创建任何新的答案。使用 top -m io 来标识最频繁使用磁盘的进程。最活跃的进程是否应该如此繁忙?
如果您的性能与预期不符,请记住,存储系统的性能仅与其最慢的组件一样好。您可以有一个非常快的SAS控制器和顶级的高速硬盘驱动器,但由于粗糙的驱动器电缆,性能很差。SATA端口乘法器(multiplier)与连接的驱动器数量成比例地大幅降低性能。仅仅因为您可以将某些硬件组件插在一起,并不意味着您应该这样做。
许多常见的存储性能调整建议都适用于ZFS,就像它适用于任何其他文件系统一样。如果您不需要知道上次使用文件的时间,请使用 atime 属性禁用记录访问时间。
ZFS旨在使用大量磁盘空间。超过80%已满的池执行得很差。这是ZFS组合的固有方式。如果您试图找出几乎已满的池运行缓慢的原因,请将一些数据移动到另一个池。在几乎满的池上释放空间可以缓解大多数ZFS问题。
ZFS还设计用于64位系统。有了一些说服力、运气和一点传统的海地巫术(Haitian voodoo),您可以让ZFS在32位FreeBSD上工作。它不会很好地工作,也不会很有效,但系统会启动。对32位系统上的ZFS性能感到沮丧就像对节奏感差的跳舞熊感到恼火。在这两种情况下,令人惊讶的是它完全有效。
如果提高文件系统性能的常见sysadmin建议无助于解决问题,则必须深入了解存储系统性能不佳的原因。每个操作系统都包含用于测量性能的工具。FreeBSD的 vmstat(8) 可以快速识别系统是否正在等待处理器、存储或内存。
要查看池的性能,请使用 zpool iostat 。
zpool iostatzpool的 iostat 组件提供了池在特定时刻的运行快照。要查看自系统启动以来池上的平均活动,可以运行 zpool iostat :
xxxxxxxxxx# zpool iostat capacity operations bandwidthpool alloc free read write read write----- ----- ----- ----- ----- ----- -----work 2.21G 1.81T 2 402 36.2K 24.0Mzroot 15.5G 905G 275 0 7.34M 0我们得到了每个池的名称,以及每个池中分配和可用的空间量。最后四列显示每个池的读写活动,单位为每秒操作数和每秒字节数。
以上例子显示了两个池:work 和 zroot 。zroot 池已分配15.5GB,可用905GB。该池每秒执行275次读取操作,即每秒7.34MB,没有写入操作。这意味着每次读取操作的平均大小约为27KB。
work 池更有趣,每秒有2个读取请求,但有402个写入,总共24MB/s。读取可以忽略不计,但每次写入的平均值约为60KB,这里正在进行实际的工作。
这对您的池意味着什么?单独采取,不多。这是特定时刻的池活动。这一瞬间可能是平均值,也可能是高点或低点。您需要池活动的持续视图才能做出任何明智的决策。
要查看单个池的活动,请指定池名称:
xxxxxxxxxx# zpool iostat work这将消除除指定池的输出外的所有输出。
请记住,这是自系统引导以来的平均行为。它不反映当前值。
要查看池在这个特定时刻的行为,以及活动如何随时间变化,请让 zpool iostat 每隔几秒钟打印一次新的统计信息。在命令行末尾指定秒数。这里我们每两秒钟更新一次。点击 CTRL-C 退出。
xxxxxxxxxx# zpool iostat 2 capacity operations bandwidthpool alloc free read write read write----- ----- ----- ----- ----- ----- -----work 3.37G 1.81T 14 107 146K 900Kzroot 15.5G 905G 3 2 32.9K 12.7K----- ----- ----- ----- ----- ----- -----work 3.37G 1.81T 0 0 0 0zroot 15.5G 905G 0 0 0 0----- ----- ----- ----- ----- ----- -----第一个条目是自系统启动以来的平均活动,就像您在没有间隔的情况下运行 zpool iostat 一样。第二个和后面的条目提供当前值。
在标头之后,当您第一次运行命令和一组虚线时,前两个条目给出池活动。两秒钟后,它在分隔符下方打印一组新的数据。
如果指定检查单个池,zpool iostat 将丢失分隔符。这里我们看一下 work 池。
xxxxxxxxxx# zpool iostat work 2 capacity operations bandwidthpool alloc free read write read write----- ----- ----- ----- ----- ----- -----work 3.35G 1.81T 15 104 148K 897Kwork 3.35G 1.81T 0 616 2.25K 3.74Mwork 3.35G 1.81T 1 553 5.74K 2.78Mwork 3.36G 1.81T 1 607 21.0K 2.57M...这个池平均每秒执行104次写入操作,但目前它每秒执行600多次写入操作,说明它正在工作中。
虽然每个池包含一个或多个相同的虚拟设备,但池对这些虚拟设备的使用可能并不相同。
一种常见的情况是当池几乎满时,并向其中添加新的虚拟设备以获得更多空间。然后,池的表面写入性能可能会下降到新虚拟设备的性能,而不是整个池及其所有虚拟设备的理论吞吐量。新的虚拟设备具有所有可用空间,因此这就是新写入的位置。随着时间的推移,在删除旧文件和删除旧快照时,每个VDEV的利用率可能会达到平均值。然而,根据您的工作负载,VDEV利用率可能永远不会达到平衡。
要查看每个池的每个VDEV活动,请在 iostat 后添加 -v 标志。
与常规的非详细 zpool iostat 一样,得到的第一组输出表示系统启动后的平均值,在详细模式下,这些数字看起来有点奇怪:
xxxxxxxxxx# zpool iostat -v work capacity operations bandwidthpool alloc free read write read write---------- ----- ----- ----- ----- ----- -----work 2.13G 1.81T 10 87 100K 715K mirror 1.06G 927G 5 43 50.9K 359K gpt/zfs0 - - 2 8 26.4K 360K gpt/zfs1 - - 2 8 26.6K 360K mirror 1.07G 927G 4 43 49.6K 356K gpt/zfs2 - - 2 8 25.0K 357K gpt/zfs3 - - 2 8 26.4K 357K---------- ----- ----- ----- ----- ----- -----你将获得池的总数、池中每个VDEV的总数以及池中每个提供者的数字。查看此池上每秒的写入操作数。自系统启动以来,整个池平均每秒进行10次读取操作。第一个镜像设备负责其中的五个,第二个负责其中的四个。每个虚拟设备中的每个磁盘每秒处理两次读取。
写入活动看起来很奇怪。ZFS的初始数据显示,该池平均每秒有87个写请求,每个池有43个。这还不错,但每个磁盘的值显示,每个磁盘平均每秒有8个写请求。论您对这些值的处理有多奇怪,它们甚至都不接近。
简而言之,ZFS的每个磁盘的平均值作为原始数字并不十分可靠。它们在比例上是正确的。 zpool iostat 在测量性能时不会锁定内核数据结构,因此在命令运行时会有轻微的变化。
与非详细的 zpool iostat 一样,要查看当前值,必须在命令行末尾提供一个间隔。以下示例显示了池中每台设备的活动,每两秒更新一次:
xxxxxxxxxx# zpool iostat -v work 2… capacity operations bandwidthpool alloc free read write read write---------- ----- ----- ----- ----- ----- -----work 2.31G 1.81T 0 69 0 147K mirror 1.15G 927G 0 40 0 93.6K gpt/zfs0 - - 0 10 0 94.9K gpt/zfs1 - - 0 10 0 94.9K mirror 1.16G 927G 0 28 0 53.3K gpt/zfs2 - - 0 6 0 54.6K gpt/zfs3 - - 0 6 0 54.6K---------- ----- ----- ----- ----- ----- -----第一个镜像VDEV每秒执行的I/O操作比第二个镜像多。同样,单个磁盘编号加起来并不等于设备中的操作总数,但它们按比例是准确的。
现在我们看到池的工作情况了,接下来讨论一些可以改变池性能的功能。
prefetch——预取
文件系统的工作是根据请求提供存储的数据。ZFS进一步深化了这一理念,准备提供您即将要求的数据。这发生在两个层面上:每个VDEV(per-VDEV)和每个文件(per-file)。FreeBSD默认情况下不启用每个VDEV预取,仅启用每文件预取。
从旋转磁盘检索数据最耗时的部分是将磁头定位在包含数据的磁道上。这就像做一个三明治,在两片面包之间拍打花生酱需要两分钟,但去商店买面包和花生酱可能需要一个小时。一旦硬件被物理地安排好,以5000或10000 RPM的速度从旋转的磁盘中读取完整的数据轨迹需要的时间是微秒(microseconds)级别的。
每个VDEV预取是试图使移动磁头有价值。默认情况下,FreeBSD不使用每VDEV预取,但如果您的工作负载涉及以下内容,则每个VDEV的预取可能会对您有所帮助:
每当ZFS从VDEV中读取几个块时,它也会读取目标块之后的几个块,以查找元数据。找到的任何元数据都会填充到特定的每个VDEV预取缓存中。请求程序很可能会返回并请求该元数据。每当程序请求预取元数据时,ZFS都会从缓存中提供它,返回到物理VDEV,并预取更多块。
每个VDEV预取的块进入简单的滚动最近最少使用的(Least Recently Used)缓存,而不是ARC。如果从未调用这些块,它们很快就会被丢弃。VDEV缓存的大小等于VDEV中存储提供程序的数量乘以可调的 vfs.zfs.vdev.cache.size 。FreeBSD默认将其设置为 0 ,因此不使用缓存。通过在 /boot/loader.conf 中将此可调项设置为所需的值来启用缓存。常用值为10 MB。
xxxxxxxxxxvfs.zfs.vdev.cache.size=”10M”重启系统之后就会获得per-VDEV预取缓存。
sysctl kstat.zfs.misc.vdev_cache_stats.misses 显示ZFS检查per-VDEV缓存的元数据但未找到的次数。同样,sysctl kstat.zfs.misc.vdev_cache_stats.hits 显示ZFS在缓存中找到某些内容的频率。
测试有无每个VDEV预取的工作负载,并查看其行为。
per-VDEV预取抢占式缓存的容量是多少?sysctl vfs.zfs.vdev.cache.max 给出了从VDEV读取的最小大小。默认值为16384,即16KB。如果程序请求的读取小于此大小,则per-VDEV预取将启动。
然而读取不仅仅扩展到16KB。sysctl vfs.zfs.vdev.cache.bshift 给出了要预取和搜索元数据的数据量。这是一个位移值,默认值16表示64KB。
因此,如果程序请求的读取小于16KB,ZFS将读取64KB。如果程序请求读取20KB,则不会发生per-VDEV的预取。
虽然更改预取值有助于提高某些旧版本ZFS的性能,但在现代ZFS中,您几乎应该始终不去管它们。作者不知道在任何情况下改变这些值会有所帮助,但我们确实知道很多时候改变这些值会导致痛苦。
如果程序请求文件的开头,它可能很快就会想要文件的其余部分。ZFS的per-file预取试图预测此类请求,在程序开始请求之前将文件缓存在ARC中。这使ZFS感觉更具响应性(responsive)。这通常被称为ZFS的智能预取(intelligent prefetch),或者有时只是称呼为预取(prefetch)。虽然文件级预取可能看起来并不十分复杂,但大多数文件系统都无法管理它。
文件级预取会增加ARC的大小。FreeBSD会自动禁用RAM小于4 GB的主机上的预取,并自动为RAM为4 GB或更多的主机启用预取。您可以通过在 /boot/loader.conf 中将可调 vfs.zfs.prefetch_diable 设置为 1 来覆盖这一点。
预取可能会在承载数十万(或更多)小文件(如64KB及以下)的系统上造成问题。您需要禁用此类主机的文件级预取。然而,这些系统相当罕见。
通常情况下,如果您的系统有足够的内存来支持文件级预取,则可以提高性能。您可以启用和禁用预取以测试性能,但在几乎所有情况下,预取都是有帮助的。
txg——transaction group,事务组,以有序方式写入磁盘的单个数据块(a single lump of data)。
您可以通过调整事务组和I/O调度程序来调整性能。我们专门讨论FreeBSD 10和更高版本的调优。在早期版本中调优OpenZFS写入的机制更为巴洛克风格(baroque——气势雄伟、装饰华丽)。
事务组(或称 txg)是以有序方式写入磁盘的单个数据块。事务组可以包含来自许多不同程序的许多块。如果整个事务组未成功写入磁盘,则会取消整个组。
您可以控制系统写入事务组的频率及其最大大小。
如果没有其他东西触发将txg写入磁盘,则ZFS每隔几秒钟写入一次,如sysctl vfs.zfs.txg.timeout 所示。虽然此设置的值在OpenZFS的早期版本中有点波动,但当前的标准是 5 秒。最糟糕的情况是,任何挂起的数据每五秒钟写入一次磁盘。
对于大多数系统,每五秒钟写入一次是可以的。像 top(1) 这样的程序可能会在待处理事务组被压缩时每隔五秒钟显示一次CPU活动状态。您很少会将超时时间减少到5秒以下。
然而,增加该值对于某些系统可能有意义。如果您在低负载虚拟机上运行ZFS,您可能会将txg超时时间拖到15左右。Lucas通常在虚拟机上运营LDAP镜像和权威DNS服务器等主机,而这类主机很少对磁盘I/O有高要求。减少事务写入频率不会提高此特定虚拟机上的性能,但它将改善在该虚拟机管理程序上运行的其他虚拟机的硬件访问。为该主机上的所有虚拟机提供类似的低设置将全面提高虚拟机的性能,但单个自私或高负载虚拟机可能会吞噬其中的许多收益。(然而,这可能正是您试图实现的目标。)
在共享读写带宽的典型硬件上,增加超时可能会在大多数情况下提高读取性能,但在写入期间会降低读取性能。
如果计时器过期,并且系统没有等待写入磁盘的事务,则ZFS不会写入任何数据。ZFS不会仅仅为了拥有事务组而编写空事务。然而,它仍然会增加事务组计数。
将事务组超时设置为小于5秒将根据I/O调度程序和写入限制运行。对我们大多数人来说,五秒钟是最小的合理值。
增长足够大的txg在超时之前提交到磁盘。FreeBSD根据主机中的内存量和可调 vfs.zfs.dirty_data_max_percent 自动调整启动时事务组的最大大小。默认值为 10(您可以在引导后更改 vfs.zfs.dirty_data_max_percent 。它不会以任何方式影响系统性能,但您可以更改它),最大为4 GB,由 vfs.zfs.dirty_data_max_max 控制。一旦事务组使用了系统RAM的10%,它就会被写入磁盘。
您可以在启动后使用sysctl vfs.zfs.dirty_data_max 更改事务组的最大大小。该值以字节为单位,因此将所需的千兆字节数乘以 10243 ,即可获得正确的sysctl值。
难题是:你应该改变交易组的规模吗?您的系统将10%的RAM写入磁盘需要多长时间?这种情况多久发生一次?大多数主机的RAM远远超过它们的I/O吞吐量。试图在五秒钟内将十分之一的RAM写入磁盘将是一场灾难。Lucas的测试主机在单个池中有几个硬盘驱动器和32GB的RAM。将3.2GB写入磁盘需要20多秒钟。如果该主机在不到标准的5秒钟txg超时的时间内生成3.2 GB的磁盘活动,则机器将很快陷入不可用状态。
如果您的系统具有高性能磁盘阵列,具有巨大的吞吐量和经济大小的RAM堆,那么您可能会发现增加最大值很有用。
在这些“写入周期”(write cycles)期间,来自磁盘的大多数读取都将暂停。这允许写入尽快完成。写作完成后,阅读将继续。这样“交错”(interleaving)工作负载通常会提高性能。使用工作负载的知识,您可以决定是减少刷新较大事务组的频率还是增加刷新较小事务组的次数是最佳方法。在同一池中批量复制数据时,Jude将txg大小增加到24 GB,将超时时间增加到30秒,性能提高了25%。
如果试图调整事务组的大小和周期,应当先查询事务组有多大以及它们提交到磁盘需要多长的时间。Adam Leventhal创建了一些用于测量两者的DTrace脚本,可在以下网站获得:http://dtrace.org/blogs/ahl/2014/08/31/openzfs-tuning/或位于http://zfsbook.com.我们将两者都讨论。
要测量每个txg中的数据量,请使用Leventhal的脚本 dirty.d(“脏数据”在内存中,等待写入磁盘):
xxxxxxxxxxtxg-syncing{ this->dp = (dsl_pool_t *)arg0;}txg-syncing/this->dp->dp_spa->spa_name == $$1/{ printf("%4dMB of %4dMB used", this->dp->dp_dirty_total / 1024 / 1024, `zfs_dirty_data_max / 1024 / 1024);}运行这个脚本,测量指定的池(系统需要加载DTrace):
xxxxxxxxxx# kldload dtraceall# dtrace -s dirty.d zrootdtrace: script 'dirty.d' matched 2 probesCPU ID FUNCTION:NAME 3 61042 :txg-syncing 2MB of 6539MB used 1 61042 :txg-syncing 7MB of 6539MB used 4 61042 :txg-syncing 5MB of 6539MB used…每次ZFS将txg写入磁盘时,DTrace都会打印每个txg的大小和ARC的大小。如果使用sysctl vfs.zfs.txg.timeout ,更改事务组之间的间隔,将看到事务组的大小发生了变化。
Leventhal的 duration.d 显示了每个事务组完成所需的时间。
x
txg-syncing/((dsl_pool_t *)arg0)->dp_spa->spa_name == $$1/{ start = timestamp;}txg-synced/start && ((dsl_pool_t *)arg0)->dp_spa->spa_name == $$1/{ this->d = timestamp - start; printf("sync took %d.%02d seconds", this->d / 1000000000, this->d / 10000000 % 100);}按上一个脚本方式运行它:
xxxxxxxxxx# dtrace -s duration.d zrootdtrace: script 'duration.d' matched 2 probesCPU ID FUNCTION:NAME 1 61043 :txg-synced sync took 0.11 seconds 2 61043 :txg-synced sync took 0.24 seconds 4 61043 :txg-synced sync took 0.22 seconds 2 61043 :txg-synced sync took 0.31 seconds尽管我们尽了最大努力,但ZFS在该系统上的工作不是很努力。
如果看到事务组相对于最大txg大小反弹,您可能希望增加txg的大小或减少事务组之间的时间。
一旦你掌握了这些诀窍, Jude创建了一个脚本,同时测量这两个。
【此脚本未能成功运行,不懂】
x
txg-syncing/((dsl_pool_t *)arg0)>dp_spa>spa_name == $$1/{ start = timestamp; this->dp = (dsl_pool_t *)arg0; d_total = this->dp->dp_dirty_total; d_max = `zfs_dirty_data_max`;}txg-synced/start && ((dsl_pool_t *)arg0)>dp_spa>spa_name == $$1/{ this->d = timestamp - start; printf("%4dMB of %4dMB synced in %d.%02d seconds", d_total / 1024 / 1024, d_max / 1024 / 1024, this->d / 1000000000, this->d / 10000000 % 100);}同时查看txg大小和时间可以进一步了解池的实际行为。
你会听到的一个术语是——write throttle——写节流阀。当程序将数据输入内存的速度超过ZFS将其写入磁盘的速度时,写节流阀就会发挥作用。随着系统RAM越来越满,ZFS开始在每个写入请求中插入一个小延迟。程序会一直等到收到对其写入请求的响应,因此在此处设置延迟会迫使它们减慢速度。目标是确定磁盘可以承受多少负载,并减慢程序速度,使其以相同的速度运行。
在ZFS的旧版本中,写入限制导致了非常不规则的性能。FreeBSD 10和更新版本中的写节流算法工作得更顺利。您可以通过下面讨论的I/O调度程序对其进行调优。
并非所有硬件都是平等的。Jude的顶级笔记本电脑的I/O容量比他的任何内容交付网络(Content Delivery Network)服务器都要小得多。FreeBSD的默认设置相当通用。虽然你真的不需要在笔记本电脑上调整它们,但如果你有几十个或几百个具有非常特定工作负载的磁盘,你可以通过调整调度来调整性能。调度I/O允许您调整延迟(latency)和吞吐量(throughput)。
虽然硬盘驱动器通常会描述它们每秒可以执行多少I/O操作(IOPS),但这并不像你想象的那么有用。能够执行250 IOPS的精心选择的数据,并不能说明驱动器处理数据的能力。
把硬盘想象成汽车。一些针对容量进行了优化,另一些针对里程进行了优化。一个巨大的串联式拖拉机拖车平台可以运输比特斯拉跑车多得多的东西,但它肯定不会在红灯后快速行驶。我们大多数人都会在周末用这辆大卡车把一个百人呼叫中心搬到镇上,但更喜欢不同的优化,以便在孩子的阑尾破裂之前把她送到医院。
与汽车不同,你可以在一定程度上控制大多数硬盘的优化。(一些专用存储设备是专门为某些优化而设计的。)这就是为什么你可以在笔记本电脑的硬盘上转储大量数据,让系统看起来没有反应——你只是用吞吐量换取了延迟。
ZFS I/O调度旨在平滑延迟(smooth out latency)。这可能会降低吞吐量,但会使整体体验更加一致。通常,通过更改调度,您可以在不引入太多延迟的情况下提高性能。
ZFS调度是围绕I/O队列构建的。
您如何知道更改是否会对系统性能产生积极影响?Adam Leventhal为illumos编写了一个延迟和吞吐量DTrace脚本,但这里有一个为FreeBSD修改的版本。
xxxxxxxxxx#pragma D option quietinline uint32_t BIO_READ = 1;inline uint32_t BIO_WRITE = 2;this uint64_t delta;BEGIN{ start = timestamp;}io:::start/ args[0] /{ ts[args[0]] = timestamp;}io:::done/args[0] && ts[args[0]]/{ this->delta = (timestamp - ts[args[0]]) / 1000; this->name = (args[0]->bio_cmd & (BIO_READ | BIO_WRITE)) == BIO_READ ? "read " : "write "; @q[this->name] = quantize(this->delta); @a[this->name] = avg(this->delta); @v[this->name] = stddev(this->delta); @i[this->name] = count(); @b[this->name] = sum(args[0]->bio_bcount); ts[args[0]] = 0;}END{ printa(@q); normalize(@i, (timestamp - start) / 1000000000); normalize(@b, (timestamp - start) / 1000000000 * 1024); printf("%-30s %11s %11s %11s %11s\n", "", "avg latency", "stddev", "iops", "throughput"); printa("%-30s %@9uus %@9uus %@9u/s %@8uk/s\n", @a, @v, @i, @b);}在系统正常负载时多运行这个脚本几次,可以获得延迟和吞吐量的准确基线。sleep(1) 命令告诉脚本收集数据要花费多长时间。rw.d 脚本监视这几秒钟的吞吐量和延迟,然后打印出两个读写性能图。最后你会得到这样的报告::
xxxxxxxxxx# dtrace -s rw.d -c 'sleep 30'… avg latency stddev iops throughputwrite 1362us 8378us 68/s 4620k/sread 6943us 5839us 2/s 46k/s一般来说,性能调优的目标是在不使其他数字过大的情况下提高您关心的数字。使用每种类型允许的最大读写次数,在运行之间将其增加20-100%。确保你没有将这些值提升到足够高,以至于你的VDEVs超过了 per-VDEV的限制,或者,也可以选择使用最小值。
stddev(标准偏差)列尤其值得注意。您可能会实现出色的吞吐量,但会发现自己的延迟变化很大。如果你的一些读写需要五秒钟才能完成,那么大吞吐量可以吗?只有你知道。
调整读取性能时,请注意ARC。如果您继续访问同一文件,内核将使用内存中的副本,而不是从磁盘重新读取。为了正确测试读取性能,您必须从ARC中刷新常用文件。卸载并重新装载读取密集型数据集,或者只是重新启动计算机。
是的,性能调优和测试是侵入性的。大多数人不费心去做这件事是有原因的。好消息是,ZFS在默认设置下表现得很好。
ZFS将I/O流量分为五个队列:同步读取、异步读取、同步写入、异步写入和scrubs。每个都有一对相关的sysctls,用于控制每个存储提供程序上可以同时活动的该类型的最大和最小未完成请求。
Synchronous reads(同步读取)是指应用程序现在正在请求数据。应用程序,可能还有用户,都坐在那里等待数据,然后才能继续工作。同样,Synchronous writes(同步写入)请求立即写入数据。数据库和其他希望确保在数据安全地存储在磁盘上之前不会执行“下一步”的应用程序请求同步写入。在ZFS中,同步写入会尽快完成。这就是SLOG发挥作用的地方,它是一种快速的专用设备,可以临时存储同步写入,比正常存储更快。
Asynchronous reads(异步读取)不太重要,主要由ZFS的预取功能组成,该功能在您需要时从磁盘加载数据。当ZFS开始读取这些数据并使其可用时,应用程序将收到通知,而不是显式等待它。Asynchronous writes(异步写入)的工作方式类似。应用程序向ZFS提供一些数据,并说:“在某个时候写下来。”ZFS将异步写入的数据保存在内存中,直到下一个txg关闭,然后将其刷新到磁盘。将这些写入内容组合在一起并批量写出可以提高性能。
然而,最大值和最小值并不同时适用。最大值适用于一组条件,而最小值适用于不同的条件。对于一种写入类型,其中一些值是相同的——例如,同步读取的最大值和最小值都设置为 10 。这不是冲突,只是针对不同情况的不同设置。
同步读取包括程序请求的数据。在文本编辑器中调用文件是同步读取。使用sysctls vfs.zfs.vdev.sync_read_max_active 和 vfs.zfs.vdev.sync_read_min_active 控制这些。每个默认值为10。用户最有可能注意到同步读取的延迟。如果用户试图打开一个文件,并且需要5秒而不是50毫秒,用户会说系统很慢。
预取请求是异步读取;还没有人请求这些数据,但ZFS猜测请求很快就会到达。如果程序读取了文件的第一块,ZFS会很好地知道,对文件其余部分的请求很快就会到来。使用sysctls vfs.zfs.vdev.async_read_max_active 和 vfs.zfs.vdev.async_read_min_active 控制未完成的异步读取请求的数量。这些默认值最大为 3 ,最小为 1 。限制异步操作数量的目的是确保新的同步操作不会出现在一堆不太重要的读取后面。当驱动器上未完成的操作数量降至最低值以下时,ZFS会向队列添加更多工作,最重要的操作会首先添加到队列中。队列中的操作顺序不会改变。
fsync(2) 系统调用的情况。这些请求直接发送到ZIL,可以在数据存储提供程序上,也可以在单独的SLOG上。这些写入是最重要的操作,因为调用应用程序正在等待操作完成,然后再继续。将此值调整得太低会降低吞吐量并增加延迟。ZFS中的写入通常是批处理的,因此利用写头处于正确位置的优势一次写入尽可能多的数据。但是,如果要排队的操作数量太多,同步读取必须等待已经排队的写入完成,这可能会对系统响应性产生负面影响。系统 vfs.zfs.vdev.sync_write_max_active 和 vfs.zfs.vdev.sync_write_min_active 控制每个提供程序在任何时候可能挂起的这些请求的数量。两者都默认为 10 。vfs.zfs.vdev.async_write_max_active 和 vfs.zfs.vdev.async_write_min_active 控制单个存储提供程序上可以同时活动的未完成异步写入请求的数量。默认最大值为 10 ,最小值为 1 。2 的 sysctls vfs.zfs.vdev.scrub_max_active 和默认值为 1 的 vfs.zfs.vdev.srub_min_active 控制此队列。调整这些旋钮可以调整擦洗对系统负载的影响。更高的队列深度会使清理更快完成,但会将其他操作排在清理操作之后。ZFS如何使用这些限制取决于允许的未完成请求的数量。
要了解ZFS将如何安排活动,您必须知道可以向系统的每个VDEV发送多少未完成的请求。考虑每个存储提供程序(通常是磁盘)每种类型的最大请求数,如前一节中的sysctls所示。
一个具有最大并发请求数的磁盘将有35个未完成的请求。10个磁盘的VDEV可能有350个同时未完成的请求,而29个磁盘的VDCV可能有1015个同时未解决的请求。
看看每种类型的请求,你会发现这是一个相当平衡的计划。同步和异步写入都有自己的队列,因此您的写入不会使ZIL过载。读取次数与异步写入次数一样多。文件级预取被拒绝,因此ZFS对程序所需数据的猜测不会淹没程序主动请求的流量。
更改这些值可以调整ZFS如何分发请求。你想让你的系统在写入数据方面保持平衡吗?增加异步写入的最大数量。你想快速擦洗吗?抬起擦洗请求天花板。
但是,您无法通过调整这些设置来提高硬件速度。现有的限制可能会使5400 RPM SATA硬盘饱和。高端存储设备,您有单独的读写硬件,可能可以处理稍高的值。即便如此,你很快就会达到最大容量。
对于固态存储,如SSD,其IOPS数量可能比旋转磁盘大得多,可以通过增加所有这些可调参数来提高性能。为了让设备发挥最大性能,您必须给它足够的工作来保持忙碌,但同时,不要做太多工作,以至于需要太长时间才能处理添加到队列末尾的重要请求。使用本章前面提供的DTrace脚本来测量负载下的延迟,并根据需要进行调整。
不过,如果你有超级硬件,把限制提高得太高,你会达到每VDEV的限制,改变一切。
ZFS有两个调度系统:一个用于当系统允许许多未完成的I/O请求时使用,另一个用于系统不允许时使用。多少是多?这取决于您的VDEV和主机。
sysctl vfs.zfs.vdev.max_active 给出了ZFS更改调度算法的标志级别。FreeBSD的默认值是1000。对于大多数主机,在默认配置中,这意味着在切换算法之前,VDEV中最多可以有28个磁盘。如果你改变了I/O队列,你就改变了数学。
达到极限意味着ZFS改变了它的调度方式。它不使用最大值作为上限,而是允许每个磁盘有等于最小值的未完成请求数量。
这意味着每个磁盘至少有25个未完成的请求。默认情况下,系统最多可以支持1000个未完成的请求,因此任何其他请求都会按优先级顺序分配。
如果单个VDEV中有40多个存储提供程序,即使是最小值也会超过系统上允许的总数。更改 vfs.zfs.vdev.max_active 以允许更多请求,或者最好重新排列VDEVs以包含足够数量的存储提供程序。
异步写入的工作方式略有不同。当系统大部分时间处于空闲状态,没有太多要写的内容时,系统会创建一个异步写请求。(从技术上讲,最小值是sysctl vfs.zfs.vdev.async_write_min_active ,但实际上没有理由将其设置为 1 以上。)内存中等待写入磁盘的数据称为“脏数据”(dirty data)。随着事务组大小和频率的增加,zfs会安排越来越多的并发写入。当系统达到 vfs.zfs.vdev.async_write_max_active sysctl定义的最大写入请求数时,它会开始人为地减缓对写入请求的响应。
sysctls的 vfs.zfs.vdev.async_write_active_min_dirty_percent 和 vfs.zfs.vdev.async_write_active_max_dirty_percent 控制ZFS如何添加写入请求。这些是系统上允许的脏数据的百分比——txg的最大大小,或sysctl vfs.zfs.dirty_data_max 的值。在最小百分比及以下,系统使用最小数量的写入请求,为读取留出更多带宽。在最大百分比及以上时,系统使用最大数量的写入请求来尝试跟上需要写入的数据量。请求数量在它们之间呈线性增长。
默认情况下,最小百分比为 30 ,最大百分比为 60 。异步写入请求的最小数量为 1 ,最大数量为 10 。结果如何?
假设一个主机将 vfs.zfs.dirty_data_max 设置为1GB,因为这使计算变得容易。一个txg的大小只能为1GB。如果主机有高达300 MB的数据可供写入(1 GB的30%),它将使用单个写入请求。超过300的每30 MB脏数据都会增加一个写入请求。如果主机有600 MB的数据准备写入(1 GB的60%),它将排队10个写入请求。
在理想情况下,系统以正常负载巡航,txg的大小应该在最小和最大大小之间。我们的 vfs.zfs.dirty_data_max 主机应该有大约450 MB的脏数据,正负(plus or minus)150。
也许你的VDEVs可以处理排队到磁盘的10多个命令,所以你想增加 vfs.zfs.vdev.async_write_max_active sysctl。将此sysctl增加到超出硬件可以处理的范围会导致延迟增加,因此请务必监控正常负载下任何更改的影响。更改未完成写入请求的最大数量会影响系统创建写入请求的速度,但不会影响百分比。
给出的百分比适用于大多数负载,但如果您的系统延迟波动,您可能会调查操作数量和延迟量。
xxxxxxxxxx#pragma D option aggpack#pragma D option quietfbt::vdev_queue_max_async_writes:entry{ self->spa = args[0];}fbt::vdev_queue_max_async_writes:return/self->spa && self->spa->spa_name == $$1/{ @ = lquantize(args[1], 0, 30, 1);}tick-1s{ printa(@); clear(@);}fbt::vdev_queue_max_async_writes:return/self->spa/{ self->spa = 0;}使用 dtrace 运行此脚本,并将池的名称作为参数。
xxxxxxxxxx# dtrace -s q.d zroot您每秒都会看到条形图,显示当前的延迟和操作次数。
如果延迟和操作次数不同,可以降低 vfs.zfs.vdev.async_write_active_min_dirty_percent ,以便系统更快地发出额外的写入请求。您还可以增加 vfs.zfs.vdev.async_write_active_min_dirty_percent 中的最大百分比,或增加系统上允许的脏数据量。
硬件都是独一无二的。如果你深入ZFS调谐(tuning),你必须转动这些刻度盘,看看它们在你的特定硬件上做了什么。
要求内核写入磁盘的程序在内核确认写入请求之前不会继续。也有例外——在多线程程序中,写请求可能只阻塞一个线程。在多个并发进程中运行的应用程序,如Apache,在I/O上可能只有一个进程块。不过,一般来说,在内核确认写入请求之前,I/O上的每个程序或程序的一部分块都是如此。
在最常见的情况下,一旦数据在准备写入磁盘的事务组中,OpenZFS就会确认收到数据。这工作得很好——直到底层硬件无法跟上写请求。虽然你不能在饱和的SATA-I驱动器上的千兆线上记录每个数据包,但有些人坚持要尝试。
当存储提供程序开始落后于写入请求时,OpenZFS会人为地延迟确认收到数据。在写请求得到确认之前,请求程序将不会继续。它会挂起几毫秒,如果需要,还会挂起更长时间。实际上,当程序对内核施加过大的压力时,内核会进行反击。
单行DTrace脚本可以识别您的系统是否延迟写入。
xxxxxxxxxx# dtrace -n fbt::dsl_pool_need_dirty_delay:return'{ @[args[1] == 0 ? "no delay" : "delay"] = count(); }'在性能问题期间运行此脚本。让它收集数据“一段时间”——从几秒钟到几分钟不等。按 CTRL-C 退出。您将获得人为延迟写入的次数和未延迟写入的数量。如果只有一小部分写入延迟,那么性能问题就出在其他地方。
FreeBSD包含sysctls来调整延迟值,或调整延迟以使其更加一致,但如果你的硬件正在备份,你显然试图通过存储I/O填充太多数据。将你的写入拆分到更多设备之间,添加硬件或改进硬件。
任何在大型企业工作过的人都会受到维护窗口政策的影响,这些政策不太适合现代硬件。Lucas不止一次推迟在工作时间更换发生故障的热插拔硬盘,因为公司的维护政策宣布,周日早上是唯一可以进行此类维护的时间。如果你被这种政策所困,那么快速完成重组和清理工作至关重要,这样你才能继续你的一天。
擦洗(scrubs)和重组(resilvers)具有内置的速率限制,因此这些操作不会干扰正常操作。如果任何其他进程需要I/O,这些维护操作就会延迟。加速scrubs和resilvers意味着禁用该速率限制。
速率(rate)限制是一个sysctl,它为该进程的每个I/O操作之间提供了一定的时间间隔。此速率限制仅在磁盘未空闲时生效。
延迟以系统节拍为单位进行测量。一秒钟内的滴答数由 kern.hz sysctl控制。默认值为 1000 ,尽管许多虚拟机和笔记本电脑用户可能会将其设置为 100 ,以期提高性能。
“不闲着”(not idle)到底是什么意思?磁盘在等于 vfs.zfs.scan_idle sysctl的滴答(ticks)数内必须没有活动。
sysctl vfs.zfs.resilver_delay 控制resilvers的这种人为延迟,而 vfs.zfs.crub_delay 处理scrub。默认情况下,scrub在操作之间等待四个滴答声,而resilver则延迟两个滴答声。如果ZFS在每个I/O之间休眠四个刻度,则非空闲池上的擦除产生的最大IOPS将为250 IOPS(每秒1000个刻度除以每次操作的四个刻度)。其他进程有机会在这些暂停期间执行I/O。运行这些操作会进一步延迟清理或重新填充。
其他OpenZFS消费者,如illumos,通常每秒使用100个滴答声。因此,FreeBSD的延迟时间只有大多数其他操作系统的十分之一。这可能是一个疏忽,而不是一个深思熟虑的设计决定。
要消除清理或重新填充延迟,请将其设置为 0 ,使您的维护与任何其他过程具有相同的优先级。记住,这些延迟只有在池中有其他活动时才会触发。
您可以控制擦除向I/O调度程序发送的数据量。增加队列深度使ZFS I/O调度器有机会更有效地运行。sysctl vfs.zfs.top_maxinflight 控制擦除I/O队列深度。默认值为32,但有些人将其提高到 2048 。增加太多会耗尽系统RAM,因此在调整scrub时要密切监视系统。
每个txg都设置了它在重新分配上花费的最短时间。默认情况下,txg在重新分配方面花费的时间至少为3000毫秒。vfs.zfs.resilver_min_time_ms 控制事务组在重新分配I/O上花费的时间。当没有重新分配时,此值将被忽略。
无论你做什么,最终你都会达到硬件工作负载的极限。重新安排瓶颈就像重新安排游轮上的躺椅。在一些船上,你会为一场精彩的沙狐球游戏腾出空间。不过,如果这艘船是泰坦尼克号,再多的资源转移也无法让你漂浮。