搬家最先坏的东西,从来不是你装箱的那些。
是那些你没装箱、也没数过、但一直被默默引用着的东西。
前天夜里,我们把一套跑了两年多的环境从一台 Mac 搬到了一台 Linux 服务器。搬完,第一件事是打开仓库看一眼。屏幕上只有一行:
1
error: index file smaller than expected
索引文件比预期的要小。它没说你文件丢了,也没说仓库坏了。它说的是:我算出来的数,和我读到的数,对不上。
那一晚我们修的东西,跟”文件”这两个字一点关系都没有。
报错说的是对不上,不是丢了
我先承认,我第一反应是最坏的那个:仓库废了。
这是人的默认路线。屏幕上出现”损坏”“缺失”“失败”,大脑立刻跳到灾难片——备份在哪、还剩多少、要不要从零开始。因为这种字眼在过去的经验里,几乎总是这个意思。
但”对不上”是一种很特殊的错。丢东西不会说对不上,丢东西说的是找不到。说对不上,意味着两边都在,只是有一边跟另一边讲不成同一句话。
于是把范围缩小到索引。仓库的对象文件都在,历史也在,只有那个本该告诉程序”东西都放在哪”的小文件,给出的数字比实际小一点点。
小一点点。这三个字是整件事的钥匙。
一万五千个不在清单上的文件
真正的原因,是一批从来不在任何清单上的文件。
一万五千零五个。名字都以 ._ 开头,或者叫 .DS_Store。
它们是 macOS 给每个文件配的旁注——这个文件该用什么程序打开、在窗口里排在哪个位置、图标是什么样。你平时在 Finder 里看不见它们,ls 默认也不显示。它们不算内容,但每一个文件身边都跟着一个。
搬家的时候,复制工具很老实:你让我搬这个目录,我就把目录里所有字节搬过去。它不判断哪个字节算内容。于是一万五千个旁注,跟着几千个真正的文件,一起上了车。
到了 Linux 上,这些旁注一文不值——新系统不认这种格式,也不需要这种解释。但它们没有消失。它们静静地躺在那里,其中恰好有一个叫 ._pack-xxx.idx,名字和老家的索引文件几乎一样。
程序的读取逻辑很简单:找索引,读第一个。它读到的那一个,大小对不上。
我讲这件事的时候,听起来像个愚蠢的巧合。可我越想越不觉得这是巧合。旁注之所以危险,正是因为它和正主长得太像。 它不是垃圾,它是原件的影子;而影子在搬家的路上,是不分先后的。

复制的是字节,迁移的是解释
这几年所有人都在说一句话:迁移嘛,复制过去就行了,rsync 一把梭。
我不认同。不是因为命令不好用,是因为这句话把两件不同的事压成了一个词。
复制,搬的是字节。迁移,搬的是解释。
一个文件在新机器上能不能用,不取决于它的内容有没有过去,取决于原来那套”它是什么、它在哪、谁在引用它”的约定,在新机器上还成不成立。旁注是旧系统对内容的解释,路径是旧系统对位置的解释,权限是旧系统对”谁能碰它”的解释。这些解释全都不在文件里。
它们在外面。
所以迁移真正的失败,几乎不会发生在复制那一步。复制是最不容易出错的动作,它只会成功或报错,而报错你会立刻看见。迁移的失败发生在复制完成之后——所有文件都在,所有解释都过期了。
那一晚我们改的第二批东西,就是这个。六十四份文件,两百三十处 /Users/eric。
这是旧机器对”家在哪”的解释。写死在配置里、脚本里、注释里。它不会有任何警告,它只会在某个凌晨的定时任务里,安静地去找一个不存在的目录,然后什么也没找到。

这个错误最贵的地方,是它长得像灾难
我把这一晚的价值,归到这一点上。
一个”索引损坏”的报错,会把一个正常人的手,直接推向最贵的那几个动作:重装、重新拉一份、从备份恢复、找人来救。
这四个动作,没有一个便宜。它们消耗的是你最稀缺的东西——不是钱,是那种”今晚必须搞定”的体力和耐心。
而正确的动作,是删掉一层你以为很重要的东西。
一万五千个文件,删掉。两百三十处文本,替换。整个过程加起来不到一小时,其中大部分时间花在确认”这些真的可以删”上,而不是删。
这就是这类故障的恶意所在:它报出来的错指向内容,真正的病根在解释。 你的每一次正确直觉,都在把你推向更远的地方。
我不觉得这是我们运气好。恰恰相反,我觉得大多数团队在这种时刻都会走错,因为走错的那条路,看起来更像在认真处理问题。删东西不像修东西。重装才像。

一个可以带走的判断
如果你只从这件事里拿走一句话,我要给的不是”备份很重要”。备份当然重要,但这话你已经听了一百遍,而且它对这一晚毫无帮助——我们的备份是好的,备份救不了这种故障。
我要给的是这个判断:
屏幕上说”坏了”的时候,先问一句:是东西坏了,还是东西之间的关系坏了。
这两种坏,修法完全相反。东西坏了,你要往回找,找备份、找历史、找原件;关系坏了,你要往前删,删掉那层过期了的解释,让系统重新自己算一遍。
判错一次,你就白熬一夜,而且会觉得自己很努力。
这件事从来不是技术问题。换电脑、换云、换供应商、换模型、换一套用了三年的工具链,全都是同一个动作:你带走的是数据,你带不走的是数据之间那层默认的默契。
所以一次迁移的真实成本,不该按容量算。该按”有多少东西在暗处互相引用”算。
我们那一晚搬的是两个多 G 的文件。真正让我们熬到凌晨的,是那十五兆不到的旁注。
补一句自嘲:那天夜里最先说出”这些是元数据”的不是我。我还在翻备份列表。团队里有人比我镇定,这件事本身也挺说明问题的——手艺人的第一反应和老板的第一反应,从来不是同一个。