第九章:调谐

volblocksize属性给出了zvol上存储块的大小。块大小应该表示zvol上使用的文件系统的块大小。默认的volblocksize为8KB,可以容纳两个4KB或16512字节的文件系统扇区。

recordsize属性给出了ZFS文件系统数据集中逻辑块的最大大小。默认记录大小为128 KB,在4 KB扇区的磁盘上为32个扇区,在512字节扇区的磁盘中为256个扇区。2015年,随着large_blocks功能标志的引入,最大记录大小增加到1 MB。许多数据库引擎更喜欢较小的块,如4KB或8KB。更改专用于此类文件的数据集上的记录大小是有意义的。即使您不更改记录大小,ZFS也会根据需要自动调整记录大小。写一个16KB的文件应该只占用16KB的空间(加上元数据和冗余空间),而不是浪费整个128KB的记录。

块大小和RAID-Z之间的交互意味着服务器的磁盘可能会突然填满,即使它们只有您期望的25%的数据。

ZFS 条带分配

条带由物理磁盘(或其他提供者,例如GELI)上的扇区组成。如果磁盘是4KB扇区,分配128KB需要32个物理扇区。

zpool将所有奇偶检验信息存储在磁盘扇区或块中。每个奇偶检验级别都需要每个条带对应一个块。RAID-Z3池需要三个块来为分配的每个磁盘块提供奇偶检验信息。

RAID-Z池总是以奇偶检验级别加1的倍数分配块。也就是说,RAID-Z1一次分配两个块,RAID-Z2一次分配三个块,而RAID-Z3一次分配四个块。这有助于ZFS防止碎片化,并降低浪费更多空间的风险。

如果一个条带不需要那么多空间,ZFS会将其填充以填充整个分配。对于通常的条带大小,每个文件多出一两个扇区并不重要。RAID-Z以一致大小的块进行分配,以便在释放块时可以轻松重用 。

考虑在RAID-Z2上分配8KB的空间。8KB只需要4个扇区,而RAID-Z2只分配3的倍数,因此它有6个块。当删除了这个文件。并在相同的扇区中分配一个4KB的文件。这个4KB的文件只需要3个块。如果RAID-Z没有填充到N+1的倍数,那么在4KB文件和下一个文件之间就会有一个未使用的磁盘块。这是个孤立的区域,永远无法使用。

写、删、写一堆不同大小的文件,很快,磁盘就有了一大堆可用空间,但都是不可用的one-block块。磁盘将瘫痪。

每个文件还需要其他元数据将其附加到ZFS树,给出包含该文件的块及其哈希值等。每个这样的元数据块都包含许多文件的信息,在本次讨论中可以忽略。

这一切似乎都很简单,也不危险,但让我们看看这些事实是如何与使用4KB和512字节扇区的文件系统交互的。在以下所有示例中,我们正在为zvol或数据库编写一个8KB的块。

镜像和条带

镜像和条带需要元数据块将其附加到ZFS树,但它们不需要任何额外的冗余块。8KB文件使用两个4KB或16个512B的磁盘扇区。

RAID-Z1

在一个有4KB块的RAID-Z1池中,8KB的数据需要两个块,还需要一个奇偶检验数据块,总共三个块。

RAID-Z1池以2的倍数分配块,因此四舍五入为4。假设有一个三驱动器的RAID-Z1,如果磁盘有4KB的块,这意味着只能将物理磁盘填满一半的数据,而不是三磁盘RAID-Z1所期望的三分之二的数据。填充位吃掉了剩下的空间。

如果这个相同的三驱动器RAID-Z1池使用具有512B块的磁盘,那么相同的8KB需要16个块,加上一个奇偶校验块,总共17个块。分配必须能被2整除,因此池会分配一个块用来填充,总数达到18个块。可以将此池填满88%的8KB块。

RAID-Z2

8KB的数据占用了两个4KB的块,在RAID-Z2池中还会需要两个块用于奇偶检验,总共四个块。

RAID-Z2池以3的倍数分配块,因此四舍五入为六个块。如果用8KB的文件填充池,将只会得到大约33%的可用空间。

