第六章:磁盘空间管理

ZFS提供了提高磁盘空间利用率的方法。

ZFS可以在文件系统级别使用压缩。

ZFS可以牺牲内存为代价执行去重操作,以提高磁盘使用率。

读取ZFS磁盘使用情况

df命令显示每个分区上的可用空间量;du命令显示分区或目录树中使用了多少磁盘。

这两个命令很适合传统的文件系统,但不太适合ZFS。

例如:

根据此数据,zroot池使用了17.5GB空间。数据集zroot/ROOT使用1.35GB数据。数据集zroot/ROOT/default也使用了1.35GB。然而zroot/ROOT/default包含在zroot/ROOT中。所有这1.35GB数据是相同的。

同样,zroot/usr使用的12.5GB也包括它下面的所有内容。

使用ZFS,不能只加上已使用的空间量来获得总数。

AVAIL列(或叫 sapce available)显示的数据更可靠。zroot池拥有847GB可用空间。

一旦是开始使用快照和克隆以及其他ZFS优点,由于引用了数据,这个847GB的空间可以包含数倍于此的数据。

引用数据(referenced data)

数据集中包含的数据量是引用的数据。查看上例中的REFER列。zroot池和zroot/ROOT都引用了144KB的空间(144KB空间一般是占位符(placeholder)所占空间)。数据集zroot/ROOT/default引用了1.35GB的数据。

引用的数据是存在于这个文件系统或数据集中的东西。如果进入zroot/ROOT/default文件系统,会发现1.35GB的内容。

多个ZFS数据集可以引用同一组数据,这正是快照和克隆的工作原理,这就是为什么ZFS可以在11GB的空间中保存几个10GB的快照。所以引用空间加起来并不等于使用的量。

克隆使用空间的方式于快照非常相似,只是更具有动态性。一旦添加了去重和压缩,磁盘空间的使用就会变得非常复杂。

甚至还有关于释放空间的问题。

自由空间(freeing space)

在许多ZFS部署中,删除文件实际上并没有释放空间。大多数情况下,由于快照和元数据,删除实际上会使磁盘空间使用量略有增加。

这些文件使用的空间将分配给最新的快照。

要成功管理ZFS,必须了解底层功能是如何工作的,以及ZFS在删除数据时的作用。

在使用快照和克隆的文件系统上,新释放的空间不会立即出现。许多ZFS操作异步释放空间,因为ZFS会更新引用该空间的所有块。

池的freeing属性显示了ZFS仍需要从池中回收多少空间。如果一次性释放一大堆空间,可以看到freeing属性的减少,自由空间增加。

ZFS回收空间的速度取决于硬件、负载量、池设计、碎片级别以及空间的使用方式。

异步释放空间容易理解:可以查看freeing属性,看看它下降的速度有多快。

但对于新手来说,ZFS的磁盘使用似乎要奇怪很多。

磁盘空间细节

要查看磁盘空间的确切位置,使用zfs list -o space:

此例中zroot/usr数据集使用了12.5GB,引用454MB,而其子数据集占用了12GB。

想要查阅磁盘使用情况,应使用zfs list -o space命令。也可使用类似以下命令查看指定数据集及其子数据集的使用情况:

zfs list的-o选项后面可以跟若干属性名称,各属性之间用逗号隔开。

例如:

 

池空间使用

有时池的可用空间比数据集的空间使用情况更重要。

对于镜像池或条带池,池空间信息相当接近现实。但对于冗余的RAID-Zn系统,理论上可以根据属性、池的当前使用情况和一些计算方式来猜测空间的使用量。但更简单的方法是向zfs询问池的根数据集。

此池使用了37.7GB,剩余854GB空间。

ZFS, df(1), 和第三方工具

ZFS有各种奇特的能力对其磁盘使用情况的显示进行切片(slice)和切块(dice)。

在ZFS环境下,df命令显示的结果往往是错误的。

传统的文件系统由单个分区组成,该分区的大小取决于底层磁盘上分配的块数。df工具迭代每个已挂载的文件系统,并显示分区的大小、当前使用的空间量以及剩余的可用空间量。

遍历文件系统对ZFS不起作用,因为数据集不是文件系统。默认情况下数据集没有设置配额,所以它没有最大大小。

