鲸落

 

作者:Andrew Nesbitt

原文链接:https://nesbitt.io/2026/02/21/whale-fall.html

当一头鲸鱼死在远洋,它的尸体会沉入深海海底,成为一个生态系统。海洋生物学家称之为”鲸落”(whale fall)。鲸落会在三个相互重叠的阶段中维持生命:流动性食腐动物在数月内啃食掉软组织;富营养机会型生物在接下来的数年里占据骨骼与周围沉积物;而化能合成细菌则在数十年间依附于骨骼,分解骨骼中储存的脂类,将其转化为能量,供养整个由特化生物组成的群落。一场鲸落,可以在原本贫瘠的深海海底维持生命长达五十年。

我和 Michael Winser 聊起被遗弃项目的依赖图会发生什么变化时,他随口提到了鲸落,这个比喻就再也挥之不去。

一个大型开源项目停止维护。也许是维护者精疲力竭,也许是背后的公司转型。项目不再更新,却没有消失。它静静躺在 GitHub 上,问题列表不断累积,最后一次提交越来越久远。于是有人 fork 它,开始合并最紧急的补丁。如果项目足够流行,就会出现多个 fork,像盲鳗争抢鲸脂一样竞争用户。多数会迅速死亡,只有一两个能凭借时间或机构支持存活下来。OpenOffice 变成 LibreOffice,MySQL 变成 MariaDB,Hudson 变成 Jenkins —— 这都是典型的”食腐式”分叉:发布 fork 公告,写迁移指南,发表”为什么你应该切换”的博客文章,最终成为事实上的正统替代品。

随后,小型项目开始抽取特定模块,或构建能处理该”死亡项目”数据格式的工具。Google Reader 并非开源,但在它关闭时也发生了同样的事情:Feedly、Miniflux、FreshRSS、Tiny Tiny RSS 以及十几款工具迅速填补空白,其中不少实现了 Google Reader API 或 Fever API——并非因为这些 API 优秀,而是因为多年来的 RSS 客户端都围绕它们构建。许可证并不重要。接口是公开的,其他软件依赖它们,这就足够了。

然后,真正持久的是结构性的”骨架”:协议、文件格式、API 合约。这些东西继续支撑着专门化的社区,甚至连开发者都未必知道这些骨骼来自哪里。OpenDocument Format 比创造它的 OpenOffice 社区活得更久,如今被几十种语言生态的文档库所支撑。Docker 在 2015 年将其容器运行时与镜像格式捐赠给 Open Container Initiative。OCI 规范如今定义了容器的工作方式,无论运行时是什么。Docker 本身的主导地位淡去,但规范仍在。Tree-sitter 原本为 Atom 构建,GitHub 归档 Atom 后,它成了 Zed、Neovim、Helix 以及近几年大多数编辑器的语法引擎。


继替

我不断看到未维护库的一个模式:连续的再殖民。

一个项目沉寂下来,有人 fork;其他项目依赖这个 fork;然后 fork 的维护者也精疲力竭,循环再次发生,但规模更小。每一代 fork 通常都比上一代更小,贡献者更少,用户群更窄。最终迁移的是”理念”,而非代码。另一个语言生态中的人,看着累积的残骸,决定从头重写,将设计延续下来,却抛弃了实现。

Sass 就经历了这样的路径。最初的参考实现是一个 Ruby gem。后来 Ruby 性能成为瓶颈,于是 LibSass 用 C++ 重写,Ruby 用户通过 sassc gem 封装它。随后 LibSass 被弃用,Dart Sass 成为标准实现。十年间,这一概念从 Ruby 迁移到 C++,再到 Dart,每一次重写都受益于前任积累的 bug 报告与设计争论。各生态中的封装库则持续依赖 Sass 语言规范的结构骨架。如今大多数写 Sass 的人,甚至不知道它始于一个 Ruby gem。

连续再殖民有一个危险的失败模式。Edera 发现 Rust 的 tar-rs 库存在一个差异解析漏洞,该漏洞影响了所有下游 fork:tar-rs 本身、async-tar、tokio-tar,以及包括 Astral 在内的多家公司内部 fork(其版本内嵌在 uv 包管理器中)。协调披露需要联系大约二十个实体,而 fork 图谱已经高度碎片化:四个库中有三个无人维护,多名维护者无法联系。漏洞存在的原因,是代码在 successive forks 中被不断复制,却无人重新审计继承自原始实现的 PAX header 解析逻辑。漏洞一直存在于”骨骼”之中,被所有 fork 继承。如何发现哪些 fork 受安全公告影响,是我正在研究的问题——目前还没有好的工具。

CentOS 在 Stream 转型之后也是类似模式,只不过规模是操作系统级别:Rocky Linux 与 AlmaLinux 进行了 fork,围绕它们出现了更小的 RHEL 兼容重建版本。而底层骨架——RPM 打包体系、systemd 约定、文件系统层级——始终未变,无论具体发行版生死如何。


许可证变更

当一个项目从开源许可证切换为源代码可见许可证时,”食腐阶段”几乎立刻触发,甚至在变更正式生效之前。Redis 到 Valkey,Elasticsearch 到 OpenSearch,Terraform 到 OpenTofu。模式已经熟练:迅速 fork 最后一个开源提交,短暂竞争,最终集中于一两个幸存者。这种情况下,原项目并未真正死亡。Redis 仍有收入与路线图。但从开源生态角度看,代码主体已不再接受外部贡献,fork 开始逐渐偏离原版,正如 MariaDB 之于 MySQL。

真正持久的是抽象层。每个与开源版本集成的项目,都必须在继续跟随专有版本或转向 fork 之间做出选择。许多项目选择构建兼容层。这些兼容层往往比引发争议本身更长寿,多年后仍静静依附在原 API 的骨架上。


Sun Microsystems

2010 年 Oracle 收购 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 中鲸落的半衰期。是否某些生态有更深的海底、更慢的分解、更持久的结构影响?数据存在于包注册表与 forge API 中,但我尚未见人提出这个问题。

一个开源生态,如果所有大型项目都由平台公司拥有,被无限期维护或在死亡时悄然吸收,那么富营养与化能阶段很少有机会发生。依赖鲸落为食的小型专门生物也无法进化。最健康的生态,反而需要稳定的鲸落供应。说起来很奇怪——这意味着希望大型项目死亡——但在深海海底,别无食物来源。