在一个有512B块的池中,8KB的数据需要16个块,两个奇偶检验块,总共18个块。ZFS一次保留三个块,所以根本不需要任何填充。可以用8KB的块完全填充整个池。

RAID-Z3

8KB的数据占用了两个4KB的块,RAID-Z3使用三重奇偶检验,因此需要三个磁盘扇区来存储奇偶检验数据,总共五个块。

RAID-Z3池以4的倍数分配块,因此ZFS为这8KB的数据分配8个扇区。8个扇区为32KB。所以磁盘的利用率可能只有25%。

在具有512B块的池中,数据本身需要16个扇区,加上3个用于奇偶检验的扇区,总共19个扇区,ZFS会以4的倍数分配扇区,因此它将占用20个扇区。磁盘的利用率可达到80%。

条带的镜像

条带化的镜像不需要任何奇偶检验。ZFS将数据批量复制到多个存储提供者。条带的镜像不会填充数据以适应分配大小。

条带的镜像是存储数据最有效的地方,但它的数据保护模型与RAID-Z不同。

三路镜像提供了与RAID-Z2类似的数据保护——可以在不丢失任何数据的情况下丢失每个VDEV的两个驱动器——但只获得33%的总空间。

更改分配大小

修改recordsize或volblocksize更改为4K会更改计算。较小的条带大小意味着更多的奇偶检验,这可能意味着可以获得更多的空填充。

以RAID-Z3上写入8KB为例,它被分为两个条带。在具有4KB块的zvol上,每个条带需要4个扇区,再次提供25%的空间效率。对于512B的块,每个条带需要11个块,四舍五入到12个,将获得约66%的空间效率。

条带大小(volblocksize)、奇偶检验和填充之间的交互是为什么你不必对文件系统数据集执行这些计算的原因。128KB的条带大小将分配填充减少到仅仅是噪声。

建议

在为数据库或zvol设置系统之前,应仔细考虑数据存储池下的存储。

对于常见的recordsize设置为8KB,我们强烈建议的优先顺序是:条带镜像池、四盘RAID-Z2池、三盘RAID-Z1池。

如果您使用volblocksize为4KB的zvols来支持虚拟机,您的选择将更加有限。镜像条带池允许您实现最大的空间效率,而所有其他大小的条带池都会因填充而导致至少一些空间损失。镜像还具有比RAID-Z更大的IOPS优势。

数据库和ZFS

许多ZFS功能对数据库非常有利。

DBA需要的是快速、简单、高效的复制、快照、克隆、可调缓存、池存储。

可以对ZFS进行调优以更好的地运行数据库。

数据库通常由多种类型的文件组成,由于每种类型都有不同的特征和使用模式,因此每种类型需要不同的调优。

以下使用场景仅涉及MySQL和PostgreSQL,但多数原则也适用于其他数据库软件。

通过recodesize属性对数据库执行的最重要的调优是数据集块大小。任何可能被覆盖的文件的ZFSrecodesize都需要与应用程序使用的块大小相匹配。

调整块大小也可以避免写入放大。当更改少量数据需要写入大量数据时,会发生写入放大。假设需要修改128KB的块中的8KB数据,ZFS需要读取这128KB数据,修改其中的8KB,计算新的校验和,并写入新的128KB块。ZFS是一个写时复制的文件系统,因此它最终会为了修改这8KB数据而写入一个全新的128KB块。现在将其乘以数据库的写入次数。写入放大会削弱性能。

虽然这种优化对很多人来说不是必须的,但对于高性能系统来说,它可能是无价的。它还可能影响SSD和其他基于闪存的存储的寿命,这些存储可以在其寿命内处理有限的写入量。

当然,不同的数据库引擎并不容易做到这一点,每个引擎都有不同的需求。日志、二进制复制日志、错误和查询日志以及其他杂项文件也需要不同的调优。

所有数据库

在数据库上启用lz4压缩实际上可以减少延迟,但这并不直观。

压缩数据可以更快地从物理介质中读取,因为读取的数据更少,这可以缩短传输时间。

