前作处理了 lsd 大量消耗 CPU 的问题。排查的过程中顺手看了一眼磁盘:这台 MacBook Pro 的数据卷一共 466 GB,已经用掉 389 GB,可用的只剩 42 GB,占用 91%。用 du 逐层往下看,~/Library/Caches 一个目录就有 106 GB,其中 102 GB 在 CloudKit 底下。
清掉这些缓存不难,难的是弄清楚它们是怎么来的。下面先交代清理的过程,再回头讨论三件事:这些缓存从哪来,以后怎么尽早发现,以及怎么避免再攒出这么多。
前置知识
bird
bird 的完整路径是 /System/Library/PrivateFrameworks/iCloudDriveCore.framework/Versions/A/Support/bird,从它所在的框架就能看出来,这是 iCloud Drive 的守护进程。iCloud Drive 的文件放在 ~/Library/Mobile Documents/ 下面,iCloud 云盘里的文件在其中的 com~apple~CloudDocs,其余以 App 命名的子目录是各个 App 自己的 iCloud 容器。bird 用的缓存则放在另一处,即 ~/Library/Caches/CloudKit/com.apple.bird/。
只在云端的文件
打开「优化 Mac 储存空间」以后,macOS 会在空间紧张时把已经上传的文件移出本地,只留一个占位。defaults read com.apple.bird 的输出里有一行 "optimize-storage" = 1,从名字看,这台机器上这个选项是开着的。
只在云端、没有下载到本地的文件,ls 照常给出完整的大小,实际却不占磁盘。以 iCloud 云盘里存放系统镜像的一个目录为例:
1 | cd ~/Library/Mobile\ Documents/com~apple~CloudDocs/<某个目录> |
ls 列出的两个 macOS 恢复镜像(.ipsw),一个 16 GB,一个 14 GB;du 给出的整个目录却是 0B。这些文件的文件名也没有 .icloud 后缀。因此,要看 iCloud Drive 在本地实际占了多少空间,应当用 du,不能看 ls 给出的大小。
APFS 的克隆
在 APFS 上复制文件时,可以用克隆(clone)代替真的复制,cp -c 就是这么做的。克隆出来的新文件与原文件共用同一批数据块,复制几乎不花时间,也不占额外的空间;之后哪一方被修改,被修改的部分才另外分配空间。反过来,删掉其中一份,只要另一份还在,那批数据块就不会被释放。
排查与处理
缓存的位置与内容
先看 CloudKit 下面各个子目录的大小:
1 | 101G ~/Library/Caches/CloudKit/com.apple.bird |
其他服务的缓存都在几十 MB 以内,只有 bird 的缓存大得出奇。继续往下翻,这 101 GB 几乎都在 com.apple.bird/<hash>/MMCS/ClonedFiles/ 里,<hash> 是一串 40 位的十六进制目录名,下文统一这样写。ClonedFiles 里有 3643 个文件,文件名形如 documentContent__<UUID>_fileContent,另有一些以 _thumb1024 结尾,看名字是缩略图。作为对比,~/Library/Mobile Documents 整个目录是 29 GB,也就是说,bird 的缓存比 iCloud 在本地的全部数据还多出 70 GB 左右。
ClonedFiles 里最大的一个文件有 10.6 GB,是一个 ZIP 包,修改于 10 月 29 日,里面的第一个文件叫 043-69093-778.dmg。用这个文件名和它的精确大小,在本机都搜不到对应的文件。除了它,超过 100 MB 的文件一个也没有,其余的大多在 53 MB 到 56 MB 之间。按修改时间的月份统计,3643 个文件里有 3598 个是 10 月份的。
同步状态
看缓存的时候,bird 的 CPU 占用是 92.3%,brctl status 里是 client:needs-sync。bird 的日志里,EINTR: client gather phase was invalidated 在 11:48:28 到 11:48:39 之间出现了 8 次,还有被取消的下载:
1 | bird[435] [com.apple.cloudkit:OP] Finished operation <BRFetchRecordsOperation: …, shortened-name=Download (user initiated) }, …, stateFlags=executing|cancelled, <private>> with error: <CKError 0x7fd4d8674890: "Operation Cancelled" (20); …> |
我没有对 bird 做任何操作。十几分钟后再看,bird 的 CPU 占用是 0.0%,brctl status 变成了 client:idle。
保留下载的文件
iCloud 云盘里有几项我设过「保留下载」,清理时不能碰它们。这个设置记在扩展属性里,属性名是 com.apple.fileprovider.pinned#PX。需要注意,末尾的 #PX 也是名字的一部分,执行 xattr -p com.apple.fileprovider.pinned 会报 No such xattr,所以要用 grep 匹配:
1 | D=~/Library/Mobile\ Documents/com~apple~CloudDocs/<某个目录> |
带这个属性的有四项,两个目录和两个脚本文件。最大的那个目录有 16 GB、996 个文件,下文称作「保留目录」。它在 Mobile Documents 底下,要删的缓存在 Caches 底下,两者是不同的目录。保险起见,每一步前后都核对一遍保留目录的文件数与 du。
删除
先把 ClonedFiles 改个名,确认没有问题再删。改名随时可以改回去,删除不能。
1 | cd ~/Library/Caches/CloudKit/com.apple.bird/<hash>/MMCS |
改名之后,bird 仍是 client:idle,也没有重新建一个 ClonedFiles。删除前可用空间是 56 GB,删除后是 156 GB。而后又删掉了 MMCS 下剩余的一个 7.3 GB 的临时文件,可用空间到了 162 GB。保留目录始终是 996 个文件、16 GB。
开头的 42 GB 与删除前的 56 GB 对不上,是因为中间还有两件事。第一,动缓存之前,我删掉了一份 iPhone 备份和一个 Time Machine 本地快照,可用空间到了 61 GB,后文「预防」一节会说到快照。第二,改名前后,可用空间从 61 GB 落到了 56 GB,同一段时间里,那个临时文件多占了 7 GB 左右,「成因」一节细说。
成因
这次其实有两样东西在占空间:ClonedFiles 里的 101 GB,以及 MMCS 下那个会自己长大的临时文件。两者要分开解释。
缓存里的是什么
缓存里的文件名只有 UUID,看不出原件是谁。线索在它们的大小和时间上:53 MB 到 56 MB,大多是 10 月份的。于是在 iCloud 云盘里找同样大小的文件:
1 | # 在 iCloud Drive 里找 50 MB 到 60 MB 的文件,只在云端的也能找到 |
一共 3376 个,其中 3275 个在同一个照片目录里。这个目录下有一个子目录,装着 2490 张索尼的 RAW 照片(.ARW),ls 合计 135.8 GB,du 是 0B,已经全部移出了本地。逐项对比:
- 大小:这批 ARW 在 52,129,792 到 56,188,928 字节之间。上午抽样看过的缓存文件里,53 MB 到 56 MB 的有 11 个,大小全部落在这个区间里
- 时间:这批 ARW 的修改时间都在 10 月 19 日 16:22 到 16:35 之间。那 11 个缓存文件里,有 7 个的时间是 10 月 19 日 16:28 到 16:33;另外 4 个是 10 月 22 日 12:13、10 月 29 日 11:32 与 13:00 的,同一个照片目录里,这两天各有一个子目录放进了几张 ARW,其中一张的修改时间正是 10 月 29 日 13:00
- 体量:除去那个 10.6 GB 的 ZIP,缓存里剩下的大约 90 GB,比这批照片的 136 GB 少
缓存里还有缩略图,数量上没法与照片一一对上,抽样也只有 11 个。不过大小和时间都对得上,ClonedFiles 里的大部分内容,很可能是 10 月 19 日放进 iCloud 云盘的这批照片。
为什么会占空间
照片的原件已经移出本地,du 是 0B,按理说这 136 GB 已经不在磁盘上了。缓存为什么还能占着 101 GB,我只能给出一个推测,没能验证。
从目录名看,ClonedFiles 里放的是克隆出来的文件。如果 bird 在上传前把文件克隆了一份,那么刚克隆的时候,缓存与原件共用数据块,并不额外占空间。之后「优化 Mac 储存空间」把原件移出了本地,可克隆的那一份还在,那批数据块就归它一个人占着了。从 Finder 里看,照片已经只在云端;从磁盘上看,空间并没有回来。
这个推测能解释两件事:照片明明已经移出本地,空间却没有回来;缓存文件的大小和时间,又恰好与原件一致。可惜等我想到这一层,缓存已经删了,没法再去检查它们当初是不是真与原件共用数据块。
另外,一般来说,文件要先传到云端,才能从本地移走。如果是这样,这批照片既然全都移出去了,应当都已经传完。也就是说,这些缓存在上传结束以后,至少又留了两周多。bird 为什么一直没有清理它们,我没有查到原因。
会长大的临时文件
另一个占空间的是 MMCS 下的 tmpm-0x0000000000001849。上午 11:48 前后,它在 ls 里是 5.2 GB,几乎不占空间;到 13:05,ls 里是 12.5 GB,du 是 7.3 GB,最后修改时间是 12:20。
删掉整个 MMCS 以后,bird 很快又建了一个同名的文件。看这段时间 bird 的日志:
1 | 13:13:42.745 bird[435] [NOTICE] downloading 1 documents in <private> |
tmpm-0x0000000000001849 的修改时间正是 13:15。几个小时后再看,它在 ls 里是 12.3 GB,du 是 1.1 GB,开头的 48 个字节全是 0。
从这些现象推测,有一个十几 GB 的文档在被反复下载。每次下载都先往这个临时文件里写一部分数据,而后被取消,然后重来。上午那 7 GB 的增长,可能也是同一件事。今天零点到 13 点多,bird 一共有 7 次下载被取消,4 次正常完成。至于是哪个文档、是谁发起的下载、为什么总被取消,日志里都是 <private>,我没能查到。
识别
把这次的经验收拢一下,以后可以按下面的顺序检查。
首先对比 bird 的缓存与 iCloud Drive 本地数据的大小:
1 | du -sh ~/Library/Caches/CloudKit/com.apple.bird |
出问题时,前者是 101 GB,后者是 29 GB;清理之后几个小时,前者是 1.1 GB。缓存比 iCloud 在本地的数据还大,就值得往下查。这时看 MMCS/ClonedFiles 与 MMCS/tmpm-* 各占多少。
如果是 ClonedFiles 大,就按大小和时间去找原件。先按修改时间看缓存文件集中在哪几天,再去 iCloud Drive 里找同期放进去的大批文件。最近放进去、du 已经是 0、而缓存里有同样大小文件的目录,就是嫌疑最大的。
如果是 tmpm-* 大,就看 bird 有没有在反复下载:
1 | # 一小时内 bird 的下载,被取消的与完成的各有多少 |
被取消的次数持续增长,同时 tmpm-* 的 du 在涨,就和这次的情况一样。
预防
第一,一次往 iCloud Drive 里放大批文件之后,等上传完成,回头看一眼 bird 的缓存。这次是一次放进去 136 GB 的 RAW 照片。若前面的推测成立,这时「优化 Mac 储存空间」移走原件也腾不出空间,反而是缓存把空间占着。磁盘越紧张,越要盯一下。
第二,清理缓存时按顺序来:
brctl status里是client:idle,bird 的 CPU 占用接近 0- 找出带
com.apple.fileprovider.pinned#PX的项目,记下它们的文件数与du - 把
ClonedFiles改名,核对保留下载的项目与 bird 的状态 - 确认无误后删除,再核对一遍
需要说明的是,这次找「保留下载」的标记,只检查了一个目录的第一层。brctl dump 里也还有一些条目标着 needs-sync-up,我没有逐条核对它们有没有传完。那个 10.6 GB 的 ZIP 包,云端还在不在也没有确认;若它只存在于缓存里,那么已经随缓存一起删掉了。照着做之前,这几处最好先自己确认。
第三,清理之前先看一眼本地快照。APFS 的本地快照会保住被删文件占用的空间。这次在清理缓存之前,我删过一份 17 GB 的 iPhone 备份,df 给出的可用空间没有变化;用 tmutil deletelocalsnapshots 2026-11-06-235702 删掉 Time Machine 的本地快照之后,可用空间才从 44 GB 涨到 61 GB。ClonedFiles 里的文件大多早于这份快照,如果当时快照还在,删掉它们多半也腾不出空间。
1 | tmutil listlocalsnapshots / |
至于那个反复被取消的下载,目前我还没有办法。tmpm 文件眼下只有 1.1 GB,先定期用上一节的命令看着。