2016年3月22日,一个开发者从npm上撤下了自己的273个包。其中一个叫left-pad,只有11行代码。几个小时后,Babel、Atom以及"成千上万个项目"的构建开始失败,按npm自己的说法,失败速度达到"每分钟数百次"。修复花了两个半小时,还动用了一次前所未有的备份恢复。
这件事在JavaScript圈子里几乎人人都听过,但真正翻过那份事故报告的人不多。它暴露的机制——依赖从一个人人可删的注册表里实时解析——至今仍是大多数人构建软件的方式。
![]()
导火索是一封商标邮件
事情的起点是一个包名。Kik的通讯产品负责人Mike Roberts后来公开了完整的邮件往来,所以整个过程有据可查。
3月11日,Kik的专利代理人要求Azer给他的kik包改名。Azer拒绝了。66分钟后,出现了那句让整个故事定型的话:"我们的商标律师会找上门来,封掉你的账号。"Azer回了一个价格:30000美元,Kik当天就给npm支持团队发了邮件。Kik自己在邮件串里的备注写着"Bob是我们的专利代理人,不是律师",并且表示Kik"已经决定给一个即将发布的包换个名字……即使我们被告知可以拿到Kik这个名字"。
3月18日,npm的CEO Isaac Schlueter裁定支持Kik:"大多数看到kik包的用户,会合理地认为它和kik.com有关。"3月20日,Azer写道:"我要删掉我所有的模块,包括我的账号。"两天后他自己动手了,并在一篇题为《我刚刚解放了我的模块》的文章里解释了原因:"NPM是某个人的私人领地,在这里企业比普通人更有权力。"
npm在事故报告里坚持了命名裁定:"造成昨天混乱的是突然撤包,不是我们的裁定政策。"
为什么重新发布一个left-pad没用
大多数复述都跳过了这个细节:为什么有人重新发布了left-pad,构建却依然在失败。Babel和Atom并不直接依赖left-pad。按npm的事故报告,它们是通过一个叫line-numbers的包把它拉进来的,而line-numbers要求的正是0.0.3这个确切版本。
依赖链大致是这样的:
- 你的项目
- └── babel(或atom)
- └── line-numbers
- └── left-pad "0.0.3" ← 精确版本,不是范围
精确锁定意味着只有那一个版本能满足依赖。Westland发布的1.0.0是同一个函数、不同的版本号,所以解析器直接忽略它,继续为0.0.3返回404。唯一的修复办法是把确切的字节找回来,这就是npm选择从备份恢复、而不是等一个新版本发布的原因。
Kik自己也被同一条链绊倒了。Roberts在文章里写道:"我们的构建开始失败,因为我们用了……JSCS。经过一长串依赖,JSCS依赖left-pad@0.0.3。"要求改名的公司,因为这个结果弄坏了自己的构建。
有三个生态特性让这一切成为可能:
- 任何作者都能瞬间删除任何版本。npm在移除一个包之前,不会检查谁在依赖它。
- 依赖是传递的,而且是实时解析的。Babel认识的是line-numbers,不是left-pad,整棵依赖树在每次安装时都会重新抓取。
- 2016年默认没有锁文件。npm shrinkwrap存在,但要手动开启。package-lock.json直到2017年5月随npm 5才默认启用。在那之前,每一次CI运行都要重新问注册表:这棵树应该长什么样。
2.5小时里发生了什么
故障"在太平洋时间下午2:30刚过"开始。十分钟内,Cameron Westland以1.0.0重新发布了这个函数(注册表记录为21:42 UTC),但构建仍在失败。随后npm的CTO发了推文。
npm称这次恢复"前所未有":"重新发布在其他情况下是不可能的。"恢复在太平洋时间下午4:55完成,距离第一批失败正好两个半小时。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.