鲸鱼死在大洋之后,尸体会沉入深海海底,变成一个微型生态系统。海洋生物学家称之为”鲸落”(whale fall)。鲸落会经历三个相互重叠的阶段:移动食腐动物在几个月内吃掉软组织;机会主义物种在骨骼和周围沉积物上定居,持续数年;化能自养细菌以骨骼本身为食,将骨头中储存的脂质转化为能量,支撑整个特化生物群落持续数十年。一次鲸落可以在原本荒芜的海底维持生命长达五十年。
Michael Winser 在讨论废弃项目的依赖图谱时随口提到了鲸落这个概念,它就一直留在了作者脑子里。
开源项目的鲸落
一个大型开源项目停止维护。也许维护者精疲力竭,也许背后的公司转了方向。项目不再更新,但也不会消失。它静静地躺在 GitHub 上积累 issue,最后一次提交越来越远,然后有人 fork 它开始合并最紧急的补丁。如果项目足够流行,会出现多个 fork 竞争用户,就像盲鳗争抢鲸脂一样,不过大多数 fork 很快就会死去,只剩一两个凭借有足够时间或机构支持的人存活下来。OpenOffice 变成了 LibreOffice,MySQL 变成了 MariaDB,Hudson 变成了 Jenkins,每一个都是通过这种熟悉的流程成为权威替代品的:fork 公告、迁移指南,外加一堆”为什么你应该切换”的博客文章。
随后,更小的项目开始提取特定模块,或者构建针对死去项目数据格式的工具。Google Reader 不是开源的,但关闭时发生了一模一样的事:Feedly、Miniflux、FreshRSS、Tiny Tiny RSS 以及十几个其他项目蜂拥填补空白,其中好几个实现了 Google Reader API 或 Fever API,原因只是多年来的 RSS 客户端都基于这些接口构建。许可证不重要,接口是公开的,其他软件依赖它们,这就够了。
然后是结构性骨骼:协议、文件格式、API 契约,它们继续支撑着甚至不知道这些”骨头”从哪来的特化社区。OpenDocument Format 比创建它的 OpenOffice 社区活得更久,被数十种语言生态中的文档格式库所使用。Docker 在 2015 年将容器运行时和镜像格式捐赠给了 Open Container Initiative(OCI)。OCI 规范现在定义了容器的工作方式,与运行时无关。Docker 自身的统治地位减弱了,规范没有。Tree-sitter 是为 Atom 编辑器构建的,GitHub 归档 Atom 之后,它成了 Zed、Neovim、Helix 以及近几年大多数编辑器的语法解析引擎。
继承
作者反复观察到的一个模式是:未维护的库会经历一轮又一轮的再殖民。项目沉寂,有人 fork,其他项目开始依赖这个 fork,然后 fork 的维护者也累垮了,整个循环以更小的规模重复。每一代 fork 通常比上一代更小,贡献者更少,用户群更窄,直到最终迁移的是想法本身,代码留在原地。另一个语言生态的人看着累积的残骸,决定从头重写这个概念,只继承设计,不继承实现。
Sass 就经历了这个过程。最初的参考实现是一个 Ruby gem。当 Ruby 的性能成为瓶颈,LibSass 用 C++ 重写了它,sassc gem 又为 Ruby 用户做了封装。然后 LibSass 本身也被弃用,取而代之的是 Dart Sass,现在它才是正式实现。概念在十年间从 Ruby 迁移到 C++ 再到 Dart,每次重写都受益于前任积累的 bug 报告和设计讨论。在每个阶段,其他生态中都有围绕 Sass 语言规范这具结构性骨骼而生的包装库。今天写 Sass 的人大多不知道它最初是一个 Ruby gem。
这种连续再殖民有一个糟糕的失败模式。Edera 发现了 Rust 的 tar-rs 库中的一个差异解析漏洞,影响了下游每一个 fork:tar-rs 本身、async-tar、tokio-tar,以及多个由公司维护的内部 fork(比如 Astral 的 fork,随 uv 包管理器一起分发)。协调披露意味着要联系大约二十个实体,横跨一个碎片化的 fork 图谱,其中四个库版本有三个已经无人维护,好几个维护者也联系不上。漏洞的存在是因为代码通过连续 fork 被逐一复制,没有人重新审计所有 fork 从原始版本继承的 PAX header 解析逻辑。bug 一直藏在”骨头”里,被每一个 fork 继承。发现一个安全通告影响了哪些 fork,这个问题作者正在另外研究,因为目前没有人有好用的工具来做这件事。
CentOS 在 Stream 转型之后,同样的模式在操作系统层面上演:Rocky Linux 和 AlmaLinux 进行了 fork,更小的 RHEL 兼容重建版出现在它们周围的富集层中,而底层的结构性骨骼(RPM 打包、systemd 规范、文件系统层级结构)无论哪个特定发行版在任何时刻是活是死,都保持不变。
许可证变更
当项目从开源许可证切换到源码可用许可证(source-available)时,食腐阶段几乎立刻触发,往往在变更生效之前就开始了。Redis 到 Valkey,Elasticsearch 到 OpenSearch,Terraform 到 OpenTofu,这个模式如今已经足够熟悉,社区把它变成了一套例行程序:抢先 fork 最后一个开源提交,短暂竞争,然后围绕一两个幸存者进行整合。在这些案例中,项目并非真正死亡。Redis 这个产品仍然有营收和路线图。但从开源生态的角度看,这具代码体已经停止接受外部贡献,fork 们开始偏离原始版本,就像 MariaDB 偏离 MySQL 那样。
抽象层是持续时间最长的部分。每一个曾与开源版本集成的项目都面临选择:跟随专有版本还是切到 fork,而很多项目干脆就构建一个兼容层(compatibility shim)。这些兼容层往往比催生它们的争论活得更久,在许可证辩论冷却多年之后,仍然静静地依附在原始 API 这具骨骼上。
Sun Microsystems 的案例
Oracle 在 2010 年收购 Sun,与其说是一次鲸落,不如说是整群鲸鱼同时死在了海里。Java、Solaris、ZFS、DTrace、VirtualBox、NetBeans、GlassFish、Hudson、MySQL,每一个都沉入了各自的海底,衍生出自己的继承链。有的产生了单一的主导 fork(Hudson 到 Jenkins,ZFS 到 OpenZFS),有的分裂成竞争的谱系(仅 MySQL 就养活了 MariaDB、Percona,以及短暂出现过的 Drizzle,Drizzle 本身在被放弃时又成了一次更小的鲸落),还有的在基金会之间辗转之后才安定下来(NetBeans 到 Apache,GlassFish 到 Payara 以及更广泛的 Jakarta EE 生态)。所有这些底下的结构性骨骼:JVM 字节码格式、ZFS 磁盘格式、MySQL 线协议,至今仍在支撑着许多项目,而那些项目的开发者从未听说过 Sun。
浅水区
有些项目死在浅水区,尸体被快速回收。收购式招聘(acqui-hire)就是这样运作的:公司被吸收,代码变成专有或被归档,知识随着人离散,没有在公开的”尸体”上积累让别人可以利用。企业整合也有类似效果,因为当一个大型独立项目被并入平台公司的专有服务时,养分在水柱中被回收,没有沉到足够深的地方让继承发生。
作者认为,当前平台提供商主导的整合趋势正在降低开源中的鲸落发生率,而这对生态多样性有二阶效应,却没有人在追踪。这是可以量化的:观察死亡项目的 fork 和依赖图谱随时间的变化,统计有多少新项目引用了一个死去的依赖,比较鲸落在 npm、crates、rubygems 中的半衰期。有些生态系统的海底是否更深,分解更慢,结构性影响更持久?数据就在包注册表和代码托管平台的 API 中,但作者还没见有人提出这个问题。
一个所有大型项目都归平台公司所有、被无限期维护或在死亡时被悄悄吸收的开源生态,其中的富集和化能自养阶段很少有机会发展,依赖鲸落为食的小型特化生物也永远没有进化的机会。最健康的生态系统有稳定的鲸落供应。这听起来很矛盾,因为它意味着期望大型项目的死亡,但深海海底没有其他食物来源。