我们都希望我们的存储快速、功能丰富、无限。
即使是最好的硬件也可能无缘无故地变慢。诊断工具有助于了解系统的性能。
FreeBSD包括检查通用磁盘和文件系统性能的工具以及ZFS特定工具。比如sysctl、vmstat,强烈建议安装zfs-stats。
在深入评估ZFS性能之前,先看看总体性能。
性能可以描述为“系统管理工作负载的能力”。
每个系统的工作负载略有不同,因此即使在执行看似相同任务的相同硬件之间,性能也会有所不同。
系统管理员最关心的是提高性能。这意味着识别和消除瓶颈。
普通计算机有四种基本资源:存储输入输出、网络带宽、内存和CPU。
最先饱和的那个资源就是整个系统的瓶颈。
提高性能需要识别和转移瓶颈。不同的工作负载可能有完全不同的瓶颈。
许多系统管理员需要将这四种资源中的一种交换为另一种,所谓系统调优也被称为重新安排瓶颈。
考虑一下ZFS压缩。它减少了系统写入磁盘和从磁盘提取的数据量。但压缩和解压缩会消耗CPU时间。大多数计算机的处理器性能是过剩的,因此在磁盘性能低下(I/O非常有限)的情况下,启用压缩会带来明显的性能提升。但在磁盘I/O多余处理器功率的系统上,启用压缩则会拖慢系统。
存储越复杂,就越能调整和转移存储瓶颈。
比如一台服务器有六个磁盘控制器,但所有的I/O都只用到其中一个。重新排列数据集,将负载分散到多个控制器上。也许某个磁盘饱和了?把负载分开。也许池中充满了写或读,或者两者兼有,可以尝试添加正确配置的SLOG和L2ARC以提供帮助。
不过,在做出任何更改之前,应调查瓶颈在哪里。
如果系统的特定工作负载受到CPU或内存的限制,购买更快的磁盘系统也无济于事。如果磁盘读取限制了服务器的性能,那么为ZIL添加快速SSD并不能提高性能。
系统管理中许多长期存在的问题之一是:是什么消耗了磁盘带宽?ZFS池擦除可能会影响其他操作,但在日常使用中,ZFS不会为这个问题创建任何新的答案。使用top -m io来识别最频繁使用磁盘的进程,然后确定最活跃的进程是否应该如此繁忙。
如果你的性能与预期不符,请记住,存储系统的性能仅与其最慢的组件一样好。有可能是电缆破旧造成性能变差。SATA端口倍数会根据连接的驱动器数量成比例地降低性能。仅仅因为你可以将某些硬件组件连接在一起,并不意味着你应该这样做。
许多常见的存储性能调优建议适用于ZFS,就像它适用于任何其他文件系统一样。如果不需要知道文件上次使用的时间,可将atime属性设置为off。
ZFS设计用于处理大量磁盘空间。当空间使用量达到80%以上,性能将会下降。这是ZFS组合方式固有特性。在几乎满的池上释放空间可以缓解大多数ZFS问题。
ZFS设计用于64位系统,强烈不建议在32位FreeBSD上使用ZFS。
FreeBSD的vmstat可以快速识别系统是在等待处理器、存储还是内存。
zpool iostat可以查看池的性能。
zpool的iostat组件提供了池在特定时刻的运行快照。要查看自系统启动以来池上的平均活动,可以运行zpool iostat:
xxxxxxxxxx
# zpool iostat
capacity operations bandwidth
pool alloc free read write read write
----- ----- ----- ----- ----- ----- -----
work 2.21G 1.81T 2 402 36.2K 24.0M
zroot 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 bandwidth
pool alloc free read write read write
----- ----- ----- ----- ----- ----- -----
work 3.37G 1.81T 14 107 146K 900K
zroot 15.5G 905G 3 2 32.9K 12.7K
----- ----- ----- ----- ----- ----- -----
work 3.37G 1.81T 0 0 0 0
zroot 15.5G 905G 0 0 0 0
----- ----- ----- ----- ----- ----- -----
输出的第一组数据是自系统启动以来的平均活动。第二个以及后面的条目给出的是当前值,每组数据之间以虚线分割。
如果指定了单独的池,则不显示用于分割的虚线:
xxxxxxxxxx
# zpool iostat work 2
capacity operations bandwidth
pool alloc free read write read write
----- ----- ----- ----- ----- ----- -----
work 3.35G 1.81T 15 104 148K 897K
work 3.35G 1.81T 0 616 2.25K 3.74M
work 3.35G 1.81T 1 553 5.74K 2.78M
work 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 bandwidth
pool 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 bandwidth
pool 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操作比第二镜像多。同样,单个磁盘编号加起来并不等于设备中的操作总数,但它们按比例是准确的。
现在我们看到池的工作情况了,接下来讨论一些可以改变池性能的功能。
文件系统的工作是根据请求提供存储的数据。ZFS进一步深化了这一理念,准备提供您即将要求的数据。这发生在两个层面上:每个VDEV(per-VDEV)和每个文件(per-file)。
FreeBSD默认情况下不启用每个VDEV预取,仅启用每文件预取。
per-VDEV预取是为了使移动磁头变得有价值。
FreeBSD默认情况下不使用per-VDEV预取,但如果工作负载涉及复杂的元数据、复杂或大型的目录树或许多小文件,per-VDEV预取可能会有帮助。
每当ZFS从VDEV读取几个块时,它也会读取目标块之后的几个块,以查找元数据。找到的任何元数据都会被填充到一个特殊的per-VDEV预取缓存中。
请求程序很有可能返回并请求该元数据。每次程序请求预取元数据时,ZFS都会从缓存中提供它,范围物理VDEV,并预取更多块。
per-VDEV预取的块会进入一个简单的滚动式最近最少使用(Last Recently Used)的缓存,而不是ARC。
如果这些块未被调用,它们很快就会被丢弃。VDEV缓存的大小等于VDEV中存储提供者的数量乘以可调的vfs.zfs.vdev.cache.size值。
FreeBSD默认将vfs.zfs.vdev.cache.size的值设置为0,因此不使用缓存。通过在/boot/loader.conf中设置该参数来启用缓存,常见值为10MB:
xxxxxxxxxx
vfs.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在缓存中找到某些内容的频率。
在有和没有per-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感觉更具响应性。这通常被称为ZFS的智能预取(intelligent prefetch),有时只是预取。虽然文件级预取可能看起来并不十分复杂,但大多数文件系统都无法管理它。
文件级预取会增加ARC的大小。FreeBSD会自动禁用RAM小于4 GB的主机上的预取,并自动启用4 GB或更多的主机。您可以通过在/boot/loader.conf中将可调vfs.zfs.prefetch_diable设置为1来覆盖这一点。
预取可能会在承载数十万(或更多)小文件(如64KB及以下)的系统上造成问题。您需要禁用此类主机的文件级预取。然而,这些系统相当罕见。
通常情况下,如果您的系统有足够的内存来支持文件级预取,则可以提高性能。您可以启用和禁用预取以测试性能,但在几乎所有情况下,预取都是有帮助的。
txg——transaction group,事务组,是以有序方式写入磁盘的单个数据块(a single lump of data)。
一个事务组可以包含来自许多不同程序的许多块(blocks)。如果整个事务组未成功写入磁盘,则整个组将被取消。
可以控制系统写入事务组的频率及其最大大小。
如果没有其他因素触发将txg写入磁盘,ZFS会每隔几秒写入一次,可用sysctl vfs.zfs.txg.timeout查看具体数据,默认为5秒。最坏的情况是,每5秒就有一个待处理的数据被写入磁盘。
对于大多数系统来说,每5秒写一次是可以的。当待处理的事务组被压缩时,像top这样的程序可能会每5秒显示一次CPU活动。
很少会将timeout时间减少到5秒以下。
然而对于某些系统而言,增加值可能是有意义的。如果在低负载虚拟机上运行ZFS,可能会将txg timeout设置为15左右。降低事务写入频率不会提高此特定虚拟机的性能,但会改善该管理程序上运行的其他VM的硬件访问。为该主机上的所有虚拟机提供类似的低设置将全面提高虚拟机的性能,但一个自私或高负载的虚拟机可能会吞噬其中的许多收益。(然而,这可能正是你想要实现的。)
在共享读写带宽的典型硬件上,增加超时时间可能会在大多数情况下提高读取性能,但会在写入过程中降低读取性能。
如果定时器到期,系统没有等待写入磁盘的事务,ZFS将不会写入任何数据。ZFS不会仅仅为了有一个事务组而写一个空事务。但是,它仍然会增加事务组计数。
将事务组超时设置为小于五秒会违反I/O调度程序和写限制。对我们大多数人来说,五秒是最小的合理值。
一个足够大的txg在超时之前被提交到磁盘。
FreeBSD在启动时根据主机中的内存量和可调的vfs.zfs.dirty_data_max_percent自动调整事务组的最大大小,默认值为10;最大为4GB,由vfs.zfs.dirty_data_max_max控制。一旦事务组使用了10%的系统RAM,它就会被写入磁盘。
系统启动后,可以使用sysctl vfs.zfs.dirty_data_max更改事务组的最大值,以字节为单位。
是否更改事务组大小,取决于系统将10%的RAM写入磁盘需要多久,以及这种情况发生的频率。
大多数主机的RAM远远超过其I/O吞吐量。试图在五秒钟内将十分之一的RAM写入磁盘将是一场灾难。
32GB的RAM,十分之一是3.2GB,写入磁盘需要20多秒。如果此主机在不到标准的5秒txg超时时间内生成了3.2GB的磁盘活动,则机器将很快陷入不可用状态。
如果系统具有高性能磁盘阵列,且具有巨大的吞吐量和经济规模的RAM堆,增大txg最大值会很有用。
在写入周期(write cycles)内,为了尽快完成写入,磁盘的大部分读取工作会暂停。写入完成后,读取工作继续。类似的交错工作负载通常会提高性能。
利用对工作负载的了解,可以决定是减少较大事务组的频率还是增加刷新较小事务组的次数。当在同一池中批量复制数据时,可以将txg增加到24GB,超市时间增加到30秒,性能可提高25%。
如果试图调整事务组的大小和周期,应当先查询事务组有多大以及它们提交到磁盘需要多长的时间。以下示例讨论用于测量这两个数据的DTrace脚本。
要测量每个txg中的数据量,可使用以下名为dirty.d脚本。(Dirty data在内存中,等待写入磁盘)
xxxxxxxxxx
txg-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);
}
运行这个脚本,测量指定的池:
xxxxxxxxxx
# dtrace -s dirty.d zroot
dtrace: script 'dirty.d' matched 2 probes
CPU 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,更改事务组之间的间隔,将看到事务组的大小发生了变化。
以下duration.d脚本显示了每个事务组完成所需的时间:
xxxxxxxxxx
<pre>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);
}</pre>
按上一个脚本方式运行它:
xxxxxxxxxx
# dtrace -s duration.d zroot
dtrace: script 'duration.d' matched 2 probes
CPU 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
如果看到事务组正在反弹到最大的txg大小,可能需要增加txg大小或减少事务组之间的时间。
以下脚本可以同时测量这两个指标:
xxxxxxxxxx
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的旧版本中,写入限制导致了非常不规则的性能。FreeBSD10和更新版本中的写节流算法工作得更顺利。
可以通过下面讨论的I/O调度程序对其进行调优。
并非所有硬件都是平等的。FreeBSD的默认设置相当通用。
对于拥有几十个或几百个具有非长特定工作负载的磁盘,可以通过调整调度来调整性能。
调度I/O允许调整延迟(latency)和吞吐量(throughput)。
吞吐量(throughput)是指可以从存储设备读取和写入的数据量。
SATA-3的6.0GB/s标称速度指的就是吞吐量。
延迟(latency)是系统为这些请求提供服务所需的时间长度。
复杂的存储架构、过高的并发存储都会导致存储延迟。
标称的IOPS(每秒IO操作)往往并不能反映驱动器真实的处理能力。
ZFS I/O调度旨在平滑延迟。这可能会降低吞吐量,但会使整体体验更加一致。通常,通过更改调度,可以在不引入太多延迟的情况下提高性能。
ZFS调度使围绕I/O队列构建的。
以下是一个测试延迟和吞吐量的DTrace脚本:
xxxxxxxxxx
#pragma D option quiet
inline 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);
}
在系统正常负载时多运行这个脚本几次,可以获得延迟和吞吐量的准确基线:
xxxxxxxxxx
# dtrace -s rw.d -c 'sleep 30'
…
avg latency stddev iops throughput
write 1362us 8378us 68/s 4620k/s
read 6943us 5839us 2/s 46k/s
一般来说,性能调优的目标是在不使其他数字过大的情况下提高你关心的数字。
使用每种类型允许的最大读写次数,在运行之间将其增加20~100%。确保这些值不会超过VDEV的限制,或者也可以选择使用最小值。
stddev(标准偏差)列尤其值得注意。可能会在优秀的吞吐量情况下发现延迟很大。如果一些读写需要5秒才能完成,那么大吞吐量将无法持续。
调整读取性能时,需要注意ARC。如果继续访问同一文件,内核将使用内存中的副本,而不是从磁盘重新读取。为了获取正确的测试结果,必须从ARC中刷新常用文件。卸载并重新载入读取密集型数据集,或者重启计算机。
注意,性能调优和测试是入侵性的。大多数人不愿意做这件事是有原因的。
好消息是,ZFS在默认设置下表现良好。
ZFS将I/O流量分为五个队列:同步读取、异步读取、同步写入、异步写入和scrubs。
每个都有一对相关的sysctls,用于控制每个存储提供程序上可以同时活动的该类型的最大和最小未完成请求。
synchronous reads(同步读取)是指应用程序现在正在请求数据。应用程序,可能还有用户,都只能等待数据,然后才能继续工作。同样的,synchronous writes(同步写入)请求立即写入数据。数据库和其他希望确保在数据安全地存储在磁盘上之前不会执行“下一步”的应用程序请求同步写入。
在ZFS中,同步写入会尽快完成。这就是SLOG发挥作用的地方,它是一种快速的专用设备,可以临时存储同步写入,比正常存储更快。
asynchronous reads(异步读取)不太重要,主要由ZFS的预取功能组成,该功能在需要时从磁盘加载数据。
当ZFS开始读取这些数据并使其可用时,应用程序将收到通知,而不是显式等待它。
异步写入的工作方式类似。
然而,最大值和最小值并不同时适用。
最大值适用于一组条件,而最小值适用于不同的条件。对于一种写入类型,其中一些值是相同的——例如同步读取的最大值和最小值都设置为10。这并不冲突,只是针对不同情况的不同设置。
同步读取包括程序请求的数据。在文本编辑器中调用文件是同步读取。使用sysctls vfs.zfs.vdev.sync_read_max_active和vfs.zfs.vdev.sync_read_min_active控制这些。
用户最有可能注意到同步读取的延迟。如果用户试图打开一个文件,并且需要5秒而不是50毫秒,则表明系统很慢。
预取请求时异步读取。如果程序读取了文件的第一块,ZFS会认为文件的其余部分也可能会被读取,于是将其预取。使用sysctls vfs.zfs.vdev.async_read_max_active和vfs.zfs.vdev.assync_read_min_active控制未完成的异步读取请求的数量。默认值最大为3,最小为1。
限制异步操作数量的目的是确保新的同步操作不会出现在一堆不太重要的读取后面。当驱动器上未完成的操作数量降至最低值以下时,ZFS会向队列添加更多工作,最重要的操作会首先添加到队列中。队列中的操作顺序不会改变。
sync writes用于程序使用fsync系统调用的情况。
这些请求直接发送到ZIL,可以在数据存储提供程序上,也可以在单独的SLOG上。
这些写入是最重要的操作,因为调用应用程序正在等待操作完成,然后再继续。将此值调整得太低会降低吞吐量并增加延迟。
ZFS中的写入通常是批处理的,因此利用写头处于正确位置的优势一次写入尽可能多的数据。但是,如果要排队的操作数量太多,同步读取必须等待已经排队的写入完成,这可能会对系统响应性产生负面影响。sysctls vfs.zfs.vdev.sync_write_maxactive和vfs.zfs.vdev.ssync_write_min_active控制每个提供程序在任何时候可能挂起的这些请求的数量。两者都默认为10。
async writes是不经过ZIL的正常流量。async写入位于txg中,然后被批量提交。sysctls vfs.zfs.vdev.async_write_maxactive和vfs.zfs.vdev.assync_write_min_active控制单个存储提供程序上可以同时活动的未完成异步写入请求的数量。默认最大值为10,最小值为1。
scrub进程有自己的队列,控制池上可以同时活动的未完成I/O请求的数量。默认值为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对程序所需数据的猜测不会淹没程序主动请求的流量。
但是,您无法通过调整这些设置来提高硬件速度。现有的限制可能会使5400 RPM SATA硬盘饱和。高端存储设备,您有单独的读写硬件,可能可以处理稍高的值。即便如此,你很快就会达到最大容量。
对于固态存储,如SSD,其IOPS数量可能比旋转磁盘大得多,可以通过增加所有这些可调参数来提高性能。为了让设备发挥最大性能,您必须给它足够的工作来保持忙碌,但同时,不要做太多工作,以至于需要太长时间才能处理添加到队列末尾的重要请求。使用本章前面提供的DTrace脚本来测量负载下的延迟,并根据需要进行调整。
不过,如果你有超级硬件,把限制提高得太高,你会达到每VDEV的限制,改变一切。
ZFS有两个调度系统:一个用于当系统允许许多未完成的I/O请求时使用,另一个用于系统不允许时使用。多少是多?这取决于您的VDEV和主机。
sysctl vfs.zfs.vdev.max_active给出了zfs更改调度算法的标志级别。FreeBSD的默认值是1000。对于大多数主机,在默认配置中,这意味着在切换算法之前,VDEV中最多可以有28个磁盘。
达到极限意味着ZFS改变了它的调度方式。它不使用最大值作为上限,而是允许每个磁盘有等于最小值的未完成请求数量。
这意味着每个磁盘至少有25个未完成的请求。默认情况下,系统最多可以支持1000个未完成的请求,因此任何其他请求都会按优先级顺序分配。
如果单个VDEV中有40多个存储提供程序,即使是最小值也会超过系统上允许的总数。更改vfs.zfs.vdev.max_active以允许更多请求,或者最好重新排列vdev以包含足够数量的存储提供程序。
异步写入的工作方式略有不同。当系统大部分时间处于空闲状态,没有太多要写的内容时,系统会创建一个异步写请求。(从技术上讲,最小值是sysctl vfs.zfs.vdev.async_write_min_active,但实际上没有理由将其设置为1以上。)内存中等待写入磁盘的数据称为“脏数据”。随着事务组大小和频率的增加,zfs会安排越来越多的并发写入。当系统达到vfs.zfs.vdev.async_write_maxactive sysctl定义的最大写入请求数时,它会开始人为地减缓对写入请求的响应。
系统vfs.zfs.vdev.async_write_active_min_dirty_percent和vfs.zfs.vdev.assync_write_active _max_dirty_per控制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的脏数据,正负150。
也许你的vdev可以处理排队到磁盘的10多个命令,所以你想增加vfs.zfs.vdev.async_write_maxactive sysctl。将此sysctl增加到超出硬件可以处理的范围会导致延迟增加,因此请务必监控正常负载下任何更改的影响。更改未完成写入请求的最大数量会影响系统创建写入请求的速度,但不会影响百分比。
给出的百分比适用于大多数负载,但如果您的系统延迟波动,您可能会调查操作数量和延迟量。
xxxxxxxxxx
#pragma D option aggpack
#pragma D option quiet
fbt::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调谐,你必须转动这些刻度盘,看看它们在你的特定硬件上做了什么。
要求内核写入磁盘的程序在内核确认写入请求之前不会继续。也有例外——在多线程程序中,写请求可能只阻塞一个线程。在多个并发进程中运行的应用程序,如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填充太多数据。将你的写入拆分到更多设备之间,添加硬件或改进硬件。
擦洗和重组具有内置的速率限制,因此这些操作不会干扰正常操作。加速scrub和resilver意味着禁用该速率限制。
如果任何其他进程需要I/O,这些维护操作就会延迟。
速率限制是一个sysctl,它为该进程的每个I/O操作之间提供了一定的时间间隔。此速率限制仅在磁盘未空闲时生效。
延迟以系统节拍为单位进行测量。一秒钟内的滴答数由kern.hz sysctl控制。默认值为1000,尽管许多虚拟机和笔记本电脑用户可能会将其设置为100,以期提高性能。
“不闲着”到底是什么意思?磁盘在等于vfs.zfs.scan_idle sysctl的滴答数内必须没有活动。
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上花费的时间。当没有重新分配时,此值将被忽略。
无论你做什么,最终你都会达到硬件工作负载的极限。重新安排瓶颈就像重新安排游轮上的躺椅。在一些船上,你会为一场精彩的沙狐球游戏腾出空间。不过,如果这艘船是泰坦尼克号,再多的资源转移也无法让你漂浮。