为了提供一些与传统df工具的兼容性,ZFS提供了一个善意的谎言。由于ZFS数据集在传统文件系统意义上没有“size”,因此它将已使用的空间和整个池的可用空间加在一起,并将该值表示为数据集的size。

上一节中的示例里,zroot/ROOT/default数据集使用了1.35GB空间,还剩余874GB可用空间,总大小为875.35GB。然后,查看zroot/usr数据集,它使用了12.5GB,剩余847GB,总共886.5GB。

现在,用df命令查看这些数据集:

/文件系统是875GB,/usr是874GB,这两个加起来就有1749GB,还剩余1748GB。

而实际上整个磁盘仅有1TB,以上所显示的文件系统使用百分比的Capacity列同样是伪造的。

随着数据集的增大,可用空间的数量会减少。根据df,文件系统随着空间的使用而缩小,当空间被释放时则会增长。

需要特别注意,类似df的传统文件系统工具会给出不正确的答案。监控ZFS系统需要ZFS专用工具。

当与用于传统文件系统的其他工具一起查看时,这种行为会产生有趣的副作用。

通过Samba在Windows计算机上挂载ZFS数据集,将仅显示已使用的极小空间量,池中剩余的空间量为可用空间。随着池中充满数据,Windows会看到驱动器缩小。

使用ZFS时,应养成用精确工具管理它的习惯。

限制数据集尺寸

ZFS的灵活性意味着用户和应用程序可以使用可用的磁盘空间。但这有利有弊。比如/var/log的无限制增长可能会填满磁盘。或者某些用户在他的目录中存放了过多的文件,造成主数据库空间不足。

配额(quota)和预留(reservation)可以防止这些问题。

配额规定了数据集及其所有子集可以使用的最大空间量。如果将挂载为/home的数据集设置为100GB的配额,/home及其下的所有数据集和快照使用的总空间量都不能超过100GB。

预留则决定了为数据集留出的空间量。为了确保数据库始终有空间写入文件,应使用预留为该数据集腾出一定量的磁盘空间。

以下先讨论预留,再讨论限额。

预留(reservation)

预留会为数据集及其子数据集留出一块磁盘空间。系统将不允许其他数据集使用该空间。

即使池空间不足,预留的空间仍将可用于写入该数据集。

假设我们从1TB的池中为/var/db预留100GB,我们的数据库将其数据文件存储在/var/db中。这个数据集中有大约50GB的数据,

一个日志文件发了疯,填满了池的其余部分。我们会收到系统上其他程序的错误,说磁盘已满,但数据库程序在/var/db中仍有可用的空间。它可能会抱怨无法将程序日志写入/var/log/db,但这是一个单独的问题。

ZFS使用两个ZFS属性管理预留:refreservation和reservation。

refreservation会影响数据集的引用数据(referenced data),也就是说,它不包括快照、克隆和其他后代。

reservation则包括子数据集、快照等。

例如:

zroot/usr数据集挂载到/usr。它使用了12.5GB,包括子数据集(比如/usr/local,/usr/obj等)。它仅引用了454MB,这意味着主数据集zroot/usr上的数量不到半GB。

如果在zroot/usr上设置了1GB的reservation,那基本上时没有意义的。子数据集的现有文件远远超出了预留值。

如果在zroot/usr上设置1G的refreservation,它只会影响zroot/usr上的文件。子数据集被排除在外。此时数据集处于半满状态,因此还有空间可以写入更多文件。

这是一个极端的例子。假设想确保所有用户都获得至少1GB的空间,为每个用户的主目录创建一个单独的数据集,并为每个用户分配一个预留值(reservation)。

reservation可以嵌套。假设有两个数据集,zroot/var/log和zroot/var/log/db,后者专门用于数据库服务器。希望数据库服务器日志始终至少有10GB的空间,因此为zroot/var/log/db分配了一个reservation。然后,需要20GB的通用服务器日志,如果希望这20GB包括数据库的日志,可使用reservation设置zroot/var/log;而如果希望这20GB的保留不包括数据库日志,则应使用refreservation设置zroot/var/log。

数据集可能同时具有reservation和refreservation。

数据集zroot/var/log/db为当前日志文件保留了10GB的refreservation空间,但应该设置更大的reservation空间,以便可以对数据集进行快照并单独计算其使用情况。

