我在 2021 年度总结 中提到过败走青云的故事。当时,我充满怨气地将项目失败归咎于与公司利益相冲突,留下一堆似是而非的空洞总结。现在一年多过去了,我开始了新的征程,是时候再回顾当时的失败原因、反思并从中学习到经验。

背景

BeyondStorage 旨在创建一个跨云数据服务开源社区,计划用完全开源的方式构建一个通用的存储库,向下对接各个存储服务,向上封装数据迁移,备份,管理等多种应用。这样应用开发者只需要开发一次就能让自己的应用跑在任意存储上,而用户则可以自由选择数据存储的位置。

基于 BeyondStorage 提供的统一数据访问接口,我们可以实现以下服务:

  • 提供存储网关,支持用户通过多种协议访问存储服务中的数据
    • 用户可以直接通过 FTP 协议访问云上某个主机内的文件
  • 提供数据迁移在线服务,支持用户在任意存储服务之间迁移数据
    • 用户可以将私有云中的数据迁移到对象
  • 提供数据备份在线服务,支持用户将数据备份到存储服务中
    • 用户可以在 macOS 中通过 Time Machine 将数据备份到对象存储

不难发现其愿景与 Apache OpenDAL 基本是一致的,但是为什么在差不多的时间点,两个项目的发展会有如此大的差异呢?

根本原因:没有用户

活跃用户是一个开源项目的生命线。在我看来,BeyondStorage 失败的根本原因是没有找到用户。没有用户意味着没有真实用户需求,项目的发展方向只能依靠我的个人感觉(事实证明我的个人感觉非常不靠谱)。

BeyondStorage 构建 go-storage 是为了满足迁移服务的需求,而迁移服务的需求来自于 go-storage 能力的自然延伸。不难发现这套逻辑中出现了一个可怕的循环,链条中完全没有真实用户的参与,项目从发展伊始就在朝着错误的方向狂奔。BeyondStorage 在相当长的一段时间里都在不断地进行各种重构和设计新的抽象框架,其目的只是为了更优雅地实现某个特定需求。比如说 BeyondStorage 设计了一整套 Metadata 框架来描述一个服务支持的所有操作及其参数,开发了一个独立的 code generator 用来生成相关代码,这套框架在短短的几个月内被重构了三次。合理的抽象固然是重要的,但是抽象必须基于真实的需求。更进一步的,重构应该尽可能在实现功能时一并完成,而不是单纯为了重构而重构。

OpenDAL 最幸运的地方在于它孵化自 Databend 的真实场景。Databend 持续不断地提出新需求,这些需求帮助我判断需求的必要性、调整任务优先级并修正错误假设。例如,过去我认为应用只会对数据进行连续读取,没有 seek 的需求。但事实上 Databend 非常需要 OpenDAL 提供原生的 read & seek 支持。除了需求之外,Databend 本身也有大量用户部署在不同环境和配置中,并反馈给 OpenDAL 许多 BUG 和易用性问题。这些反馈帮助 OpenDAL 快速成熟并变成一个生产可用的项目,并切实解决了用户的需求。因此可以快速推广到同类用户场景中。

次要原因:错误方向

BeyondStorage 失败的次要原因是战术选择错误。BeyondStorage 并非没有意识到缺乏用户的问题,实际上我也尝试过向 tidb 等项目提议使用 BeyondStorage 作为底层存储,这些尝试最终都以失败告终。

现在想来,在具体的执行过程中,我犯了以下错误:

错误的用户

TiDB 不是一个适合 BeyondStorage 当时发展阶段的项目。很多时候我们会想当然的觉得需求有高低,只要实现更强版本的需求,比它更弱的需求就自然被实现了,所以我们应当优先选择满足更强的需求。选择 TiDB 作为主攻方向的思路也是如此:我把 TiDB 都搞定了,其他项目的需求不都是小菜一碟吗?事实上并非如此。TiDB 使用了大量 BeyondStorage 尚未支持的特性,在实现这些需求的过程中我们一次又一次的发现 BeyondStorage 的抽象有问题的地方,开始了一轮又一轮的重构。一直到 BeyondStorage 社区彻底失活,我们都没有能够给 TiDB 加上 BeyondStorage 的支持。

OpenDAL 第一个主动选择的用户是 sccache,它是一个 ccache-like 编译缓存工具,支持将缓存上传到不同的存储服务中。它对存储服务的能力要求非常简单,只需要读和写即可,不需要为 OpenDAL 新增任何功能即可支持。