使用lz4的早期中止功能,最坏的情况只比不压缩慢几毫秒,但好处通常相当明显。这就是为什么ZFS对其所有元数据和L2ARCs使用lz4压缩的原因。在不久的将来,当压缩ARC功能在OpenZFS实现时,对数据集启用压缩也将允许更多数据保存在ARC中,ARC是ZFS中最快的缓存。

在Delphix进行的一项生产案例研究中,一台具有768GB内存的数据库服务器从使用90%以上的内存来缓存数据库变成仅使用446GB来缓存1.2TB的压缩数据。压缩内存缓存可以显著提高性能。有些机器可能无法支持更多内存,压缩式唯一的改进方法。

ZFS元数据也会影响数据库。当数据库快速更改时,为每次更改写入两到三个元数据副本可能会占用备份存储的大量可用IOPS。通常,与默认的128KB记录大小相比,元数据的数量相对较小。不过,数据库在记录大小较小的情况下工作得更好。保留元数据的三个副本可能会导致与将实际数据写入池一样多的磁盘活动,甚至更多。

较新版本的OpenZFS还包含一个redundant_metadata属性,默认为all。这是ZFS早期版本的原始行为。但是,此属性也可以设置为most,这会导致ZFS减少其保留的某些类型元数据的副本数量。

根据实际需求和工作负载,允许数据库引擎管理缓存可能会更好。ZFS默认在ARC中缓存数据库中的大部分或全部数据,而数据库引擎保留自己的缓存,导致浪费的双重缓存。将primarycache属性设置为metadata,而不是默认的all,这会告诉ZFS避免在ARC中缓存实际数据。secondarycache属性类似地控制L2ARC。

根据访问模式和数据库引擎的不同,ZFS可能已经更高效了。使用zfs-tool包中的zfsmon等工具来监控ARC缓存命中率,并将其与数据库内部缓存的命中率进行比较。

一旦压缩ARC功能可用,考虑减小数据库内部缓存的大小,让ZFS处理缓存可能是明智的。ARC可能能够在相同数量的内存中容纳比数据库多得多的数据。

MySQL – InnoDB/XtraDB

InnoDB成为MySQL 5.5中的默认存储引擎,与之前使用的MyISAM引擎具有显著不同的特性。

Percona的XtraDB也被MariaDB使用,与InnoDB类似。

InnoDB和XtraDB都使用16KB的块大小,因此包含实际数据文件的ZFS数据集应该将其recordsize属性设置为匹配。

我们还建议使用MySQL的innodb_one_file_per_table设置,将每个表的innodb数据保存在单独的文件中,而不是将其全部分组到一个ibdata文件中。这使得快照更有用,并允许更具选择性的恢复或回滚。

在不同的数据集上存储不同类型的文件。数据文件需要16KB的块大小、lz4压缩和减少的元数据。您可能会看到仅缓存元数据带来的性能提升,但这也会禁用预取。尝试一下,看看你的环境是如何表现的。

主MySQL日志最好用gzip压缩,不需要在内存中缓存:

复制日志在lz4压缩下效果最佳:

然后在/usr/local/etc/my.cnf中进行设置,告诉MySQL套用这些数据集:

现在,可以初始化数据库并开始加载数据了。

MySQL – MyISAM

许多MySQL应用程序仍然使用旧的MyISAM存储引擎,这基本上是旧习惯的惯性所致。

MyISAM使用8KB块大小。数据集记录大小应设置与之匹配。数据集布局应该与InnoDB的布局相同。

PostgreSQL

如果调整得当,ZFS可以支持非常大和快速的PostgreSQL系统。在创建所需的数据集之前,不要初始化数据库。

PostgreSQL默认对所有内容使用8KB存储块。如果更改PostgreSQL的块大小,则必须更改数据集大小与其匹配。

在默认的FreeBSD安装中,PostgreSQL位于/usr/local/pgsql/data中。对于大型安装,实际上可能有一个单独的数据池。在以下示例中,我们有一个名为pgsql池供PostgreSQL:

现在我们遇到了一个鸡和蛋的问题。PostgreSQL的数据库初始化例程希望创建自己的目录树,但我们希望特定的子目录有自己的数据集。最简单的方法是让PostgreSQL初始化,然后创建数据集并移动文件。

