ZFS系列(九)压缩与数据去重

本文主要介绍ZFS的压缩和数据去重特性。

压缩

如果你开启了ZFS的压缩功能,这个压缩过程是透明的。也就是说,在你开启压缩功能后,在你存储池中的每一个文件都被压缩了,但是从应用程序的角度来看,感受不到这一点,感觉文件没有被压缩。主要是ZFS压缩解压磁盘上的数据是动态进行的,并且压缩对CPU消耗很小而且一些压缩算法很快,所以上层通常感知不到(没有注意到压缩这个事情)。

可以对每个数据集启用和禁用压缩。支持的压缩算法有LZJB、LZ4、ZLE和Gzip。对于Gzip,支持标准级别是1到9,其中1是尽可能快的,压缩率最少,9是压缩比例高,但是耗时最多,默认值是6,这是GNU/Linux和其他Unix操作系统的标准值。LZJB是由Jeff Bonwick发明的,同时他也是ZFS的作者。LZJB被设计为具有紧凑的压缩比,这是大多数Lempel-Ziv算法的标准。LZJB是ZFS默认采用的压缩算法。ZLE是一个速度非常块,具有非常轻的压缩比。在性能和压缩方面,LZJB似乎提供了最好的全面结果。

更新:自从写这篇文章以来,LZ4已经被引入到Linux上的ZFS中,现在它是使用ZFS进行压缩的首选方式。它不仅速度快,而且提供了比LZJB更紧凑的压缩比——平均约为0.23%

显然,根据存储的数据的不同,压缩节省的磁盘空间也会有所不同。如果数据集主要存储未压缩的数据,如纯文本日志文件或配置文件,则压缩比可能是巨大的。如果数据集主要存储的是压缩的图像和视频,那么在磁盘节省方面就不可观。压缩在默认情况下是禁用的,启用LZJB或LZ4似乎不会产生任何性能影响。因此,即使您存储的是大量压缩的数据,对于那些未压缩的数据文件,启用压缩后您也可以节约一些磁盘空间,而不会影响存储服务器的性能。所以,在我看来,我建议对所有的数据集启用压缩。

警告:对数据集启用压缩是不可追溯的!它只适用于新提交或修改的数据。数据集中以前的任何数据都将保持未压缩状态。因此,如果您想使用压缩,您应该在开始提交数据之前启用它。

要在数据集上启用压缩,我们只需要修改“compression”属性。该属性的有效值是:”on”, “off”, “lzjb”, “lz4”, “gzip”, “gzip[1-9]”和”zle”。

1
2
# zfs create tank/log
# zfs set compression=lz4 tank/log

现在我们已经在这个数据集上启用了压缩,让我们复制一些未压缩的数据,看看我们会看到节省多少空间。未压缩数据的源文件是/etc/和/var/log/目录。让我们为这些目录创建一个tar包,看看它的原始大小,然后再看看节省了多少空间:

1
2
3
4
5
6
7
8
9
# tar -cf /tank/test/text.tar /var/log/ /etc/
# ls -lh /tank/test/text.tar
-rw-rw-r-- 1 root root 24M Dec 17 21:24 /tank/test/text.tar
# zfs list tank/test
NAME USED AVAIL REFER MOUNTPOINT
tank/test 11.1M 2.91G 11.1M /tank/test
# zfs get compressratio tank/test
NAME PROPERTY VALUE SOURCE
tank/test compressratio 2.14x -

在上面的例子中,我创建一个24MB的未压缩的tar包,把它拷贝到开启压缩的数据中后,它只占用11.1MB,还不到原始文件的一半(文本文件很适合压缩)!我们可以读取数据集上的“compressratio”属性,看看节省了多少磁盘空间。在上面的例子中,输出告诉我,如果未压缩,压缩后的数据将占用2.14倍的磁盘空间,非常好的结果。

数据去重(Deduplication,重复数据删除/重删)

