跳转到内容

ZFS 深入:ARC、L2ARC、SLOG、压缩与 recordsize ​

这篇假设你已经读过ZFS 入门,知道池、vdev、ARC 是什么。这里讲更进一步的调优:精确控制 ARC 占用、要不要加 L2ARC/SLOG、压缩和 recordsize 怎么按负载调整,以及 PVE 9.2 自带的 ZFS 2.4 带来了什么变化。

给池加 special/log/cache 设备,以及扩容 raidz vdev,都是不可撤销的操作

本文提到的几类操作各有各的「回不了头」:

  • special 设备加入池后不能移除。 它一旦故障且没有冗余,整个池的数据都没了,不只是丢失加速效果。
  • raidz_expansion 这类新特性一旦启用,池的特性标志 (feature flag) 就回不去了,旧版本 ZFS 无法再导入这个池,救援盘、异地机器上的 ZFS 版本都要留意。
  • 误删快照、误改 special_small_blocks 等属性可能造成不可逆的空间浪费或数据重排,不是致命但代价不小。

开始前:先在测试池(几块闲置小盘,或虚拟机里的虚拟磁盘)上完整走一遍要做的操作,确认命令和参数没问题,再动生产池。这些操作不会替代备份——冗余和特殊 vdev 都属于「防硬件坏」的范畴,不是备份,执行前确认关键虚拟机有独立备份,见备份与恢复。

精确控制 ARC 占用 ​

ZFS 入门提到要给 ARC 设上限,这里给出官方的经验公式和完整步骤。

PVE 8.1 起,安装程序在选择 ZFS 作为根文件系统时,会自动把 ARC 上限写入 /etc/modprobe.d/zfs.conf(默认值:总内存的 10%,上限 16 GiB)。更早的安装或者是数据盘上后建的池,需要手动设置。

官方给的经验公式

基础 2 GiB + 每 TiB 已用存储 1 GiB。比如一个已用 8 TiB 的池,ARC 上限设到 10 GiB 左右比较合适。这是起点,具体还要看这台机器上虚拟机/容器总共需要多少内存。

设置为永久生效(以 8 GiB 为例,按上面的公式换成你自己的数字):

sh
# 宿主机 shell 执行
echo "options zfs zfs_arc_max=8589934592" > /etc/modprobe.d/zfs.conf
update-initramfs -u -k all

这项改动需要重启宿主机才会生效

写进 /etc/modprobe.d/zfs.conf 之后,必须重启才能应用新的上限(模块参数不能在运行中卸载重载,因为 ZFS 池还挂载着)。想立刻临时验证效果、重启后失效:

sh
echo "$((8*1024*1024*1024))" > /sys/module/zfs/parameters/zfs_arc_max

zfs_arc_min 默认是系统内存的 1/32。如果只调 zfs_arc_max 而不动 zfs_arc_min,一旦你把 max 设得比默认 min 还低,min 会被忽略——需要同时把 min 设为「比 max 小一点」:

sh
# 临时生效,重启后失效,仅用于验证
echo "$((8*1024*1024*1024 - 1))" > /sys/module/zfs/parameters/zfs_arc_min
echo "$((8*1024*1024*1024))" > /sys/module/zfs/parameters/zfs_arc_max

Swap 不要放在 ZFS zvol 上

zvol 上的 swap 分区在内存压力大时可能拖慢整个系统甚至挂起,官方建议:内存尽量给够、真要 swap 就用独立物理分区,并且把 vm.swappiness 调低(例如 10,写入 /etc/sysctl.d/99-swappiness.conf 持久化)。

L2ARC 和 SLOG:要不要加缓存盘/日志盘 ​

这两个都是可选的加速设备,不是必需品,加错了反而增加复杂度和故障点。

L2ARC(二级缓存)SLOG(独立日志设备)
解决什么问题ARC(内存缓存)不够大,用一块 SSD 扩展读缓存同步写入 (sync write) 的延迟,常见于数据库、NFS 存储
需要什么硬件较快的 SSD 即可,无需掉电保护必须是带掉电保护 (PLP) 的 SSD,否则起不到应有的安全作用
是否需要冗余不需要,也不支持镜像;坏了自动回退到读池本身建议镜像,全部失效时主池会接管,但短暂窗口有风险
容量建议视工作集大小而定几 GB 通常够用,超过一半内存大小也没有实际收益
多数家用/小型场景是否需要通常不需要,内存本身已经是很大的缓存只有大量同步写场景(数据库、走同步挂载的 NFS)才值得加

新建池时一起加:

sh
# 宿主机 shell 执行,示例:一块盘做池,另一块做 L2ARC
zpool create -f -o ashift=12 tank <pool-device> cache <cache-device>

# 加 SLOG:<log-device> 必须是有掉电保护的 SSD
zpool create -f -o ashift=12 tank <pool-device> log <log-device>

给已有池追加(先用 parted/gdisk 分好区,用 /dev/disk/by-id/ 路径,避免设备名漂移):

sh
zpool add -f tank log <log-device-by-id> cache <cache-device-by-id>

special 设备:第三种加速手段

special vdev 用来存放元数据,以及(通过 special_small_blocks 属性)小于某个大小的数据块,能显著加速有大量小文件/元数据操作的负载。它必须有冗余,且加入后无法移除:

sh
zpool add tank special mirror <device1> <device2>
zfs set special_small_blocks=4K tank