初始化例程创建数据库、视图(views)、模式(schemas)、配置文件以及高端数据库的所有其他组件。

现在,我们为特殊部件创建数据集。

PostgreSQL将数据库存储在/usr/local/pgsql/data/base中。预写日志(Write Ahead Log——WAL)位于/usr/local/pgsql/data/pg_xlog中。把这两个都移开:

这两个都使用8KB的块大小,我们希望分别对它们进行快照,因此为每个创建一个数据集。与MySQL一样,告诉ARC只缓存元数据。还要告诉这些数据集使用logbias属性将吞吐量与延迟进行偏差:

然后将原始目录的内容复制到新数据集中:

现在可以启动PostgreSQL。

调整文件大小

ZFS被设计成一个好的通用文件系统。如果你有一个ZFS系统作为典型办公室的文件服务器,你真的不必调整文件大小。不过,如果你知道你将拥有多大的文件,你可以进行更改以提高性能。

小文件

在没有SLOG的系统中高速创建许多小文件时,ZFS会花费大量时间等待文件和元数据完成稳定存储的刷新。

将sync属性设置为disable可以将所有zfs所有写入视为异步,这样做可以欺骗应用程序令其认为文件已经写入磁盘。但这样做有可能会丢失最后5秒内创建的新文件(默认的vfs.zfs.txg.timeout为5秒)。

高速SLOG允许同步快速存储这些小文件。比如添加闪存盘做log缓存。

大文件

ZFS最近通过large_block功能增加了对大于128KB的块的支持。如果你要存储许多大文件,当然要考虑这一点。

默认的最大块大小为1MB。

理论上,可以使用大于1MB的块大小。然而,很少有系统对此进行广泛的测试,与内核内存分配子系统的交互也没有在长时间使用下进行测试。

sysctl vfs.zfs.max_recordsize控制最大块大小。

一旦激活large_blocks(或任何其他功能),不支持该功能的主机将无法再使用该池。通过销毁任何recordsize设置为大于128KB的数据集来停用该功能。

存储系统难以平衡延迟和吞吐量。ZFS使用logbias属性来决定应该向哪个方向倾斜。

ZFS的logbias属性默认值是latency,以便数据快速同步到磁盘,从而允许数据库和其他应用程序继续工作。

在处理大文件时,将logbias属性更改为throughput可能会带来更好的性能。

必须根据实际环境自己进行测试,并决定哪种设置适合实际的工作负载。

两个世界中最糟糕的: Bittorrent

bittorrent将大文件的最坏部分和小文件的最差部分组合在一个方便的包中。

bittorrent的乱序写入模式会导致大量数据集碎片化。此外,如果数据集的记录大小不是16KB,bittorrent的16KB块大小可能会导致写入放大。

这个两个问题的最佳解决方案是实际拥有两个数据集,下载期间存储文件的数据集使用较小的记录大小:

存储已完成种子的数据集应该具有更大的块大小。将文件从一个数据集移动到另一个数据集会对文件进行碎片整理,从而提高读取性能并避免池碎片。

大多数torrent客户端支持使用单独的目录进行正在进行的下载,因此除了创建两个数据集外,这甚至不需要采取任何行动。

如果你下载大型文件,如操作系统ISO,可以考虑使用1MB的recordsize来进一步提高新能,并撤销元数据和冗余。

告诉你的torrent客户端这些目录,你就可以开始了。

短行程

常规的机械硬盘具有特定的特性,可能会导致性能不均匀。

对磁盘开头的读取和写入是最快的(吞吐量更高)。

机械硬盘最慢的方面是寻道时间,即驱动器在要读取或写入的扇区上物理重新定位磁头所需的时间。数据越分散,性能降低越显著。

短行程是只使用磁盘容量的一小部分的过程,通常是磁盘中速度最快的10~30%的分区。现在磁头只需要在磁盘表面的一小部分上移动,平均寻道时间将大大缩短。

将来如果需要更多的存储空间,可以简单地调整分区大小,ZFS将扩大池。

记住,在磁盘空间不足的情况下运行ZFS会增加碎片化,这可能比更高的平均寻道时间更糟糕。