作者:marinewu,腾讯 PCG 工程效能平台部专家
腾讯 PCG 工程效能平台部自 2020 年开始进行大仓基本能力建设,并在 2021 年与工蜂合作成立了代码大仓研效联合项目组。在此, 我们想分享大仓/单仓踩过的坑。我们认为这些坑是真实存在且很难避免的,不是小马过河。
单仓并不简单。成功的单仓所带来的效果绝不止简单的代码聚合,但成本是大量的工具支持以及工程实践。单仓像放大镜,可以将优秀的工程实践以极低的成本推广,但同时也会将错误迅速放大。同时,向单仓迁移的过程也有相当程度的风险。本文会详细讨论单仓的益处、挑战,以及我们对挑战的应对之道,以供参考。
单仓/大仓是什么?
-
单仓:指Monorepo,或单一仓库,是指将多个项目放在同个仓库中的版本控制策略。单仓在实践中可能有不同规模,如中心级、部门级、BG 级、公司级单仓。业界最著名的单仓是Google 的 google3,除一些开源项目,Google 内其它所有代码都放置于其中,有超过 20 亿行代码和超过 86T 的容量。目前公司内部成功应用的单仓大多在部门级及以下。
-
大仓:指大型规模的仓库。“大型规模”并没有严格的定义 -- 在本文中,我们将大仓定义为“单机已无法承受其容量的仓库”。一个粗略的估计:大于五千万行代码 或 容量超过 100G 的仓库即为普遍认可的大仓。
单仓是我们的目的,而大仓则是(终极)单仓的必然结果。
为什么要发明单仓?
单仓的设计核心起源于"One Version"的哲学,并内化了规模化的思想。
Scalability 可规模化
在软件工程中,我们希望“解决一次,解决全部”。即,任何的一个良好的工程实践,都以自动化、规模化的手段几乎零成本地推广到所有的团队及代码。这样可以减轻开发人员的心智负担,使之将更多的精力放在创造性的工作上。典型的例子包括代码静态检查、自动化测试及持续集成、开发流程标准化、工具统一化等等。
单仓极大地简化了规模化:当所有代码都放置在一处,并且高度统一,则所有的工具都可以规模化地在单仓上作业。
Single Source of Truth ->One Version 从单可信源到单一版本
Single source of truth(SSoT)原则,是指开发人员在任意时间可以确定代码仓库内的哪个分支是唯一可信依赖源(SSoT)。在 CVS 中,单一来源是核心原则;在 DVS 中,如 git,在现代的业界实践中也采取了该原则,即要求永远都只有一条主干,且所有的分支(除了发布分支)最终都会被收拢回主干里。
单一版本(One Version)则更进一步,是指在任意时间,代码库内的每一份组件、每一个依赖只有一个版本。
-
对内部库而言,这意味着使用主干开发(见下),并且必须在主干 HEAD 上依赖。这是一个非常强的约束——这意味着除了终端制品,任何一个内部被依赖的库都不能通过分支发布,而必须保持自己在单仓的主干上一直是发布状态。
-
对外部依赖而言,同一个第三方库在单仓中永远只会引入一个版本。
不强制 SSoT/One Version 的版本控制策略往往通过制品/版本分支发布。这意味着在整个依赖关系图之上还有一个版本的维度。这也是在开源社区/SDK 发布商通常采用的策略,其根本原因在于并非完全掌握下游用户的情况。这样除了导致较高的维护成本,还会导致依赖关系难以满足(Dependency hell, 依赖地狱)。
主干开发 Trunk-based development
基于版本控制的协作模式一般分两种:
-
分支开发:每个分支对应一个功能,开发者在这个分支上开发,直到最后完成功能后合并回主干。这是现在小仓下的主流模式。
-
主干开发:每个分支只对应一个简单的修改。每个开发者在分支上完成修改后经过 CR 尽快合入主干。这是在单仓下推荐的开发模式。
两者的主要区别在于分支存活时间:保持主干始终健康,将所有的 commits 尽快小批量合入的是主干开发;以 feature 为单位,当 feature 完成之后再重新合入的是分支开发。
虽然听上去差异微小,但从分支开发迁移到主干开发对研发模式的影响深远。请务必确保您的团队深入理解前置需求(挑战)和长期影响(益处)。下文将分别阐述。
Case: API Deprecation
考虑以下常见场景:一个公共库的作者如何 deprecate 旧的 API 并提供新的 API 作为替代品?
小仓场景:这个公共库按版本分支或按制品发布。API 可以在下一个版本直接更新,并强制 API 调用方对 API 进行更新。这样的好处是 API 提供方的责任较简单,坏处是每一个 API 调用方都需要自行更新代码,并且 API 提供方无法保证自己的新版 API 已经被使用。
单仓/主干场景:公共库只提供源码依赖,并不按版本发布。这样,我们需要保证公共库在主干上始终是正确的。API deprecation 时,API 提供方可以查找所有 API 调用方,并且发动大规模自动修改,原子性地将所有的旧 API 调用更新至新的 API。这样的好处是 API 调用方的责任更简单,但是这种修改只有在能够查找 API 的所有用户、并且整个持续集成水平较高时才有可能。
从上面的例子可见,在单仓/主干的场景下,很多代码的维护工作可以左移并规模化。减少重复劳动、大规模的工业化,是我们启用单仓的原动力。
益处
以下的益处基于开发者视角和代码维护者视角。
再次提醒, 单仓想要达成预期效果需要工具、流程和文化三方面的准备,以及长期大量持续维护。贸然迁移到大仓不但不会带来收益,反而会导致项目和代码管理彻底混乱。请确保您对挑战部分有所准备。
主干开发
主干开发是为了进行持续集成。频繁地、小批量地构建/测试是持续集成的关键。通过主干开发可以:
-
避免较长的稳定期,使持续集成可以持续运行,均摊每次合入的风险