我们还有另一种与压缩相结合的方法来节约磁盘空间,那就是重复数据删除。现在,有三种主要的重复数据删除类型:文件、块和字节。文件重复数据删除是性能最高、系统资源成本最低的。每个文件都使用加密哈希算法进行哈希,比如SHA-256。如果哈希值匹配多个文件,则不将新文件存储在磁盘上,而是在元数据中引用原始文件。这可以节省大量空间,但也有一个严重的缺点。如果文件中的单个字节发生了变化,哈希值将不再匹配,这意味着我们不能再在文件系统元数据中引用整个文件。因此,我们必须将磁盘所有块做一个复本,对于大文件,这对性能有很大的影响。

另一种极端情况是字节重复数据删除,这种重复数据删除方法成本最高,因为您必须保留“锚点”,以确定重复数据删除和唯一字节区域的开始和结束位置。毕竟,字节就是字节,是不知道哪些文件需要它们,它只不过是一个数据的海洋。这种重复数据删除技术适用于文件可能被存储多次的存储,即使文件没有在相同的块下对齐,比如邮件附件。

块重复数据删除处于三者中间位置。ZFS仅支持块重复数据删除。块重删共享文件中所有相同的块。这允许我们只在磁盘上存储唯一的块,并在RAM中引用共享块。它比字节重复数据删除更高效,比文件重复数据删除更灵活。然而,它有一个缺点——它需要大量的内存来记录哪些块是共享的,哪些不是。不过,因为文件系统读写数据是以块的方式,所以对现代文件系统使用块重复数据删除是最有意义的。

共享块被存储在所谓的“重复数据删除表”中,文件系统上重复的块越多,这个表就越大。每次写入或读取数据时,重复数据删除表都会被引用。这意味着您希望将整个重复数据删除表保存在快速RAM中。如果您没有足够的RAM,那么表将溢出到磁盘上。这可能会对存储的性能产生巨大的影响,无论是读数据还是写数据。

数据去重的开销

所以这还有个问题:你需要多少内存来存储你的重复数据删除表?这个问题没有一个简单的答案,但是我们可以对如何处理这个问题有一个很好的总体思路。首先,查看存储池中的块数量。你可以看到这个信息,用如下的命令(耐心点-在它给出报告之前,可能需要一段时间来扫描文件系统中的所有块):

1
2
3
4
5
6
7
8
9
10
11
12
# zdb -b rpool

Traversing all blocks to verify nothing leaked ...

No leaks (block sum matches space maps exactly)

bp count: 288674
bp logical: 34801465856 avg: 120556
bp physical: 30886096384 avg: 106992 compression: 1.13
bp allocated: 31092428800 avg: 107707 compression: 1.12
bp deduped: 0 ref>1: 0 deduplication: 1.00
SPA allocated: 31092244480 used: 13.53%

在本例中,存储池“rpool”中有288674块被使用(查看“bp count”)。池中的每个重复数据删除块需要大约320字节的内存。因此,对于288674块乘以320字节/块,我们得到大约92 MB。文件系统大约有200 GB大小,所以我们可以假设重复数据删除只能增长到大约670 MB,尽管它只有13.53%被填满。也就是说,每1GB的文件系统需要3.35 MB的重复数据删除数据,或者每1TB的磁盘需要3.35 GB的RAM。

如果您需要提前规划存储,并且希望在提交数据之前知道大小,那么您需要计算出块的平均大小。 在这种情况下,您需要非常熟悉这些数据。ZFS以128KB块大小的形式读写数据。但是,如果您要存储大量的配置文件、主目录等,那么您的文件将小于128KB。对于本例,我们假设平均块大小为100 KB,如上例所示。如果我的总存储是1TB,那么1TB除以100KB每个块大约是10737418块,然后乘以每块320字节,我们得到3.2 GB的RAM,这接近我们之前得到的数字。

一个好的经验法则是,为每1TB磁盘规划5GB RAM。这很快就会变得非常昂贵。一个12 TB的池(在许多企业中很小)将需要60 GB RAM来确保您的重复数据删除表被存储和快速访问。请记住,一旦重复数据删除表溢出到磁盘,就会造成严重的性能影响。

数据去重的总内存开销

ZFS在RAM中存储的不仅仅是重复数据删除表。它还存储ARC以及其他ZFS元数据。你猜怎么着?重复数据删除表的大小上限是ARC的25%。这意味着,一个12TB的存储阵列需要的内存不只是60GB,而是需要240GB RAM来确保您的重复数据删除表能被完全存下。换句话说,如果您计划做重复数据删除,请确保将内存占用增加四倍,否则您将受到伤害(性能受到很大影响)。