试图违反reservation会产生“空间不足”错误。当出现该错误时,即使仍有可用磁盘空间,也应该检查reservation设置。保留的数据集将显示可用空间,但所有其他数据集可能都满了。

查看预留

使用zfs get reservation,refreservation,usedbyrefreservation来查看特定的属性列表:

或者使用偷懒的方法——使用grep过滤信息:

可以使用zfs list -o name,reservation,refreservation,usedbyrefreservation查看对应的信息:

usedbyrefreservation属性显示了如果删除refreservation,此数据集将释放多少空间。

设置和删除预留

设置预留与设置其他ZFS属性一样:

取消预留设置的方法是将对应的属性值设置为none:

配额

quota表示数据集、用户、用户组可以使用的最大空间量。

所有配额都是基于每个数据集设置的。

数据集配额

类似于预留,配额也使用了两个属性:quota和refquota。

如果不想配额影响快照和子数据集,应设置refquota属性。

设置配额

使用zfs set quota设置配额:

日志文件可以使用不超过100GB的空间,包括快照、子数据集和其他任何数据。

以上示例中,我们为两个日志数据集设置了单独的refquota,并为这两个数据一起设置了quota。

查看配额

使用以下命令可以查看数据集的配额:

配额会改变数据集的最大大小和数据集的可用空间。此池有数百GB的可用空间,但对此数据集执行zfs list却显示情况并非如此:

zroot/var/log数据集有25GB数据,75GB可用空间。ZFS知道有100GB的配额,它显示了正确的使用率。这比df显示的要准确。

ZFS知道父数据集的配额是100GB,因此也在子数据集上设置了最大的大小。

如果/var/log有75GB可用空间,/var/log/db有85GB可用空间,是不是意味着这两个分区有160GB的可用空间?不是,因为池的可用空间是这两个分区共用的。

数据集zroot/var/log/db似乎有更多的可用空间,因为其父数据集中的数据没有反映在子数据中的使用情况中。

超出配额

如果用户或进程试图编写一些会超出数据集配额的内容,会收到配额错误:

需要释放一些空间,但要注意,快照可能会使这一过程复杂化。

如果同时设置了quota和refquota,则用户可能能够通过删除文件来释放空间,即使这回增加文件系统快照的大小。

用户和组配额

用户和组配额控制用户或组可以向数据集写入多少数据。

与数据集配额一样,用户和组配额是基于每个数据集进行控制的。

用户和组配额不适用于子文件系统、快照和克隆。必须将配额应用于希望它们影响的每个单独的数据集。

查看每个数据集的已用空间和现有配额

使用zfs userspace命令可以查看数据集中每个用户使用了多少空间。

此例中,用户mwlucas使用了1.16GB。用户root使用了298MB。

当使用tar -p提取tarball时,会保留原始文件的所有权,这可能会带来本系统没有的用户ID。

在FreeBSD14.1系统上,以上命令的输出结果中还有OBJUSED和OBJQUOTA列,从列的名称看,应该是列出了userobjused和userobjquota属性值。

使用zfs groupspace命令显示每个组所拥有的文件使用了多少空间:

如果服务器有多个组,可以为每个组或用户设置配额。

分配和删除用户和组的配额

使用userquota和groupquota属性设置用户和组的配额。在属性名称后面添加@符号来指定用户或组的名称。例如:

上一节显示,mwlucas账户中有超过1G的数据,已经超出了配额,当用户尝试创建文件时,会收到错误提示:

同样,使用groupquota@设置组配额:

要清除配额,可以将配额值设置为none。

查看个人配额

使用以下命令查看用户或组的配额:

ZFS 压缩

如果不能增加现有磁盘的大小,可以更改数据使用磁盘的方式。

ZFS可以在文件系统级别实现实时压缩文件。包括专门为文件系统使用而设计的压缩算法。

压缩和解压缩需要消耗CPU的时间,因此盲目地在任何地方启用最严格的gzip压缩可能会对磁盘性能增加另一个限制。

启用压缩

ZFS压缩在每个数据集的基础上工作,可以指定某些数据集启用压缩,而其他数据集不启用。

使用compression属性启用和禁用压缩。现在我们先查看压缩设置:

默认的压缩算法是LZJB(FreeBSD14.1版默认是LZ4),并不是ZFS提供的最有效的算法。几乎所有情况下都使用LZ4压缩。

下面例子中我们指定zroot池中所有数据集使用LZ4压缩,但是特别指定zroot/var/cdr数据集使用gzip-9压缩算法:

ZFS在文件写入磁盘时压缩文件。如果你有一个充满文本文件的数据集,添加压缩不会使它们缩小。为了减少文件使用的磁盘空间,您必须在启用压缩后重写所有文件。

压缩算法

LZ4是一种更新、更快的特定文件系统压缩算法。并非所有数据都是可压缩的,LZ4可以快速检测不可压缩的文件,并且不会尝试压缩它们。如果没有特殊需求,应使用LZ4压缩算法。

ZLE算法压缩文件中的零字符串。这是一个最小的压缩系统,对大多数文件来说不是很有用。

对于特殊情况,ZFS支持gzip压缩。gzip比LZ4使用更多的CPU时间,但对于某些数据集来说可能更有效。对于不经常访问的数据,节省磁盘空间可能更重要。

gzip有九个压缩级别,从1(最快但压缩比最小)到9(最慢但压缩比最大)。

指定gzip但没有指定级别的话,ZFS默认使用级别6。

压缩属性

有几个属性可以深入了解ZFS压缩数据的效果。

选择算法

如何判断数据是否可以从压缩中受益,或者不同的算法如何影响文件大小?

获取一些典型的数据文件并对其进行测试。使用du或ls -ls查看磁盘上文件的实际大小。

在测试自己的数据时,需要使用一大堆不同的实际数据文件。

以下示例中的文件来自古腾堡计划中的人类基因组计划。

未压缩的情况下,这个文件占用280721个块,或者大约274MB。

用于测试的数据集叫db。这个数据集上还没有其他数据,因此我们可以准确地评估压缩对这个特定文件地影响。现在,启用压缩:

再查看文件大小:

文件大小没有变化,因为压缩、去重等类似功能仅适用于已经启用了该功能后写入的文件。删除文件并重新放入:

等几秒钟让ZFS完成写入动作,再次查看:

文件仅使用了139577个块,大约136MB。大约缩小了一半,像数据集属性显示的那样:

refcompressration和compressratio属性值相同,因为数据集中只有一块数据,而且池上也只有一个数据集。在复杂的池中,值可能会不同。

试试修改压缩算法为lz4:

重新复制文件,等几秒钟,再次查看:

LZ4将数据压缩到142MB,在这个特定文件上,LZ4如不LZJB有效。这是因为不同的算法对不同的数据有不同的处理方式。

再试试gzip:

重新复制文件,再次查看:

这次数据只有72MB了,压缩比(compressratio)达到了3.78。调高gzip级别:

使用gzip-9压缩的数据仅剩下了62MB,压缩比达到了4.41。gzip-9使可用空间增加了四倍多。

但以上示例是骗人的,除了古腾堡计划添加的样板外,人类基因组计划完全由四个字母组成。它可能是现存最冗余、最可压缩的真实世界数据。从大多数现实世界的数据中,你不能指望这一点。

何时更改压缩算法

通常,除非在有迫切需要时才更改LZ4的压缩算法,额外的CPU开销和较慢的磁盘访问不会影响LZ4算法的实际工作。

只有按照上一节测试后能获取更大压缩比时,才考虑更换压缩算法。

压缩和性能

先看一下示例中的这些属性:

这个数据集用了48.7MB磁盘空间。如果忽略压缩,数据集有220MB的数据。压缩的数据集可以存储更多的逻辑数据。

这就是压缩的有效性真正发挥作用的地方。读写数据最慢的部分是在存储介质上获取数据。

物理介质是磁盘事务中最慢的部分。将48.7 MB写入磁盘所需的时间大约是写入220 MB的22%。

通过启用压缩,您可以以少量CPU时间为代价将存储时间缩短78%。

如果您的磁盘可以写入100 MB/s,那么写入48.7 MB的压缩数据大约需要半秒。

如果你从写数据的应用程序的角度来看,你实际上在半秒内写了220 MB,相当于440 MB/s。我们打赌你不认为你的笔记本电脑磁盘可以做到这一点!

