磁盘少了 100G 却找不到文件——一次 macOS 服务器 Spotlight 索引失控的误诊复盘
一台常年跑着各种后台服务的家用 Mac mini,内置 245G 盘长期 90% 满,可用只剩 20G。最直觉的反应是”哪个服务又写爆日志了”,于是 sudo du 全盘一扫——实际文件加起来只有 70G 出头,但系统报告”已用”将近 190G。差出来的 ~130G,既不在任何文件里,也找不到对应的快照或进程占用。
这篇是这个问题的完整复盘。它有意思的地方不在于最终的修复(其实就两条命令),而在于第一次排查给出了一个完全错误的结论,并且这个错误结论看起来证据链还挺完整。
现象
# 容器层:卷占用 91%,只剩 22G 未分配Capacity In Use By Volumes: 222.9 GB (91.0% used)Capacity Not Allocated: 22.2 GB (9.0% free)
# 其中 Data 卷自己就吃了 190.5GVolume Used Space: 190.5 GB但把 Data 卷从头扫一遍,能对上号的文件只有 70G 左右。~130G 既看不见、又删不掉——删任何文件,可用空间纹丝不动。
第一次排查:一条看起来很扎实的错误路线
第一次排查的思路是这样的:既然”已用”远大于”文件总量”,那这 130G 一定是被某种不以普通文件形式存在的东西占着。macOS / APFS 上,这类”隐形占用”的常见嫌疑有三个,于是逐一排除:
- APFS 快照:
diskutil apfs listSnapshots和tmutil都报告 Data 卷无快照 → 排除 - 被删除但仍被进程打开的文件(deleted-but-open):
sudo lsof扫下来这类 fd 合计几乎是 0 → 排除 - 文件系统账目损坏:
fsck_apfs -n(只读检查)报告健康、账目一致 → 看起来也没问题
三个嫌疑全部排除,剩下的解释似乎只有一个:APFS 的空间统计出了 bug,有 130G 已经没有引用、但没被回收。而 APFS 在挂载状态下没法做完整修复(fsck_apfs -n 只读 OK,但真正的修复需要卸载卷),于是给出的结论是:
重启进恢复模式 → 磁盘工具 First Aid;若无效则备份后抹盘重装。
这个结论错得很彻底,但当时看起来证据链是闭合的:每一个”正常嫌疑”都被一条命令明确排除了。
错在哪?错在一个没被质疑的前提:「du 扫不到」被默认等同于「文件不存在」。整条排查从一开始就跳过了最朴素的一种可能——文件就在那儿,只是我没真正扫到它。
关键转折:换个姿势扫盘
第二次排查没有顺着”APFS 一定有 bug”往下走,而是回到原点先质疑那个 70G:我真的扫全了吗?
这里有个 macOS 特有的坑。Catalina 之后,系统是只读的 System 卷 + 可写的 Data 卷,通过 firmlink 拼成你看到的 /。如果你扫的是 /,du -x(不跨文件系统)会在 firmlink 边界停下,根本进不了 Data 卷的真实内容。正确的做法是直接扫 Data 卷的真实挂载点,而且必须 root(否则 .Spotlight-V100 这类系统目录会被静默跳过):
sudo du -xh -d1 /System/Volumes/Data 2>/dev/null | sort -rh | head一把命中:
177G /System/Volumes/Data112G /System/Volumes/Data/.Spotlight-V100 ← 就是它 40G /System/Volumes/Data/Users7.6G /System/Volumes/Data/System ...130G 的”幽灵”根本不是幽灵,是 Spotlight 索引目录 .Spotlight-V100 膨胀到了 112G(正常状态下这玩意儿就几百 MB)。第一次排查里那个”只有 70G”的数字,就是因为没真正以 root 扫进这个隐藏目录而漏掉的——不是 APFS 在骗人,是扫盘姿势在骗自己。
再往里看一层,确认是单个索引 store 失控:
112G .Spotlight-V100/Store-V2/DBD51353-...
# 里面一堆从去年累到现在、从没被清理的索引代际:8.0G 719.indexPositions (今年 5 月)1.8G 54.indexIds (去年 11 月)1.8G 53.indexIds (去年 11 月)1.8G 51.indexIds (去年 8 月)...根因:索引代际只增不减
先说能确定的事实:Spotlight 的 mds_stores 把倒排索引按”代际”存放(就是那些带编号的 .indexIds / .indexPositions 文件)。正常情况下它会周期性地把老代际合并压缩、删掉旧文件。而这台机器上,这些代际文件的时间戳从去年 7 月一路连续排到现在、一个都没被删过。所以可以确定:索引压缩长期没有成功执行过,索引只增不减地堆到了 112G。这一条不是推断,是文件时间戳直接证明的。
至于”为什么压缩一直没成功 + 为什么涨得这么猛”,下面是我的推断,没有去读 mds_stores 的源码,按可能性排:
- 高频写盘把索引喂炸。这台机器跑着摄像头 NVR、还有几个持续写日志的后台服务。每次文件增删改都触发重新索引,录像分段和日志轮转意味着每天上万次修改事件,Spotlight 大概率陷入永久重建。尤其是大文本日志:一个几 G 的日志文件能产出巨量索引 token,撑出 1.8G 的
.indexIds很合理。 - 7×24 的服务器永远不空闲,压缩窗口被挤掉。索引压缩是重活,通常需要机器相对空闲才会跑。一台从不空闲的服务器,
mds_stores反复被打断,压缩永远跑不完,代际只堆不并。 - 可能有某一代索引损坏,导致合并直接失败、旧代际永不回收。“单调猛涨从不回落”这个形态,和这个猜测吻合,但我没有进一步验证。
需要强调的是:后面的修复方案不依赖这几条推断成不成立。哪怕喂炸索引的具体机制我猜错了,“索引已经失控膨胀、需要清掉并阻止它再长”这个事实和对应的修复都独立成立。
修复:两条命令,SSH 全程搞定
这台机器只能 SSH 够到,所以”进恢复模式”那条路本来就走不通——好在根本不需要。.Spotlight-V100 是纯搜索索引缓存,删掉 macOS 会自己重建,零数据风险。而且对一台无头服务器来说,本地 Spotlight 搜索毫无用处,正确的做法是直接永久关掉:
# 1. 全卷关闭索引(持久生效,无头服务器不需要 Spotlight,也根治复发)sudo mdutil -a -i off
# 2. 擦除索引 store(关索引后 -E 只擦不重建)sudo mdutil -E /sudo mdutil -E /System/Volumes/Data效果立竿见影:
| 之前 | 之后 | |
|---|---|---|
| Data 卷已用 | 178G | 66G |
| Data 卷可用 | 20G | 132G |
| 容器空闲 | 22G | 141.5G |
.Spotlight-V100 | 112G | 764K |
副作用确认:关掉 Spotlight 后失去的是「本地按内容/元数据搜索文件」的能力——Finder 搜索、mdfind 这类会失效。对一台只跑服务、靠 SSH 操作的服务器来说,这个能力本来就用不到,关掉无感。需要找文件直接 find / fd 即可,反而更可控。如果哪天确实要恢复,sudo mdutil -a -i on 一条命令就回来了。
复发监测也简单——sudo mdutil -a -s 应当一直显示 Indexing disabled。极少数情况下 macOS 大版本更新后可能把某个卷的索引重新打开,届时重跑第一条命令即可。
通用经验
这次踩坑真正值钱的不是”Spotlight 会膨胀”这个知识点,而是怎么避免被自己的测量手段误导:
- 在 macOS 上量盘,要扫
/System/Volumes/Data的真实挂载点,而且必须 root。扫/会被 firmlink 边界和du -x拦在 Data 卷外,非 root 又会静默跳过.Spotlight-V100、.fseventsd这类系统隐藏目录——两个因素叠加,就会扫出一个看似可信、实则严重偏小的数字。 - 「工具没找到」不等于「东西不存在」。当”已用”和”实测”对不上时,先怀疑测量手段,再怀疑系统有 bug。这次错误排查的全部问题,就出在把”
du没扫到”直接升级成了”APFS 账目损坏”。把”我可能没扫全”放进嫌疑列表,是最便宜也最容易被跳过的一步。 - 诊断结论的代价越大,越要回头质疑前提。当排查把你逼向”恢复模式 / 抹盘重装”这种核选项时,那恰恰是停下来重验最初那个数字的信号——而不是顺着它往下冲。
- 无头服务器默认关掉 Spotlight。它对没有图形界面的机器零价值,却会因为日志/录像这类高频写盘悄悄膨胀。与其等它炸,不如装好系统就
mdutil -a -i off。