小型 VPS ZFS 存储池多久执行一次 scrub?
ZFS scrub 会读取所有已分配块并校验数据。单设备 VPS 存储池只能检测损坏,通常无法修复,本文说明合理的执行频率。
ZFS scrub 的实际作用
ZFS scrub 会读取池中每个已分配的块,重新计算其校验和,并将结果与父块指针中存储的校验和进行比较。如果两者不一致,ZFS 会使用池中现有的冗余数据修复该块。ZFS 中没有其他机制负责此项工作。普通读取只会验证实际访问到的块,因此,某个文件如果两年未被打开,就会一直处于未验证状态,直到 scrub 读取它。
scrub 不是 fsck,也不是其他文件系统所需的离线修复过程。ZFS 不需要结构修复阶段,因为它不会让磁盘上的格式处于损坏状态:每次写入都会写入新位置,最后才更新 uberblock,即池的根指针。scrub 也不会读取整个设备。它只读取已分配的块,因此几乎为空的池可以在几分钟内完成 scrub,而同一个池在使用率达到 80% 时会耗时更久。
scrub 使用 ZFS 可用的最低 I/O 优先级运行。在 Linux 上,zfs_vdev_scrub_max_active 默认值为 2,因此每个 vdev(virtual device,ZFS 视为一个单元的磁盘组)最多同时执行两个 scrub 读取。zfs_scrub_min_time_ms 默认值为 750,表示每次事务组刷新之间,同步线程处理 scrub 任务的最短时间;事务组是 ZFS 将写入批量提交的周期性提交单元。在空闲机器上,scrub 会使用整个磁盘的 I/O 能力。系统有负载时,它会让出资源。在只有一个或两个设备的池中,没有其他资源可以让出,因此相比拥有六十块磁盘的大型机箱,这类环境更需要合理安排 scrub 时间。
为什么要对无法自行修复的存储池执行 scrub?
这是决定小型存储池其他配置的关键。没有冗余时,scrub 可以检测损坏,但无法修复。 VPS 中的单个虚拟磁盘构成没有镜像、没有校验数据的存储池。ZFS 会读取损坏的数据块,校验和验证失败后,将其计入 CKSUM 列并指出所属文件,然后停止处理,因为没有第二份副本可用于重建。
有两个需要了解的部分例外。ZFS 默认会额外存储一份元数据(redundant_metadata=all),并将其写入设备的其他区域。因此,即使是单设备存储池,scrub 也可以修复损坏的目录项或块指针。设置 copies=2 的数据集会保留两份数据块,但空间占用也会增加一倍。这两种机制都无法应对设备完全不可用的情况。copies 属性文档明确警告了这一点:不要创建条带化存储池、设置 copies=2,然后误以为这样就有了冗余。
因此,在单设备存储池上,scrub 能提供的一项能力是:尽早且准确地发出通知。当备份中仍保留该文件的正确版本时,scrub 会将静默数据损坏转换为 zpool status -v 中的文件名。这说明应当做好备份,而不是不执行 scrub。如果您还没有明确区分时间点映像和真正存放在服务器外部的副本,请先阅读为什么 VPS 快照不是备份,因为只有其他位置保留完整副本时,scrub 结果才有实际帮助。
scrub 未发现任何问题同样是一种结果。它表明您即将信任的数据完好无损,这正是执行恢复或迁移前需要确认的信息。
小型 VPS 池应多久执行一次 scrub?
每月一次是合适的默认设置,软件包也已按此设置。Debian 和 Ubuntu 提供的 cron 任务会在每月的第 2 个星期日对正常运行的池执行 scrub。FreeBSD 的 periodic 系统根据天数阈值运行,daily_scrub_zfs_default_threshold默认为 35;手册将其描述为 5 周。
对于繁忙的小型池,每周执行 scrub 通常得不偿失。只有 1 或 2 个设备时,scrub 会与应用争用同一个队列,也没有备用设备来承担这项工作。在 VPS 上,I/O 配额是有限的,因此 scrub 消耗的读取操作会挤占数据库可用的读取操作。相比之下,每周执行 scrub 最多只能让您提前 3 周发现一个实际上无法修复的故障。只有 scrub 成本较低时,这种权衡才有意义。
先测量耗时,再决定。手动运行一次 scrub,并观察其耗时。
- 在业务空闲的晚上运行
sudo zpool scrub tank,并从zpool status记录总耗时。 - 如果任务在 1 小时以内完成,且服务器夜间处于空闲状态,则每周执行的成本可以接受。
- 如果任务在池提供网络流量服务期间持续了数小时,则保持每月执行,并由软件包提供的任务负责调度。
- 每当池的规模明显增长时,重新测量耗时,因为 scrub 时长取决于已分配的数据量,而不是磁盘容量。
无论选择哪种频率,都应将其记录在其他定期服务器维护事项旁边。scrub 应与软件包升级和日志轮换列在同一清单中:参见 每月 Linux 服务器维护检查清单。
启动、暂停和停止 scrub
sudo zpool scrub tank
sudo zpool status tank暂停和停止是不同的操作。选择错误的操作可能导致数小时的重复工作。
sudo zpool scrub -p tank
sudo zpool scrub tank
sudo zpool scrub -s tank-p 会暂停 scrub。暂停状态和进度会定期同步到磁盘,因此暂停的 scrub 在导出或重启后仍会保持暂停:存储池恢复后,scrub 仍处于暂停状态,等待您的操作。再次运行 zpool scrub 会从上次写入磁盘的检查点继续。-s 则会停止 scrub,您下次启动 scrub 时会从头开始。需要暂时释放磁盘时,使用 -p。希望完全停止 scrub 时,使用 -s。
还有两个值得了解的选项。-w 会等待 scrub 完成后再返回。脚本中需要使用此选项,以免下一步过早开始。-e 只会检查 zpool status -v 报告存在数据错误的文件。这是确认从备份恢复的文件现在是否完整且无错误的快速方法。
ZFS 每个存储池一次只运行一个 scrub 或 resilver。resilver 是更换设备后执行的重建过程,因为这两项操作都会大量使用 I/O。如果某个设备正在执行 resilver,scrub 必须等待。
运行 scrub 时如何读取 zpool status
运行 sudo zpool status tank,并根据您自己的数值进行判断,不要拿它们与他人的输出比较。scrub 运行期间,scan: 行会显示已扫描数量、已发起数量、总量、已修复数量、完成百分比和预计剩余时间。
Scanned 表示元数据阶段:ZFS 遍历块树,并收集需要读取的地址。Issued 表示数据阶段:实际发送到设备的读取请求,并按磁盘顺序排序。scrub 会对读取请求排序,因此有这两个计数器;其中 issued 才能反映实际进度。开始阶段,scanned 会远远领先于 issued,此时剩余时间估计几乎没有参考价值。应在完成前 10% 后再进行判断。
Repaired 表示根据有效副本重写的字节数。在没有冗余的存储池中,无论 scrub 发现什么,该值都会保持为 0。这用数字再次说明了前面的结论,您可以持续观察该值。
然后查看每个设备的列。READ 和 WRITE 统计设备自身报告的 I/O 错误。CKSUM 统计校验和验证失败的块;scrub 的作用之一就是填充 CKSUM 列。设备看起来正常但 CKSUM 非 0 时,这是真实错误:数据已经返回,但内容不正确。
最后一行给出结论。errors: No known data errors 表示通过。其他任何结果都意味着需要运行 sudo zpool status -v tank。该命令会列出自上次完整 scrub 以来的全部数据错误,包括受影响的文件名。请从备份中恢复这些文件,运行 sudo zpool clear tank 重置计数器,然后再次执行 scrub。每次完整 scrub 都会重新生成该列表,因此,经过一次完整且无错误的 scrub 后不再出现的文件名,确实已经不在错误列表中。
您的服务器上运行的是哪个定期 scrub 任务?
不要假定一定存在此类任务,也不要假定只有一个。具体机制取决于平台和软件包。所有平台使用相同的池格式,因此很容易忘记周边工具并不相同;ZFS 在 FreeBSD 和 Linux 上的发布方式正是这里需要关注的差异。
在 FreeBSD 上,该任务属于 periodic 系统。在 /etc/periodic.conf 中设置以下内容:
daily_scrub_zfs_enable="YES"
daily_scrub_zfs_pools="tank"
daily_scrub_zfs_default_threshold="35"daily_scrub_zfs_pools 是以空格分隔的池名称列表。将其留空会 scrub 所有池。daily_scrub_zfs_default_threshold 是未设置池专用阈值时两次 scrub 之间的天数,手册给出的默认值为 35。daily 任务每天运行,但只有超过该阈值后才会启动 scrub。
在 Linux 上,具体情况取决于发行版提供的 ZFS 软件包;有些系统会同时包含两种机制。每个池都有对应的 systemd timer,分别是 zfs-scrub-monthly@tank.timer 和 zfs-scrub-weekly@tank.timer,需要逐个池启用。Debian 和 Ubuntu 还提供 /etc/cron.d/zfsutils-linux,它会运行一个脚本,在每月第 2 个星期日 scrub 所有 ONLINE 池。在添加任何任务前,先检查当前配置:
systemctl list-timers 'zfs-*'
ls /etc/cron.d/ | grep -i zfs
sudo zpool history tank | grep scrubzpool history 才是准确答案,因为它记录了池实际启动过的 scrub 及其日期。每月执行 2 次 scrub 表明两种机制都在运行,其中一个应当停用。启用 timer:
sudo systemctl enable --now zfs-scrub-monthly@tank.timer可用空间比任何可调参数都重要
小型池的 scrub 持续时间取决于已分配数据量及其分散程度。池空间越满,这两项情况都会越差。
OpenZFS 建议将池的可用空间保持在 10% 以上。低于此值后,metaslab(分配器处理的数据块)会开始低于 4% 的可用空间阈值,分配器也会从 first-fit 切换为 best-fit。best-fit 的 CPU 开销高得多。写入延迟会上升,碎片化会随之加剧,下一次 scrub 还会更慢,因为相同数量的数据现在要通过更多、更小的读取操作处理。
因此,首先应采取的措施不是调整参数,而是删除数据。在 ZFS 服务器上,旧快照通常是主要原因,其次是无人清理的 Docker 镜像和层以及升级后遗留的内核软件包。先运行 zfs list -o space,再进行其他操作,因为它可以区分快照占用的空间和活动数据占用的空间。
然后再简要处理相关参数。在 Linux 上,可以读取当前值:
cat /sys/module/zfs/parameters/zfs_scrub_min_time_ms
cat /sys/module/zfs/parameters/zfs_vdev_scrub_max_activeFreeBSD 通过 sysctl 提供相同的参数,因此可使用 sysctl -a | grep scrub 查找对应参数。提高这些参数的值可以更快完成 scrub,但会降低应用性能。降低这些参数的值则相反。在只有一个或两个设备的池上,没有任何设置可以同时实现这两个目标,因为只有一个队列可供分配。调整参数很少能修复设计问题。如果每月执行一次 scrub 都会造成明显影响,实际原因通常是池过满或设备过慢;可调参数只能转移问题。
Scrub 时间可作为 resilver 时长的预估
resilver 执行的遍历过程与 scrub 相同:读取已分配的数据块并进行校验,然后将缺失的数据块写入替换设备。因此,scrub 所需的时间是最接近实际的重建时长预估,也能反映池在重建期间以较低冗余运行的持续时间。
ZFS 为 resilver 分配的调度优先级高于 scrub,因此对同一个池执行重建通常会比执行 scrub 更快。应将 scrub 时间视为较保守的上限。如果 scrub 需要 9 小时,就应按大致相同的时长规划重建窗口,并了解在此窗口内再次发生设备故障会导致整个池丢失。这也是选择镜像对而不是一个宽 raidz 组的实际依据,因为 raidz 是 ZFS 用来替代 RAID 5 的校验布局,它会通过读取所有仍正常工作的设备来完成重建。
对于单设备池,根本不存在 resilver。设备故障后,池也会随之不可用。此时恢复时间就是还原时间,因此应改为测量还原所需的时间。未经实际执行过的还原不能算作恢复计划。
租用磁盘后有哪些变化
在 VPS 上,块设备是虚拟的。虚拟机监控程序会提供一个卷,而卷的底层可能是本地 NVMe,也可能是自带奇偶校验的复制网络卷。这会对 scrub 产生两个影响。
首先,平台提供的冗余对 ZFS 不可见,因此 ZFS 无法使用它。如果平台在底层修复了介质错误,ZFS 永远不会看到这个问题。如果平台向上层返回了错误的块,ZFS 可以检测到它,但无法修复,因为正确的数据副本位于这层边界的另一侧。
其次,通常无法读取虚拟磁盘底层设备的 SMART(自我监测、分析和报告技术)数据。因此,VPS 上的磁盘健康监控所依赖的早期警告可能完全不可用。scrub 产生的 CKSUM 计数器会成为您能够掌握的主要信号。
如果希望 ZFS 能够修复错误,而不只是报告错误,则池中需要同一实例内的多个设备。这属于方案选择,而不是调优设置。选择存储型 VPS,而不是普通 VPS可以获得所需容量,但是否能获得两个独立设备取决于具体方案。在使用实际只是同一个卷的两个分片构建镜像之前,请运行 lsblk 并确认设备情况。我们提供 Linux 和 FreeBSD 服务器租用服务,但不提供托管式 ZFS 设备,因此 scrub 计划和备份都需要由您负责执行。这就是其中的取舍:您完全控制存储池,也完全负责其维护。
FAQ
多久应对 VPS 上的 ZFS 池执行一次 scrub?
每月一次适合大多数小型池,也符合软件包的现有设置:Debian 和 Ubuntu 使用每月第 2 个星期日的 cron 任务,FreeBSD 的 periodic 系统默认阈值为 35 天。只有在测量过 scrub 的耗时,并确认它能在空闲主机上快速完成后,才适合每周执行一次。对于只有一个或两个设备的繁忙池,每周执行 scrub 都会实际占用应用 I/O,但只能将问题发现时间提前几周。
对单磁盘 ZFS 池执行 scrub 是否没有意义?
不是,只要明确它能提供什么。没有冗余时,scrub 可以检测损坏,但无法修复损坏;元数据除外,因为 ZFS 默认会额外保留一份元数据副本。你可以在 zpool status -v 中获得受损文件的明确列表,并在其他位置仍存在完好副本时及时恢复这些文件。正确的应对方式是改进备份,因为 scrub 会明确告诉你需要恢复哪个文件。
可以暂停 ZFS scrub,之后再继续吗?
可以。zpool scrub -p tank 会暂停 scrub,暂停状态和进度会定期写入磁盘,因此在执行 export 或重启后,scrub 仍会保持暂停状态。再次运行 zpool scrub tank,即可从上一个检查点继续。不要为此使用 zpool scrub -s tank:-s 会停止 scrub,下一次执行时会从头开始。
为什么我的 ZFS scrub 这么慢?可以加快吗?
scrub 的耗时取决于已分配数据量和碎片情况,而不是磁盘容量。池的使用率超过 90% 后会变慢,因为可用空间低于 4% 的 metaslab 会使分配器从 first-fit 切换为 best-fit,随后产生的碎片会让 scrub 变成大量小型读取。释放空间通常比调整任何参数更有效。你可以提高 zfs_scrub_min_time_ms 或 zfs_vdev_scrub_max_active,让 scrub 获得更大的队列份额;但对于只有一个或两个设备的池,这部分份额会直接减少应用可用的 I/O。