Every post now carries one unique local-ComfyUI/Ideogram 4 base frame, composited into EN + ZH share cards, a banner and list thumbs. - the shipped prompts_catalog held only 21 distinct metaphors for 72 posts (16 shared one visual), so all 72 were rewritten as unique object+state metaphors and 10 frames re-rolled after OCR found pseudo-text rendered at legible size - render_from_art.mjs emits JPEG by default (558 KB PNG -> 84 KB mozjpeg q85); the ZH 1200x630 card did not exist before this batch - 288 frontmatter fields migrated; the 144 v1 PNGs are gone - PostList's thumb helper keys off the base slug (a ZH card is zh-<slug>.jpg while the thumb is <slug>-800.webp, which 404'd the whole ZH listing) - [...slug].astro's <picture> regex accepts .jpg - the repo now carries the art inputs (metaphors.json, captions/<slug>.json, refreshed prompts_catalog.json) so a frame is reproducible from git alone
12 KiB
title, description, pubDate, category, tags, ogImage, banner
| title | description | pubDate | category | tags | ogImage | banner | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| SMART 显示健康,RAID 却在悄悄损坏数据 | 两块看起来完全健康的 NVMe 组成的 btrfs RAID1 阵列,静默地把 Windows 虚拟机喂成了全零字节。我靠校验和取证抓住它——但根因(M.2 转接卡链路故障 vs 批次缺陷)至今仍未定论,现在两块盘测试都干净。 | 2026-09-16 | devops |
|
/og/zh-when-smart-says-healthy-but-your-raid-is-corrupting-data.jpg | /banners/when-smart-says-healthy-but-your-raid-is-corrupting-data.jpg |
所有存储指南都告诉你同一件事:相信 SMART、检查事件日志、相信你的文件系统。我的硬件不这么想。2026 年 9 月,一个 btrfs RAID1 阵列——两块企业级 NVMe 盘镜像,我用每个能想到的工具查它都说**「健康」**——却在悄悄地把我的 Windows 11 虚拟机喂成全零字节。不是看得见的故障,是静默损坏。
下面是完整故事:损坏是怎么浮现的、哪个校验和出卖了它——以及为什么最终定论如今变得不确定。
更新(2026-09-16):根因至今未定论。 下面的「两块盘都换」是我当时的判断,但后来一次不相关的故障让我不得不重新审视。现在两块 NVMe 测试都干净,而最强的头号嫌疑是两块盘共用的那块 M.2 转接卡——转接卡上的链路层故障,恰好会复现我当初归咎于硬盘的那种「两块盘关联地吐出全零」的损坏。SMART 对链路故障完全无感,所以无法排除它。我在一篇配套文章里写了那次警醒(一颗「死掉的」SATA SSD 其实只是坏线)。先读下面的原始分析,再看更正——然后还有续集:两周后同一个全零指纹出现在完全不同的硬盘堆栈上,这次终于挖出了根因。见《换了不同的硬盘,同样的损坏又来了——真凶是 TRIM,不是 SSD》。
更新:为什么我不再怪两块盘
这篇文章发出后,有两件事改变了我的想法。
1. 现在两块盘测试都是干净的。 在当前条件下,两块 PM9A3 读写都正常。如果它们带着 9 月还在活跃损坏数据的批次级缺陷,应该会挂掉一轮持续的写入+校验(verify=crc32c)泡测——但它们没有。
2. 链路故障才是真正的教训。 就在这次事故之后,我在另一颗盘上撞见了完全相同的故障签名(一颗 Seagate IronWolf 110 狂报 WRITE FPDMA QUEUED 超时、mkfs 失败、然后静默——教科书般的「死盘」)。结果是坏在 SATA 数据线上。一条不稳定的链路会复现下面我归咎于硬件的每一个症状:在算出 CRC 之前就已丢掉的超时 I/O、真的丢掉的页面,以及——如果两个 RAID1 成员共用同一块有问题的 M.2 转接卡或插槽——两块盘关联地损坏。这是唯一一个不需要任何硬盘缺陷、却能解释「两个成员用同一种方式撒谎」的答案。
所以我没法再诚实地断言「盘有问题」。它们可能一直好好的,损坏可能来自它们共同坐着的 M.2 转接卡。注意:这并不代表这次事故是虚惊一场——真实数据确实读不回来过,无论根因是什么,我都把它移走、迁移数据、并逐字节验证过。但正确的教训是「先排除链路,再给盘判死刑」,而不是「同批次盘不可信」。
完整的链路对盘排查,请看 "我差点把这颗 SSD 送修——真正坏的是 SATA 数据线"。
原始分析(保留供透明考量)
下面是我当时得出的结论。这是当时证据下最站得住的解读,而且可能仍然是对的——只是我无法排除别的可能——但请把它当作事故报告来读,而不是最终定论。
为什么这件事重要
损坏状态的磁盘是最糟的存储故障,因为它最容易漏掉:
- SMART 看起来完美。 零媒体错误、几乎满余量,而且是能扛企业级写入负载的盘。
- 阵列有冗余。 btrfs RAID1 把每个块镜像到两块盘上。当两个成员用同一种方式撒谎时,冗余毫无意义。
- 故障是静默的。 数组状态没有红色警告、没有硬盘报错、没有硬件事件——只有虚拟机读回垃圾,直到备份恢复成了唯一的选择。
如果你的数据在一切看起来都正常的时候悄悄坏掉,「备份」就不是偏执——它是全部。这次事故让我明白:一条可测试、可验证的恢复胜过任何阵列状态屏幕。
环境
- unRaid 7.x,Intel i9-13900K,Windows 11 虚拟机是主力工作站(GPU 直通)。
- 缓存池: 两块 Samsung PM9A3 3.84TB NVMe 组 btrfs RAID1。而且两块是同一生产批次(同一个控制器/固件版本
GDC7702Q)。 - Windows 虚拟机的虚拟磁盘(
vdisk1.img、vdisk2.img)就在这个池的domains/共享里,另外还有 Docker 数据根目录。 - 虚拟磁盘没有设
NOCOW——所以每次读取都会被 btrfs 校验和验证。也正因为这个,我最后才抓得住它。
症状
我开始发现缓存读取慢得离谱,主机命令也卡住:cat、读 /proc/loadavg、甚至 docker image ls 都在超时。那块坏 NVMe 正以 45–75 MB/s 被狂读,每次读都塞满整台主机的 I/O。本该几秒跑完的 docker system prune 直接挂死。
然后真正的麻烦来了:Windows 虚拟机开始狂报读错误,回来的是错的数据——满文件的全零字节、guest 里的校验和失败,最后主力 VM 直接引导不了。
取证:csum 0x8941f998
btrfs 在读校验失败时会往内核日志记一条校验和不匹配。同一个值反复出现:
btrfs: checksum verify failed on <device> wanted 0x8941f998 found <other>
这个特定值是最重要的线索。0x8941f998 就是一整块零字节的 CRC32C。 它每次都出现、跨越互不相关的文件和 inode,绝不是巧合。正在发生的不是随机比特腐烂,而是盘稳定地返回全零块,而这些零填充的读取正是 btrfs 校验失败的对象:
- 验证一下:零填充块的
crc32c就等于0x8941f998。 - 同一个值反复出现 → 读取是确定性地吐出空数据,不是偶尔翻几个比特。
两块虚拟磁盘都中招(vdisk1.img inode 2358574、vdisk2.img inode 2362817),正好对上「存储层在把零当成被请求的数据返回」的图景。
破绽:永远不生效的「自愈」
btrfs RAID1 本应自愈:一个成员坏块、从镜像重建、写回好数据。但我一遍遍 scrub、重读同一块(仍然全零的)数据后发现,重写根本不落地。每次重建「成功」后,立刻重读同一 extent 又吐出零。
这是关键区别:
- 一块只读不出来的盘是读问题——自愈应该能从镜像救回来。
- 一块连回调也无脑吞垃圾存进去的盘是写或控制器问题——镜像救不了你,因为两条路径都对同一个错误答案达成一致。
当自愈重写一直不生效,你就不在「坏一个扇区」的范畴了,而是「这块盘在写入上撒谎」——RAID 冗余救不了。
为什么两块盘都得换(我当时的推理)
这是当初「都换掉」决定的依据。动手前先读上面更新里的更正——一块共用的 M.2 转接卡链路故障,在无需任何硬盘缺陷的情况下也能对上这份证据。
数据指向的是同批次盘的硬件缺陷,而不是磨损或一次性的偶发事件:
- SMART 干净(0 媒体错误、几乎满余量,
nvme0大约写了 586 TiB——对能扛企业级负载的盘来说算重负载但仍正常)。 - 两块盘同批次、同控制器/固件(
GDC7702Q)。 - 损坏以相关的方式同时命中两个 RAID1 成员——镜像「死一块也能幸存」的整个意义,因为两块盘正以同一种方式一起死掉而完全失效。
面对批次级的关联缺陷,留下那块「看起来健康」的盘当幸存成员是假省心。我从备份恢复了 Windows 虚拟机,然后把两块盘都换掉。
我同时做的事:把数据迁出那个池
在换盘之前,任何还值得读的数据都得搬出这个坏池——而且要验证,不能想当然:
- Docker 数据根目录迁出缓存:
docker挪到/mnt/user/docker(在disk1上,overlay2)。整棵树通过路径、大小、mtime 逐文件比对、并与字节对比交叉验证,共 3,997,569 个文件。 - 缓存的
isos/、system/、swapfile搬到disk1;isos/(约 13 GB)用diff -rq检查 → EXIT=0,无差异。 - 所有搬走的文件都在删除缓存副本之前先跟源比对过——没验证过的副本绝不删。
因为存在读侧损坏(全零块)和写侧损坏(自愈不落地),那个池里的任何东西,没有镜像、没有校验和、没有新鲜备份可对照,我就一律不信任。
如果重来我会怎么做
- 在需要它之前就先测试恢复虚拟机备份。 我当时确认了备份存在,但损坏事故不是该第一次学恢复流程的时候。定时做的测试恢复既能发现备份问题,也能发现静默的存储损坏。
- 把同批次的两块盘当成一块盘。 两块 PM9A3 同批次同固件。对需要长期搭档的缓存池(任何 RAID1)这是关联失效风险——优先选不同批次的盘,或者至少在信任镜像前把这对盘狠狠测一遍。
- 主动检测,而不是被动祈祷。 正是 btrfs 校验和让它在噪声里浮出来。不开
NOCOW意味着文件系统在验证每一次读取;一个带校验的数据池,就是「当场抓住并恢复」和「把损坏文件带出去几个月」之间的分水岭。 - 别用 SMART 判断闪存。 一块看起来健康却照样损坏的 NVMe,事件计数什么也说明不了——固件级的缺陷可以躲过所有 SMART 属性。
结果
一台 Windows 虚拟机从备份恢复、数据安全迁出那个坏池、Docker 数据根目录也搬了家——外加一条事后再推翻的教训。 我当时换掉了两块盘,但现在我没法确定它们就是元凶:证据同样支持一块共用的 M.2 转接卡链路故障,而且现在两块盘测试都干净。(完整排查见 "我差点把这颗 SSD 送修——真正坏的是 SATA 数据线"。)
事故被控制住,是因为损坏被校验和抓住了、而不是在里面泡了几个星期——这一点无论根因是什么都成立。但更诚实的教训,比「把盘换掉」这句要更广、也更不戏剧化:
- 一块「正在损坏」的盘,并不等于盘本身坏了。 在给硬件判死刑前,先排除链路:重新插拔/更换线材、换到别的口/转接卡/插槽,重跑相同的失败操作。如果故障跟着口/转接卡走,那就是连接的问题。
- SMART 看不见链路故障。 零媒体错误和几乎满余量,对一条不稳定的线材或转接卡毫无意义——超时在算出 CRC 之前就把 I/O 丢了。
- 同批次盘共用一条通道,无论谁坏都是共享失效风险。 不管损坏来自盘还是转接卡,一对盘坐同一条故障路径上,就是穿着两个序列号的单点故障。
- 给你的数据加校验、并提前测试恢复备份。 把数星期静默损坏变成一次被控制住的事故的,是这一点——不是我那部分诊断。