错误的方式

另一个重大错误是选择了错误的集成方式。在尝试集成 BeyondStorage 的过程中,我错误地选择了一次性替换的方式,将整个 TiDB 访问对象存储的地方都替换为 BeyondStorage,这引入了大量的变更,整个集成工作的复杂程度很快就失控了。事实上,更加好的做法是逐步替换,每次只修改一个服务,一方面便于 review,另一项目整个项目的复杂度和进度也更可控。

OpenDAL 在与 sccache 集成的过程中就深刻吸取了这个教训,每次的修改只有一百行,单个 PR 基本上一周内就能成功合并。更进一步的,在与 Vector 的集成过程中,OpenDAL 采用了新增服务的方式来跑通完整的 review 和发布流程,增强了维护者对于 OpenDAL 的信心,以至于用户出现了新的存储需求时都会优先考虑由 OpenDAL 来支持。

直接原因:失去金主

BeyondStorage 失败的最直接原因是失去了最大金主:青云科技。BeyondStorage 的所有维护者都是青云科技在职员工,由青云支付他们的工资。当青云解散团队后,尽管项目仍然保持着开源状态,但由于所有的维护者均已离职,项目实际上已经死掉了。那么 BeyondStorage 是如何逐步失去金主欢心的呢?

首先,青云没有任何项目依赖 BeyondStorage。正如之前所讨论的,作为一个云服务供应商,其所有业务都对 BeyondStorage 没有任何依赖性。相反地,在一定程度上 BeyondStorage 的发展壮大还会影响到青云自身利益。

其次,虽然青云看中了 BeyondStorage 的未来可能性,但这个可能性变现得太慢了。BeyondStorage 花费整整一年时间却没有交付出任何成熟产品。我们拥有完整的团队:研发、前端和市场等人才;但到最后只能勉强推出一个前端,并不能运行实际业务。内部最乐观预计也需要再过一年才能上线,并且产生营收更是遥遥无期。

最后,21 年末是最后的夕阳余晖,青云已经做好了过冬准备,开始压缩预算、裁员和调整组织架构。当时 BeyondStorage 有多个可能的去向,但无论哪个部门领导都对这个项目前途比较悲观。我自己也打了退堂鼓,决定不再坚持。

其他原因

除了上述原因外,BeyondStorage 犯了新生开源团队可能犯的所有问题:

  • 盲目扩张项目:在 go-storage 还没有取得进展时,就盲目开启了 beyond-tp、beyond-fs 和 beyond-ftp 等多个分支项目。
  • 过度社区运营:为追求项目的 star 数量和贡献者数量,参加各种露脸活动来提高曝光度,并建立 PMC。但这些只是短暂的热闹,留下一地鸡毛。
  • 过早分裂项目:在项目发展早期就拆成大量子项目,导致不同项目的开发者各自为战。
  • 对细节过于推崇:在项目还不成熟时对实现细节过于推崇,并考虑大量边界情况,延误了功能上线时间。

OpenDAL 的经验吸取

站在 BeyondStorage 的尸体上,OpenDAL 确立了自己的发展思路:

  • 专注于用户需求:积极将 OpenDAL 集成到用户项目中,关注真实需求,拒绝“有了会更好”的要求。
  • 避免过早优化:不追求引入庞大而复杂的框架和过度追求实现细节,除非出现类似的用户需求并且不影响整体质量。
  • Monorepo:通过维护一个整体 monorepo 来简化工作流程,并减轻开发人员在不同项目之间切换的负担。
  • 不做社区运营:OpenDAL 没有专职社区运营人员,不组织各种社区活动,专注于满足用户需求。
  • 超高速反馈:始终坚持给予贡献者快速反馈,在最短时间内完成 PR 的 review/merge/close 等操作。

总结

在这篇文章中,我将 BeyondStorage 的失败总结如下:

  • 根本原因:没有用户
  • 次要原因:错误方向
  • 直接原因:失去金主

BeyondStorage 很不幸的像千千万万个开源项目一样死掉了,但是它的精神和思想又很幸运的能在 OpenDAL 中延续。希望大家能从 BeyondStorage 社区的失败中吸取经验教训,帮助更多开源项目存活下去。这样,BeyondStorage 就能像 RethinkDB 一样获得永生。