请确保系统具有足够的内存来支持重复数据删除,如下所示:

  • 每个块的对应的重复数据删除表项大小约为 320 字节
  • 用分配的块数乘以 320,即可得到大概需要多少内存。
    例如:
    in-core DDT size = 2.63M x 320 = 841.60M
    

L2ARC中的重复数据删除表

内存放不下的重复数据删除表可能会溢出到L2ARC,不一定是慢盘。如果您的L2ARC由快SSD或RAM驱动器组成,那么每次读取和写入重复数据删除表对性能的影响不会像溢出到盘片磁盘那样严重。然而,它仍然会有影响,因为SSD性能还是比不上内存。因此,对于性能要求不是很高的存储服务器,例如夜间或每周备份服务器,重复数据删除表溢出到L2ARC上,是完全可以接受的。

启用数据去重

可以通过设置”dedup”属性来启用数据去重。同压缩一样,数据去重对以前提交的数据不起作用。它只应用于新提交或修改的数据。此外,重复数据删除后的数据不会作为原子事务刷新到磁盘。相反,这些块是串行地写入磁盘的,每次一个块。因此,在写入块之前出现电源故障时,这将使您面临数据损坏的风险。

让我们在“tank/test”数据集上启用数据去重特性,然后拷贝一个相同的tar包,给它一个不同的名称,并看看这将如何影响重复数据删除比率。注意,获取重复数据删除比率是使用“zpool”命令,而不是使用“zfs”命令。首先,我们需要启用数据集的重复数据删除功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# zfs set dedup=on tank/test
# cp /tank/test/text.tar{,.2}
# tar -cf /tank/test/boot.tar /boot
# zfs get compressratio tank/test
NAME PROPERTY VALUE SOURCE
tank/test compressratio 1.74x -
# zpool get dedupratio tank
NAME PROPERTY VALUE SOURCE
tank dedupratio 1.42x -
# ls -lh /tank/test
total 38M
-rw-rw-r-- 1 root root 18M Dec 17 22:31 boot.tar
-rw-rw-r-- 1 root root 24M Dec 17 22:27 text.tar
-rw-rw-r-- 1 root root 24M Dec 17 22:29 text.tar.2
# zfs list tank/test
NAME USED AVAIL REFER MOUNTPOINT
tank/test 37.1M 2.90G 37.1M /tank/test

在这个例子中,首先对数据进行压缩,然后进行数据去重。原始数据通占用66MB的磁盘空间,但是由于压缩和数据去重,它只占用37MB,节约了不少存储空间。

结论及建议

毫无疑问,压缩和数据去重可以提供巨大的存储效益。对于实时运行的生产数据,压缩节省了巨大的存储空间,而对性能的影响可以忽略不计。对于混合数据,我通常会看到1.15倍的节省,就成本而言,这是很值得的。但是,对于数据去重,我发现不值得这么麻烦,除非性能根本不是问题。数据去重对RAM和L2ARC的消耗占比是巨大的。当它溢出到普通盘上时(内存和L2ARC上存不下数据去重表),你就可以和性能说再见了。对于混合数据,我很少看到它能节省超过1.10倍的,在我看来这是不值得的。此外,在我看来,数据去重带来的数据损坏风险也是不值得的。因此,作为建议,我建议您在默认情况下对所有数据集启用压缩,不要开启数据去重,除非您知道您有足够的内存来容纳数据去重表。如果您负担得起这种开销,那么节省的空间将非常可观,这是ext4、XFS和其他文件系统无法实现的。

关于是否启用ZFS的数据去重,我再多说一点:主要还是取决于你的数据。如果你的数据确实不包含重复,则开启去重功能则会带来额外的开销且没有任何的好处。但是如果你的数据包含重复,则使用ZFS的去重可以节约空间而且提高性能。节约空间是显而易见的,性能的提高是因为减少了重复数据的写磁盘消耗和内存页的置换。

参考资料

https://pthree.org/2012/12/18/zfs-administration-part-xi-compression-and-deduplication/

ZFS与数据去重

如果你觉得本文对你有帮助,欢迎打赏