HC620 使用 Btrfs zoned 时,如果文件系统接近 100%,随后突然只能读取、不能写入,最常见的线索确实是 ENOSPC、zone reclaim 没有足够工作空间,或者事务提交失败后被内核强制切换为只读。
但“写满”和“只读”只能说明故障发生时的表象,不能直接证明硬盘没有损坏。正确顺序是:
- 立即停止写入任务;
- 保存故障发生时的内核日志;
- 确认设备、挂载状态与 zoned 空间分布;
- 区分纯空间耗尽、回收受阻、事务中止和底层 I/O 错误;
- 只有证据支持纯 ENOSPC 时,才尝试重新以读写方式挂载并分批腾空间;
- 空间恢复后再做低风险整理和完整验收。
不要一看到只读就执行
mount -o remount,rw、全盘 balance 或btrfs check --repair。强制只读是文件系统的保护结果,先找出触发它的第一条错误。
本文假设挂载点是 /mnt/hc620。设备名仅用 /dev/sdX 表示,执行前必须替换成实际设备或分区。不要照抄盘符。
为什么 HC620 写满后比普通硬盘更难腾空间
Western Digital Ultrastar DC HC620 是 Host-Managed SMR,也就是由主机管理写入区域的叠瓦盘。
Linux 通常把它暴露为 zoned block device:
|
|
预期能看到类似:
|
|
实际型号、容量显示和设备名可能不同,以本机输出为准。
普通 CMR 硬盘可以在已分配扇区上覆盖写入。Host-Managed SMR 的顺序写 zone 则必须从当前写指针继续写;要重新使用一个写过的 zone,通常需要先搬走仍然有效的数据,再 reset 整个 zone。
Btrfs 从 Linux 5.12 起支持 zoned mode。它采用 COW,文件覆盖、元数据更新或删除后,旧数据块可能不再被文件系统引用,却不会立刻变成可重新写入的空 zone。
这些空间会在 btrfs filesystem usage 中显示为:
|
|
它的含义不是“永久坏掉的硬盘容量”,而是过去写过、现在已经没有引用,但仍需经过 block group reclaim 和 zone reset 才能重新使用的空间。
回收过程需要把 zone 中仍有效的数据搬到新位置。如果已经没有足够的新 zone 可接收这些数据,就可能出现暂时性 ENOSPC。
因此,在 HC620 上:
|
|
不一定立即等于:
|
|
这也是 zoned Btrfs 真正写到极限后,比普通盘更难现场处理的原因。
第一时间停止哪些任务
发现挂载点只读后,不要反复重试原来的复制任务。
先停止可能继续制造 I/O 或日志噪声的服务,例如:
rsync、rclone或下载任务;- 容器和虚拟机;
- 媒体库扫描;
- 定时快照和备份;
- scrub、balance 或大规模删除任务。
先确认当前是否有 balance 或 scrub:
|
|
不要为了执行本文命令而机械取消任务。先记录状态;如果 balance 或 scrub 正在运行并且系统仍有响应,应结合日志判断它是否就是故障现场的一部分。
查看有哪些进程仍占用挂载点:
|
|
这条命令只做观察,不会自动终止进程。
先保存故障日志,再卸载或重启
dmesg 中最有价值的往往不是最后一行 forced readonly,而是它之前最早出现的错误。
先保存完整内核日志到另一块正常磁盘,例如系统盘:
|
|
再提取相关行:
|
|
如果机器已经重启过,查看上一次启动:
|
|
如果系统没有持久化 journal,上一次启动的日志可能不存在。因此故障现场应尽量先导出日志,再重启。
同时保存基本环境:
|
|
其中 findmnt 的 OPTIONS 如果包含 ro,只能确认当前挂载是只读,不能说明为什么变成只读。
确认设备身份,禁止凭感觉填写 /dev/sdX
先从挂载点反查源设备:
|
|
再查看文件系统中的设备:
|
|
确认稳定设备链接:
|
|
如果文件系统建在分区上,后续挂载必须使用该分区,而不是整盘。比如实际源设备是 /dev/sda1,就不能写成 /dev/sda。
把实际结果记录为变量时,也要人工核对:
|
|
不要把示例中的中文占位符原样执行。
读取 Btrfs 空间分布
文件系统仍处于挂载状态时执行:
|
|
重点记录:
|
|
典型的高风险组合类似:
|
|
不要只看 df -h。Btrfs 的数据、元数据、system block group 和设备未分配空间是不同层次;COW 还需要新的空间完成事务。
Device zone unusable 也不能单独作为故障结论:
- 数值非零是 zoned COW 工作后的正常现象;
- 数值很大且
Device unallocated接近 0,才说明可回收空间与立即可分配空间严重错位; - 数值在后台回收期间可能变化;
- 即使它为 0,也不能排除元数据耗尽或底层 I/O 错误。
检查持久化设备错误计数
执行:
|
|
正常输出示例:
|
|
各项含义:
write_io_errs:下层块设备未能完成写请求;read_io_errs:下层块设备未能完成读请求;flush_io_errs:带 FLUSH 的写入失败,关系到事务落盘顺序;corruption_errs:发现校验不匹配或损坏的元数据头;generation_errs:块的 generation 与父节点期望不一致。
全为 0 是好迹象,但不是“硬盘一定健康”的证明。还要结合内核日志、SMART、HBA 和链路错误判断。
暂时不要使用:
|
|
-z 会在打印后清零计数。故障证据尚未保存时清零,会丢失重要基线。
如果确实要观察错误是否继续增加,先保存当前输出,再记录时间:
|
|
完成受控测试后再次比较,而不是只看某一次快照。
用日志把故障分成四类
第一类:纯空间耗尽或临时 ENOSPC
更支持这一类的证据包括:
|
|
同时满足:
btrfs device stats没有非零错误;- 日志中没有
I/O error; - 没有 checksum、tree-checker、corrupt leaf 或 parent transid 错误;
- 文件系统确实接近写满;
Device unallocated很少,或 zoned 回收明显受限。
这种情况才适合进入后面的“受控读写挂载并腾空间”流程。
第二类:zone reclaim 或 block group 回收受阻
日志可能出现:
|
|
此时问题不一定只是容量数字归零,还可能涉及特定内核下的回收路径、长时间锁等待或 zoned 活跃 zone 限制。
不要在这时追加全盘 balance。先保留调用栈、内核版本和完整日志,再参考站内的真实故障复盘:
HC620 上 Btrfs zoned 两次故障复盘:锁死、只读与内核排查
第三类:事务中止,但原因尚未确定
下面几行只说明结果:
|
|
真正原因通常在前面。要向上查几十到几百行,找到第一条错误、返回码和调用栈。
不能把所有 transaction aborted 都归类成写满。底层写失败、元数据验证失败和内核缺陷同样可能中止事务。
第四类:I/O、校验或结构错误
出现以下任一线索,应停止“删点文件就能修好”的流程:
|
|
这时优先保护数据副本,检查磁盘、SAS/SATA HBA、线材、供电和内核 zoned 错误。不要把设备计数非零简单解释为“只是满盘”。
先做只读挂载与数据抢救
如果当前挂载还能读,先复制最重要且没有其他副本的数据。
若需要卸载后重新挂载,先退出占用目录的 shell,再执行:
|
|
确认已经卸载:
|
|
没有输出才表示该挂载点不在当前挂载表中。
先以只读方式挂载:
|
|
然后复制重要文件到另一块健康磁盘。只读抢救时不要把目标目录放回 HC620 本身。
如果普通只读挂载失败,不要随机叠加 rescue= 参数。不同错误需要不同救援选项,错误参数可能掩盖日志或改变读取结果,应根据具体报错再决定。
什么时候才允许尝试重新读写挂载
同时满足以下条件时,可以考虑一次受控尝试:
- 已保存故障日志;
- 重要数据有其他副本,或已先完成只读抢救;
- 日志的第一原因明确指向
ENOSPC; - 没有 I/O、校验、tree-checker 或 zone write pointer 错误;
btrfs device stats没有异常计数;- 没有卡住的旧内核任务;
- 文件系统可以干净卸载。
先卸载只读挂载:
|
|
再做一次普通挂载,不使用 remount,rw 强顶当前错误状态:
|
|
立即验证:
|
|
如果挂载选项仍然包含 ro,或者日志再次出现事务中止,停止尝试,不要循环卸载、挂载。
如果成功显示 rw,也不要马上恢复复制服务。读写窗口的第一个任务是分批释放已知可删除的数据。
读写恢复后怎样安全腾空间
优先选择已经确认有其他副本、可以重建、并且体积大的目录。
先查看一级目录大小:
|
|
不要直接照抄:
|
|
应先打印真实目标,确认没有变量展开错误:
|
|
确认目标正确后再删除:
|
|
对于多 TB 数据,建议按独立子目录或批次删除,不要同时进行大规模写入。
每批后检查:
|
|
btrfs filesystem sync 可能需要一段时间。命令返回前不要把“目录消失”误当成空间已经完成回收。
应先以释放数百 GiB 的量级为目标,而不是只删几 GiB 后马上继续灌满。具体安全余量取决于工作负载、zone 大小、元数据和回收效率,不能给所有 HC620 一个绝对比例。
长期使用中,不建议把 zoned Btrfs 维持在 99%~100%。要同时预留:
- 日常新写入空间;
- COW 事务空间;
- 元数据增长空间;
- zone reclaim 搬运仍有效数据的工作空间;
- 应急删除和维护窗口。
删除后为什么空间可能没有立即回来
删除文件主要解除引用并更新元数据。对于 zoned 设备,相关 zone 里可能还有有效 extent,Btrfs 必须先迁走它们,才能 reset zone。
因此可能看到:
- 文件已经删除;
Used有所下降;Device zone unusable暂时上升;Device unallocated没有按删除量同步增加;- 后台 cleaner 继续产生 I/O。
这不一定表示删除失败。观察一段完整的受控回收周期,并以日志和 btrfs filesystem usage -T 的趋势判断。
如果数值长期不变化,同时出现 cleaner 阻塞、事务错误或 D 状态任务,就应按回收路径故障处理,而不是继续大量删除。
balance 只能在空间恢复后逐步执行
balance 会搬移 block group。大多数 balance 操作需要先创建新的 block group 作为工作空间,因此满盘时直接运行无过滤器的 balance 可能再次触发 ENOSPC。
不要一上来执行:
|
|
先确认当前没有 balance:
|
|
在日志稳定、已经释放空间、设备错误计数没有增长的前提下,可以先处理完全未使用的 block group:
|
|
usage=0 不需要额外工作空间,是 Btrfs 官方文档针对 ENOSPC 给出的低风险起点。
完成后检查:
|
|
只有空间和日志都稳定时,才考虑低阈值:
|
|
再次观察后,才可能提高到:
|
|
这些数字不是必须逐级执行的固定处方。阈值越高,可能搬移的数据越多,所需工作空间和 I/O 也越大。
如果之前已经出现 btrfs-cleaner、block group 删除或 zone finish 路径卡死,不要把 balance 当成自动修复。它可能重新进入相近代码路径,应先查清内核问题。
不要在故障盘上做的操作
不要强制 remount 为读写
避免:
|
|
如果内核刚刚因为错误把文件系统强制只读,直接 remount 会跳过“保存日志、卸载、重新评估”的边界,而且通常不能消除导致只读的根因。
不要盲目执行全盘 balance
避免:
|
|
满盘 balance 需要搬移数据,可能比原故障需要更多临时空间。
不要运行 btrfs check --repair
避免:
|
|
Btrfs 官方文档明确警告:除非得到开发者或有经验人员针对具体错误的建议,否则不要使用 --repair。
如果确实需要离线结构检查,必须先卸载,并默认使用只读模式:
|
|
但对 14TB 或 15TB 文件系统,这项检查可能耗时很长、占用大量内存并产生持续读取。只有纯 ENOSPC 证据时,通常不需要把它当作第一步。
不要先清零设备错误统计
避免在保存证据前执行:
|
|
清零不会修复硬盘、线材或 HBA,只会抹掉比较基线。
不要继续边删边写
Host-Managed SMR 上数 TB 删除、zone reclaim 和数 TB 新写入重叠,会显著扩大故障面。
更稳妥的流程是:
|
|
空间恢复后的验收
恢复 rw 只是开始,不能作为最终成功标准。
验证挂载状态
|
|
确认:
- 源设备正确;
- 文件系统为
btrfs; - 挂载选项包含
rw; - 没有挂错子卷或分区。
验证空间分布
|
|
不要只追求 Device zone unusable 归零。更重要的是:
- 已经有足够可用空间;
Device unallocated或可分配空间恢复;- 元数据不再逼近极限;
- 回收过程中没有新错误。
验证错误计数没有增长
|
|
与故障后保存的基线比较。如果 write_io_errs、flush_io_errs、corruption_errs 或 generation_errs 增长,停止恢复大规模写入。
验证内核没有再次强制只读
|
|
关注新产生的日志,不要把几小时前的旧错误误判为刚刚复发。
做小规模写入测试
先写到一个明确的测试目录:
|
|
删除测试文件并再次提交:
|
|
随后再次检查日志、空间和设备统计。
1GiB 测试通过只能证明小规模写入成功,不能证明数 TB 持续写入一定稳定。正式任务应从小批量恢复,并保留监控。
有冗余或备份后再安排 scrub
scrub 会读取全部已分配数据并验证校验和,可能持续很久。不要在刚恢复的满盘上立刻与 balance、复制和删除同时运行。
确认文件系统稳定且重要数据有副本后,再安排独立维护窗口:
|
|
完成后查看:
|
|
单盘 single 数据没有另一份镜像可供 Btrfs 自动修复。scrub 能发现校验问题,不等于一定能修复。
长期避免再次写满的做法
给写入任务设置容量停止线
不要让备份脚本只看命令退出码。写入前后都记录:
|
|
当可用空间低于你的维护阈值时,停止接收新数据并告警。
阈值不应只按“还能放下下一个文件”计算,还要包含 COW、元数据和 zone reclaim 的空间。
把大量删除和大量写入错开
推荐:
|
|
不要安排 rsync、快照删除、scrub 和 balance 在同一个维护窗口中争抢 I/O。
对归档数据保留外部清单
可为重要目录生成校验清单,并把清单复制到另一块磁盘:
|
|
文件名中包含换行符时,上面的文本清单格式仍可能不便处理;如果数据来源会产生特殊文件名,应使用支持 NUL 分隔的专用清单流程。
不把单盘文件系统当成备份
Btrfs 校验和能发现静默损坏,但单盘数据 profile 通常没有第二份数据副本可恢复。
HC620 上的数据至少还应有:
- 另一块本地磁盘副本;
- 另一台机器或异地副本;
- 可定期验证的对象存储或离线副本。
常见问题
Device zone unusable 很大,是否说明盘坏了
不一定。它表示过去写过、现在因 COW 不再被引用、但尚未经过回收和 zone reset 的空间。
应同时看 Device unallocated、数据与元数据使用率、内核日志、回收趋势和设备错误计数。
删除文件后能马上恢复同等容量吗
不能保证。删除先改变文件系统引用,底层 zone 还可能需要搬移有效数据并 reset。
正常重新挂载成功,是否就能继续写满
不能。应先腾出明显余量,确认日志和错误计数稳定,再做小规模测试,最后分批恢复业务。
write_io_errs 为 0,是否排除硬盘问题
不能完全排除。它只说明 Btrfs 记录的该类持久化计数没有写失败;还要检查内核块层日志、SMART、HBA、线材和供电。
可以使用 mount -o recovery 吗
不要把旧教程里的 recovery 当成通用修复开关。当前 Btrfs 的救援挂载选项有明确适用错误,而且通常要求只读挂载,应根据实际日志选择。
为什么 df 显示还有空间却报 ENOSPC
Btrfs 的 COW、数据与元数据 block group、事务预留和 zoned 回收都需要空间。df 展示的文件级可用估算不能完整表达这些内部约束。
要不要把元数据从 DUP 改成 single
不要仅为了挤出空间就临时改变 profile。profile 转换本身要搬移 block group,需要工作空间和大量 I/O;zoned 模式支持的 profile 还受内核与工具版本约束。
usage=0 balance 为什么相对安全
因为它只扫描并回收完全未使用的 block group,官方文档说明该过滤器不需要额外工作空间。
“相对安全”仍不等于适合所有故障。如果日志显示回收路径卡死或结构错误,先停止并分析。
能否直接断电重启
如果系统仍有响应,应先保存日志、停止任务、等待必要事务并正常卸载。强制断电会增加未提交写入和故障分析的不确定性。
如果内核任务长时间处于不可中断 D 状态、机器无法正常关机,应先通过另一台机器或远程控制台保存尽可能多的日志,再评估重启风险。
一份可执行的故障清单
按顺序执行,不要跳到 balance 或 repair:
|
|
需要收集哪些输出才能继续判断
如果仍无法确认属于哪一类,收集以下输出时隐藏序列号等敏感信息,但不要删掉错误上下文:
|
|
根据这些输出,通常可以把下一步收敛到:
- 单纯写满,可以受控腾空间;
- zone reclaim 没有工作空间或回收路径阻塞;
- Btrfs 事务中止,需要追溯首条错误;
- HC620、HBA、线材或供电出现真实 I/O 故障;
- 文件系统出现校验或结构错误,需要先抢救数据再由有经验人员处理。
参考资料
- Western Digital Ultrastar DC HC620 Data Sheet
- Btrfs 官方文档:Zoned mode
- Btrfs 官方文档:btrfs-filesystem
- Btrfs 官方文档:btrfs-balance
- Btrfs 官方文档:btrfs-device
- Btrfs 官方文档:btrfs-check
HC620 写满后变只读,最重要的不是尽快让它重新显示 rw,而是先判断内核为什么保护性只读。只有证据明确支持纯 ENOSPC,才应在保存日志和数据副本后受控腾空间;一旦出现 I/O、校验、结构或 zone write pointer 错误,就应停止写入,把工作重点转向数据抢救和硬件链路排查。