special_small_blocks 取值是 0(关闭)或 512B 到 1M 之间的 2 的幂;设到大于等于 recordsize 时,几乎所有数据都会落到 special 设备上,请确保它的容量和冗余撑得住。

压缩:几乎总是该开 ​

sh
zfs get compression,compressratio tank
zfs set compression=lz4 tank

lz4 是官方推荐的默认选择——CPU 开销很低,绝大多数场景下都是净赚(省下的 IO 比花掉的 CPU 划算)。lzjb、gzip-1 到 gzip-9 也可用,压缩率更高但 CPU 开销更大,一般不需要。改动只影响之后新写入的数据,已有数据不会被重新压缩。

recordsize 与 volblocksize:按负载匹配块大小 ​

recordsize(数据集用)和 volblocksize(zvol 用,创建后不能改)决定 ZFS 按多大的块读写数据。PVE 给虚拟机磁盘用的 zvol 默认 volblocksize 是 8K。

为什么 RAIDZ2 下小 zvol 块会「浪费」空间

以 ashift=12(4K 扇区)的 RAIDZ2 为例,一个 8K 的 zvol 块要配上两个 4K 的校验块,实际写入放大到 16K。想缓解:

  • 建池时按需提高 volblocksize(只能在创建 zvol 时设置,之后改要重建)。
  • 换成 mirror 而不是 raidz(校验开销结构不同)。
  • 或者接受这个开销,换取 raidz 的容量利用率。

调大 volblocksize 要连同客户机内的文件系统块大小一起考虑,不然写放大只是从存储层挪到了客户机层。

普通数据集(容器、文件共享用):

sh
zfs set recordsize=1M tank/media   # 大文件顺序读写场景,比如媒体库
zfs set recordsize=16K tank/db     # 数据库这类小块随机 IO

recordsize 是上限,实际写入小于它的数据不会被填充到这么大;改动同样只影响之后新写入的数据,历史数据要靠重写(比如 zfs send/receive 到新数据集)才能应用新设置。

RAIDZ 扩容:给已有 RAIDZ 加一块盘 ​

OpenZFS 2.3 起支持用 zpool attach 直接给已有的 RAIDZ vdev 追加一块盘,不用像以前那样只能整个新增一组 vdev。PVE 9.0 起自带的 ZFS 版本已经具备这个能力:

sh
# 宿主机 shell 执行,vdev 名称要用 zpool status 里显示的完整名字(如 raidz2-0),不能只写 raidz
zpool attach tank raidz2-0 <new-device-by-id>

扩容不会立刻按新的盘数重新摊薄旧数据

扩容过程会读出现有数据并重新分布到包含新盘的整个 vdev,期间冗余等级不变(RAIDZ2 扩容后仍是 RAIDZ2,能扛的坏盘数不变)。但历史数据保留着扩容前的数据/校验比例,只有之后新写入的数据才享受到新盘数带来的空间效率,实际可用空间增量会比「重新建一个更宽的 vdev」略少。扩容期间如果又有盘故障,扩容会暂停直到冗余恢复。

PVE 9.2 上 ZFS 2.4 带来的变化 ​

以下几项是查得到官方发布记录支撑的 ZFS 2.4 新特性,其余网传的「新特性」未在官方发布说明中找到依据,本文不展开:

  • 默认配额 (default quota):可以给用户/组/项目设置默认配额,不用逐个手动指定。
  • special vdev 可以承载 ZIL:此前 ZIL(意图日志)只能靠 SLOG 或主存储盘,2.4 起在有 special vdev 的池上,它也可以作为 ZIL 的落地位置。
  • 工具改名:arc_summary 和 arcstat 分别改名为 zarcsummary 和 zarcstat。如果你有脚本或监控还在调用旧名字,升级后要改。PVE 自带的 pvereport 已经同步改用新名字。

升级前确认所有节点/救援介质版本一致

启用新的 ZFS 特性标志(feature flag)后,用旧版本 ZFS 的系统(包括救援 U 盘、灾备机器)可能无法导入这个池。集群环境下确认所有节点已经升级到同一 ZFS 版本,再考虑启用新特性。

检查结果 ​

sh
# ARC 上限确认生效(重启后)
cat /sys/module/zfs/parameters/zfs_arc_max

# 池结构,确认 special/log/cache 设备状态正常
zpool status -v

# 压缩效果
zfs get compression,compressratio tank

# 当前 recordsize/volblocksize
zfs get recordsize tank/<dataset>
zfs get volblocksize tank/<zvol>

在测试负载下(比如给测试虚拟机跑一次 fio)对比调整前后的延迟和吞吐,比单看参数更有说服力。

常见问题 ​

加了 L2ARC,读性能没有明显提升。 L2ARC 只有在工作集大小超过 ARC(内存)但又能被 L2ARC 装下时才有明显收益。工作集比内存小,或者比 L2ARC 还大,都不会有帮助。先用 arc_summary(ZFS 2.4 上是 zarcsummary)看缓存命中率。

SLOG 加了但写入延迟没变化。 检查客户机/应用是不是真的在发同步写 (fsync)。异步写不经过 ZIL,加 SLOG 没有意义。也检查 SLOG 设备本身是否真的有掉电保护、性能是否够好。

special vdev 的盘满了会怎样。 池整体会认为空间不足,即便主 vdev 还有很多空闲。要按元数据和小文件的实际增长提前规划容量,不要贴着上限跑。

参考资料 ​

Proxmox VE 非官方中文使用指南,与 Proxmox Server Solutions GmbH 无隶属关系。