外观
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_maxzfs_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_maxSwap 不要放在 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 tankspecial_small_blocks 取值是 0(关闭)或 512B 到 1M 之间的 2 的幂;设到大于等于 recordsize 时,几乎所有数据都会落到 special 设备上,请确保它的容量和冗余撑得住。
压缩:几乎总是该开
sh
zfs get compression,compressratio tank
zfs set compression=lz4 tanklz4 是官方推荐的默认选择——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 # 数据库这类小块随机 IOrecordsize 是上限,实际写入小于它的数据不会被填充到这么大;改动同样只影响之后新写入的数据,历史数据要靠重写(比如 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 还有很多空闲。要按元数据和小文件的实际增长提前规划容量,不要贴着上限跑。