如果你存储了许多小文件,压缩的效果就不那么好。小于扇区大小的文件无论如何都会被分配一个完整的块。如果你想要真正有效的压缩,请使用具有实际512字节物理扇区的磁盘,并告诉ZFS使用该扇区大小。

压缩并不完美。顺序和随机访问可以改变压缩的性能。始终在您的环境中使用您自己的数据进行测试。压缩工作得很好,FreeBSD在默认安装中启用了lz4压缩。

大多数CPU大多处于空闲状态。让懒惰的动物处理一些数据!

停用压缩

要停用压缩,应将数据集的compression属性设置为off。

就像激活压缩只会影响新写入的文件一样,停用压缩只影响新数据。压缩文件在重写之前保持压缩状态。ZFS足够聪明,可以知道文件是压缩的,并在访问时自动解压缩,但它仍然有开销。

除非重写所有文件,否则无法清除数据集中的所有压缩痕迹。你最好重新创建数据集。

去重

文件一遍又一遍地重复相同的数据,排列方式略有不同。多个文件包含更多的重复。您系统中超过一半的数据可能是其他地方发现的数据的重复。ZFS可以识别文件中的重复数据,提取并记录它,从而只存储每条数据一次。这与压缩非常相似。在某些情况下,去重可以减少磁盘使用。

ZFS在文件系统块级别进行去重(由reconrdsize属性显示)。

使用较小的块可以提高去重的效果,但会增加内存需求。ZFS仅存储一次相同的块,并将去重表存储在内存中。

为了实现高效的去重,系统必须有足够的内存来保存整个去重表。

ZFS将去重表存储在磁盘上,但如果主机每次想要访问文件时都必须查阅磁盘上的副本,性能将变慢。

慎用。

 

去重内存需求

经验而言,可以按每TB去重需要5GB的内存。

建议计算数据需要多少RAM,然后使用最悲观的结果。

去重后的数据集上的每个文件系统块使用大约320字节的RAM。ZFS的zdb工具可以分析一个池,看看有多少块被使用,使用-b选项:

注:10T左右的池,以上操作可能需要1小时左右。

"bp count"显示了存储在池中的ZFS块大小的总数。这个池使用了139025个块。

如果池中有许多小文件,则需要更多的内存。

倒数第三行的used显示该池使用1.81%。假设此池中的数据在增长过程中将保持相当一致。将已使用的块数量四舍五入到140000。将使用的块除以块的满度,可以看到满池将有大约7734806个块,每个块320字节,这些数据使用2475138121字节的RAM,大约2.3GB。

这还不到经验法则的一半。假设此池上的ZFS重复数据删除表每TB存储需要5 GB RAM。

每TB的去重意味着至少20GB的RAM,即使根据块使用情况进行更有希望的计算,每TB磁盘需要2.3GB的RAM,25%的限制意味着每TB的去重池大约需要10GB的RAM。

去重效率

ZFS可以模拟去重,并很好地估计数据的去重效果。在池上运行zdb -S,会得到一个很好的利用率和常见元素的直方图。最后一行的结果可用来参考:

以上显示这个池数据可以重复3.68次,如果这个池中的所有数据都是可以去重的,那么可以在每TB的存储中容纳3.68TB的数据。然而这些数据可能存在异常冗余,用户程序和主目录的zroot池大约是1.06可去重。

根据实际情况,更快的磁盘和更多的CPU来增强压缩比,比用大量内存进行去重,更具有成本效益。

去重并不能提高磁盘读取速度,尽管它可以提高缓存命中率。它只会在发现重复块时提高写入速度。

去重还会显著增加释放块所需的时间,因此销毁数据集和快照可能会变得非常缓慢。

去重可能只有在磁盘空间有限、价格昂贵且性能非常高的情况下才有意义。例如在SSD池中,去重才会显得有意义。

启用去重

ZFS的dedup属性值(on或off)控制去重:

与压缩属性一样,去重属性仅影响新写入的数据。

禁用去重

将dedup设置为off可以关闭去重:

如果池中有去重的数据,关闭此属性并不会影响已经去重的数据,依然会继续因去重影响系统的性能。最好使用zfs send和zfs receive将数据发送到不使用去重的新数据集,才能完全消除影响。

最好的选择可能是不使用去重。