学习ZFS的系统管理员通常会花时间对ZFS的空间使用感到困惑。将池存储、数据集、快照和克隆结合起来,使ZFS空间利用变得非常复杂,需要 《FreeBSD Mastery:ZFS》中的整整一章。当你开始摆弄数据库和zvol的 recordsize 和 volblocksize 属性时,空间利用率可能会直接进入阴阳魔界(Twilight Zone)。
volblocksize 属性给出了zvol上存储块的大小。块大小应该表示zvol上使用的文件系统的块大小。默认的 volblocksize 为8KB,可以容纳两个4KB或16512字节的文件系统扇区。
recordsize 属性给出了ZFS文件系统数据集中逻辑块的最大大小。recordsize 大小为128 KB,在4 KB扇区的磁盘上为32个扇区,在512字节扇区的磁盘中为256个扇区。2015年,随着 large_blocks 功能标志的引入,recordsize 大小增加到1 MB。许多数据库引擎更喜欢较小的块,如4KB或8KB。更改专用于此类文件的数据集上的 recordsize 是有意义的。即使您不更改 recordsize ,ZFS也会根据需要自动调整记录大小。写一个16KB的文件应该只占用16KB的空间(加上元数据和冗余空间),而不是浪费整个128KB的记录。
块大小和RAID-Z之间的交互意味着服务器的磁盘可能会突然填满,即使它们只有您期望的25%的数据。
了解原因需要更深入地了解ZFS如何分配块。
第九章:调谐ZFS 条带分配镜像和条带RAID-Z1RAID-Z2RAID-Z3条带的镜像更改分配大小建议数据库和 ZFS所有数据库MySQL – InnoDB/XtraDBMySQL – MyISAMPostgreSQL调整文件大小小文件大文件两个世界中最糟糕的: Bittorrent短行程
条带由物理磁盘(或其他提供商,如GELI)上的扇区组成。如果您的磁盘有4KB扇区,分配128KB需要32个物理扇区。
Zpools将所有奇偶校验信息存储在磁盘扇区或块中。每个奇偶校验级别都需要每个条带对应一个块。RAID-Z3池需要三个块来为分配的每个磁盘块提供奇偶校验信息。
RAID-Z池总是以奇偶校验级别加1的倍数分配块。也就是说,RAID-Z1一次分配两个块,RAID-Z2一次分配三个块,而RAID-Z3一次分配四个块。这有助于ZFS防止碎片化,并降低浪费更多空间的风险。如果一个条带不需要那么多空间,ZFS会将其填充(pads it out)以填充(fill)整个分配。对于通常的条带大小,每个文件多出一两个扇区并不重要。RAID-Z以一致大小的块进行分配,以便在释放块时可以轻松重用。
考虑在RAID-Z2上分配8KB的空间。8KB只需要四个扇区,而RAID-Z2只分配三的倍数,因此它有六个块。您可以擦除该文件,并在相同的扇区中分配一个4KB的文件。这个4KB的文件只需要三个块。如果RAID-Z没有填充到N+1的倍数,那么在4KB文件和下一个文件之间就会有一个未使用的磁盘块。这个孤立的街区,一个孤立的区域,永远无法使用。
写、删、写一堆不同大小的文件,很快你的磁盘就有了一大堆可用空间,但都是不可用的 one-block 块(chunks)。你的磁盘将瘫痪。
每个文件还需要其他元数据将其附加到ZFS树,给出包含该文件的块及其哈希值等。每个这样的元数据块都包含许多文件的信息,在本次讨论中可以忽略。
这一切似乎都很简单,也不危险,但让我们看看这些事实是如何与使用4KB和512字节扇区的文件系统交互的。在所有这些示例中,我们正在为zvol或数据库编写一个8KB的块。
镜像和条带需要元数据块将其附加到ZFS树,但它们不需要任何额外的冗余块。8KB文件使用两个4KB或16个512B的磁盘扇区。
在一个有4KB块的RAID-Z1池中,8KB的数据需要两个块。我们还需要一个奇偶校验数据块,总共三个块。RAID-Z1池以2的倍数(奇偶校验级别加1)分配块,因此四舍五入为4。假设您有一个三驱动器RAID-Z1。如果磁盘有4KB的块,这意味着您只能将物理磁盘填满一半的数据,而不是三磁盘RAID-Z1所期望的三分之二的数据。衬垫吃掉了剩下的空间。
如果这个相同的三驱动器RAID-Z1池使用具有512字节块的磁盘,那么相同的8KB需要16个块。我们需要一个奇偶校验块,总共17个块。分配必须能被2整除,因此池分配一个块用于填充,使总数达到18个块。您可以将此池填满88%的8KB块。
我们的8KB数据再次占用RAID-Z2池上的两个块,其中有4KB块。我们需要两个块用于奇偶校验数据,总共四个块。RAID-Z2池以三的倍数(奇偶校验级别加一)分配块,因此四舍五入为六个块。在四驱动器RAID-Z2上,您希望能够将磁盘填满一半的真实数据。不过,如果你用8KB的文件填充池,在填充占用你的空间之前,你只会得到大约33%的空间。
在一个有512字节块的池中,8KB的数据得到16个块。两个奇偶校验数据块使我们达到18个块。ZFS一次保留三个块,所以我们根本不需要任何填充。你可以用8KB的块完全填满这个池。
在具有4KB文件系统扇区的ZFS上,数据本身需要两个扇区。此池使用三重奇偶校验,因此您需要三个磁盘扇区来存储奇偶校验数据。这总共是五个扇区。ZFS仅在奇偶校验级别加1的块中分配扇区。RAID-Z3允许您以4的倍数分配扇区,因此ZFS为这8KB的数据分配了8个扇区。八个扇区为32KB。您不能将此zvol填满超过25%。你可以完全填满一个200 GB的zvol,只要池中有800 GB的物理空间。
在具有512字节文件系统扇区的ZFS上,数据本身需要16个扇区。它还需要另外三个扇区用于奇偶校验数据,总共需要19个扇区。由于分配必须是4的倍数,因此此写入将分配20个文件系统扇区。20个扇区为10KB,效率为80%。
条带镜不需要任何奇偶校验数据。ZFS将数据批量复制到多个存储提供商。条带镜像不会填充数据以适应分配大小。条带镜像是存储数据最有效的地方,但它的数据保护模型与RAID-Z不同。
三向镜像提供了与RAID-Z2类似的数据保护——您可以在不丢失任何数据的情况下丢失每个VDEV的两个驱动器——但您只获得了总空间的33%。
将 recordsize 或 volblocksize 更改为4KB会更改计算。较小的条带大小意味着更多的奇偶校验,这可能意味着有更多的空填充。
看看我们在RAID-Z3上的8KB写入示例。它被分成两条条纹。在具有4KB块的zvol上,每个条带需要四个扇区,再次为您提供25%的空间效率。对于512字节的块,每个条带需要11个块,四舍五入到12。您将获得约66%的空间效率。
条带大小(volblocksize)、奇偶校验和填充之间的交互是为什么您不必对文件系统数据集执行这些计算的原因。128KB的条带大小将分配填充减少到仅仅是噪声。
在为数据库或zvol设置系统之前,请仔细考虑数据存储池下的存储。对于常见的 recordsize 设置为8KB,我们强烈建议按顺序:镜像条带池 > 四驱动器RAID-Z2池 > 三驱动器RAID-Z池。
如果您使用 volblocksize 为4KB的zvols来支持虚拟机,您的选择将更加有限。镜像的条带池允许您实现最大的空间效率,而所有其他大小的条带池都会因填充而导致至少一些空间损失。镜像还具有比RAID-Z更大的IOPS优势。
许多ZFS功能对数据库非常有利。每个DBA都希望快速、简单、高效的复制、快照、克隆、可调缓存和池存储。虽然ZFS被设计为通用文件系统,但您可以对其进行调优以使数据库运行。
数据库通常由多种类型的文件组成,由于每种类型都有不同的特征和使用模式,因此每种类型需要不同的调优。我们将特别讨论MySQL和PostgreSQL,但这些原则适用于任何数据库软件。
您可以通过 recordsize 属性对数据库执行的最重要的调优是数据集块大小。任何可能被覆盖的文件的ZFS recordsize 都需要与应用程序使用的块大小相匹配。
调整块大小也可以避免写入放大(write amplification)。当更改少量数据需要写入大量数据时,会发生写入放大。假设您必须在一个128 KB的块中间更改8 KB。ZFS必须读取128 KB,修改其中的8 KB,计算新的校验和,并写入新的128 KB块。ZFS是一个写时复制的文件系统,因此它最终会写入一个全新的128KB块来更改8KB。你不想那样。现在将其乘以数据库的写入次数。写入放大会削弱性能。
虽然这种优化对我们许多人来说不是必需的,但对于高性能系统来说,它可能是无价的。它还可能影响SSDs和其他基于闪存的存储的寿命,这些存储可以在其寿命内处理有限的写入量。当然,不同的数据库引擎并不容易做到这一点,每个引擎都有不同的需求。日志(journals)、二进制复制日志(binary replication logs)、错误和查询日志(error and query logs)以及其他杂项(miscellaneous)文件也需要不同的调优。
在创建 recordsize 较小的数据集之前,请确保您了解VDEV类型和空间利用率之间的相互作用。在某些情况下,具有较小512字节扇区大小的磁盘可以提供更好的存储效率。完全有可能,您最好为数据库设置一个单独的池,为其他文件设置一个主池。
对于高性能系统,请使用镜像而不是任何类型的RAID-Z。是的,为了提高弹性,您可能需要RAID-Z,选择您的痛苦。
在数据库上启用lz4压缩实际上可以减少延迟,这并不直观。压缩数据可以更快地从物理介质中读取,因为读取的数据更少,这可以缩短传输时间。使用lz4的早期中止功能(early abort feature),最坏的情况只比选择不压缩慢几毫秒,但好处通常相当显著。这就是为什么ZFS对其所有元数据和L2ARC使用lz4压缩的原因。在不久的将来,当压缩ARC功能登陆OpenZFS时,对数据集启用压缩也将允许更多数据保存在ARC中,ARC是ZFS中最快的缓存。
在Delphix进行的一项生产案例研究中,一台具有768 GB RAM的数据库服务器从使用90%以上的内存来缓存数据库变成了仅使用446 GB来缓存1.2 TB的压缩数据。压缩内存缓存可以显著提高性能。由于机器无法支持更多的RAM,压缩是唯一的改进方法。
ZFS元数据也会影响数据库。当数据库快速更改时,为每次更改写出两到三个元数据副本可能会占用备份存储的大量可用IOPS。通常,与默认的128KB记录大小相比,元数据的数量相对较小。不过,数据库在记录大小较小的情况下工作得更好。保留元数据的三个副本可能会导致与将实际数据写入池一样多的磁盘活动,甚至更多。
较新版本的OpenZFS还包含一个 redundant_metadata (冗余元数据)属性,默认为 all 。这是ZFS早期版本的原始行为。但是,此属性也可以设置为 most ,这会导致ZFS减少其保留的某些类型元数据的副本数量。
根据您的需求和工作负载,允许数据库引擎管理缓存可能会更好。ZFS默认在ARC中缓存数据库中的大部分或全部数据,而数据库引擎保留自己的缓存,导致浪费的双重缓存。将 primarycache 属性设置为 metadata ,而不是默认的 all ,这会告诉ZFS避免在ARC中缓存实际数据。 secondarycache 属性类似地控制L2ARC。
根据访问模式和数据库引擎的不同,ZFS可能已经更高效了。使用zfs工具包中的 zfsmon 等工具来监控ARC缓存命中率,并将其与数据库内部缓存的命中率进行比较。
一旦压缩ARC功能可用,考虑减小数据库内部缓存的大小,让ZFS处理缓存可能是明智的。ARC可能能够在相同数量的RAM中容纳比数据库多得多的数据。
InnoDB成为MySQL 5.5中的默认存储引擎,与之前使用的MyISAM引擎具有显著不同的特性。Percona的XtraDB也被MariaDB使用,与InnoDB类似。InnoDB和XtraDB都使用16KB的块大小,因此包含实际数据文件的ZFS数据集应该将其 recordsize 属性设置为匹配。我们还建议使用MySQL的 innodb_one_file_per_table 设置,将每个表的innodb数据保存在单独的文件中,而不是将其全部分组到一个ibdata文件中。这使得快照更有用,并允许更具选择性的恢复或回滚。
在不同的数据集上存储不同类型的文件。数据文件需要16KB的块大小、lz4压缩和减少的元数据。您可能会看到仅缓存元数据带来的性能提升,但这也会禁用预取。尝试一下,看看你的环境是如何表现的。
xxxxxxxxxx# zfs create -o recordsize=16k -o compress=lz4 -o redundant_metadata=most -o primarycache=metadata mypool/var/db/mysql主MySQL日志最好用 gzip 压缩,不需要在内存中缓存:
xxxxxxxxxx# zfs create -o compress=gzip1 -o primarycache=none mysql/var/log/mysql复制日志在lz4压缩下效果最佳:
xxxxxxxxxx# zfs create -o compress=lz4 mypool/var/log/mysql/replication然后在 /usr/local/etc/my.cnf 中进行设置,告诉MySQL套用这些数据集:
xxxxxxxxxxdata_path=/var/db/mysqllog_path=/var/log/mysqlbinlog_path=/var/log/mysql/replication现在,可以初始化数据库并开始加载数据了。
许多MySQL应用程序仍然使用旧的MyISAM存储引擎,这基本上是旧习惯的惯性所致。
MyISAM使用8KB块大小。数据集记录大小应设置与之匹配。数据集布局应该与InnoDB的布局相同。
如果调整得当,ZFS可以支持非常大和快速的PostgreSQL系统。在创建所需的数据集之前,不要初始化数据库。
PostgreSQL默认对所有内容使用8KB存储块。如果更改PostgreSQL的块大小,则必须更改数据集大小与其匹配。
在默认的FreeBSD安装中,PostgreSQL位于/usr/local/pgsql/data中。对于大型安装,实际上可能有一个单独的数据池。在以下示例中,我们有一个名为 pgsql 池供PostgreSQL使用:
xxxxxxxxxx# zfs set mountpoint=/usr/local/pgsql pgsql# zfs create pgsql/data现在我们遇到了一个鸡和蛋的问题。PostgreSQL的数据库初始化例程希望创建自己的目录树,但我们希望特定的子目录有自己的数据集。最简单的方法是让PostgreSQL初始化,然后创建数据集并移动文件。
xxxxxxxxxx# /usr/local/etc/rc.d/postgresql oneinitdb初始化例程创建数据库、视图(views)、模式(schemas)、配置文件以及高端数据库的所有其他组件。现在,我们为特殊部件创建数据集。
PostgreSQL将数据库存储在 /usr/local/pgsql/data/base 中。预写日志(Write Ahead Log——WAL)位于 /usr/local/pgsql/data/pg_xlog 中。把这两个都移开:
xxxxxxxxxx# cd /usr/local/pgsql/data# mv base base-old# mv pg_xlog pg_xlog-old这两个都使用8KB的块大小,我们希望分别对它们进行快照,因此为每个创建一个数据集。与MySQL一样,告诉ARC只缓存元数据。还要告诉这些数据集使用 logbias 属性将吞吐量与延迟进行偏差:
xxxxxxxxxx# zfs create -o recordsize=8k -o redundant_metadata=most \-o primarycache=metadata logbias=throughput pgsql/data/pg_xlog# zfs create -o recordsize=8k -o redundant_metadata=most \-o \primarycache=metadata logbias=throughput pgsql/data/base然后将原始目录的内容复制到新数据集中:
xxxxxxxxxx# cp -Rp base-old/* base# cp -Rp pg_xlog-old/* pg_xlog现在可以启动PostgreSQL。
ZFS被设计成一个好的通用文件系统。如果你有一个ZFS系统作为典型办公室的文件服务器,你真的不必调整文件大小。不过,如果你知道你将拥有多大的文件,你可以进行更改以提高性能。
在没有SLOG的系统中高速创建许多小文件时,ZFS会花费大量时间等待文件和元数据完成稳定存储的刷新。
如果您愿意冒丢失在最后五秒内创建的任何新文件的风险(如果您的 vfs.zfs.txg.timeout 更高,则可能会丢失更多),将 sync 属性设置为 disabled 将告诉zfs将所有写入视为异步。即使应用程序要求在文件安全之前不要被告知写入已完成,ZFS也会立即返回并写入文件以及下一个定期计划的txg。
高速SLOG允许您同步快速地存储这些小文件。
ZFS最近通过 large_block 功能增加了对大于128KB的块的支持。如果你要存储许多大文件,当然要考虑这一点。默认的最大块大小为1 MB。
理论上,您可以使用大于1 MB的块大小。然而,很少有系统对此进行了广泛的测试,与内核内存分配子系统的交互也没有在长时间使用下进行测试。你可以尝试非常大的记录大小,但一定要在一切出错时提交一份错误报告。sysctl vfs.zfs.max_recordsize 控制最大块大小。
一旦激活 large_blocks (或任何其他功能),不支持该功能的主机将无法再使用该池。通过销毁任何 recordsize 设置为大于128 KB的数据集来停用该功能。
存储系统难以平衡延迟和吞吐量。ZFS使用 logbias 属性来决定应该向哪个方向倾斜。ZFS的 logbias 默认为 latency (延迟),以便数据快速同步到磁盘,从而允许数据库和其他应用程序继续工作。在处理大文件时,将 logbias 属性更改为 throughput (吞吐量)可能会带来更好的性能。您必须进行自己的测试,并决定哪种设置适合您的工作负载。
Bittorrent将大文件的最坏部分和小文件的最差部分组合在一个方便的包中。Bittorrent的乱序写入模式会导致大量数据集碎片化。此外,如果数据集的 recordsize 不是16KB,Bittorrent的16KB块大小可能会导致写入放大。
这两个问题的最佳解决方案是实际拥有两个数据集。下载期间存储文件的数据集使用较小的记录大小。
xxxxxxxxxx# zfs create -o recordsize=16k -o redundant_metadata=most -o compress=off mypool/torrents/in-progress存储已完成种子的数据集应该具有更大的块大小。将文件从一个移动到另一个会对文件进行碎片整理,从而提高读取性能并避免池碎片。大多数torrent客户端支持使用单独的目录进行正在进行的下载,因此除了创建两个数据集外,这甚至不需要您采取任何行动。
如果您下载大型文件,如操作系统ISO,您还可以考虑使用1 MB的记录大小来进一步提高性能,并摊销元数据和冗余。
xxxxxxxxxx# zfs create -o recordsize=1m mypool/torrents告诉你的torrent客户端这些目录,你就可以开始了。
常规旋转的磁盘具有特定的特性,可能会导致性能不均匀。对磁盘开头的读取和写入可能比稍后在磁盘上的写入快得多(吞吐量更高)。常规旋转磁盘最慢的方面是寻道时间,即驱动器在要读取或写入的扇区上物理重新定位读/写头所需的时间。当数据分散在磁盘上时,这会显著降低性能。
Short Stroking(短行程)是只使用磁盘容量的一小部分的过程,通常是磁盘中速度最快的10-30%的分区。现在磁头只需要在磁盘表面的一小部分上移动,平均寻道时间将大大缩短。将来,如果需要更多的存储空间,可以简单地调整分区大小,ZFS将扩大池。请记住,在磁盘空间不足的情况下运行ZFS会增加碎片化,这可能比更高的平均寻道时间更糟糕。
现在,您拥有了以最佳方式配置数据集、池和硬件的工具。现在让我们来看看ZFS的一些更模糊的角落。