随着团队和应用程序规模的增长,采用可提供清晰代码所有人、隔离构建及高效代码交付等优化的架构是至关重要的。很多项目一开始只有前端、后端之类的一两个代码库,但随着代码库规模的发展,这种方式将难以为继。领英中风格产品各不相同的开发团队,不断为各类应用提交贡献,而领英的基础建设团队能够让开发者们在大型应用程序中,不受代码库规模的影响,高效合作。在面临生产力相关的挑战时,我们的领英人才解决(LTS)团队近期采用的 yarn 工作区,在管道部署中的代码交付时间方面有了 97% 的改善,从 39 小时减少至 125 分钟。
领英人才解决方案是我们招聘生态系统的核心部分,其中包含了如领英猎头、工作、人才中心、招聘专版、大数据洞察等大量产品,我们依据这套生态系统的基础,在其上搭建了分布且高度可扩展的产品,将人才与工作机会大规模地连结起来。这一套产品让猎头、求职者及企业从领英的经济图谱中寻找、连接并雇佣人才,让领英每分钟都能有八人新受雇。这项艰巨的任务之所以能够完成,全依赖我们坚持不懈地建立一致且高质量的大规模代码。
背景
我们这套人才解决方案产品的前端代码在开发初期是经典单体应用程序结构,而随着功能的添加,代码库规模自然而然地随着产品需求而增长,就和大多数项目一样。然而,随着时间的推移和代码库的扩大,代码所有者不明确、构建时间增长以及其他痛点层出不穷,让单体式的优势荡然无存。迁移和升级等维护工作越来越难,整体代码库的修复工作需要多个团队密切协作才能完成,应用程序的任何修改都需要执行完整测试套件,甚至包括不受影响的功能。即便单体架构在项目启动时是个合理的选择,但它已经无法适应我们现在的需求了。
为解决这些问题,我们开始将部分代码根据其功能分区,抽取到单独的代码库中,项目中负责某部分产品的团队拥有对应的代码库,以完全独立于整体应用程序的方式构建并测试。通过只向生产运送部分代码,我们的工程师在本地开发中体验到了更短的构建时间和更快的反馈周期。每个代码库都可以独立进行版本管理和发布,不仅可以与无关的产品领域解耦,还能让基础架构与核心应用程序分离,并在扩展到新领域时在应用程序之间共享代码。
生长痛
这种多库结构很好地为我们服务了多年,但我们的持续发展导致了代码的飞速扩张。四年后,我们拥有超过 70 个不同的代码库,专为存放人才解决方案应用程序的前端代码。这种方法虽然让我们受益良多,但这些年来也不乏痛点。
我们的代码分布于如此之多代码库,以至于开发者们在本地开发及应用程序内端到端测试工具方面,在很大程度上需要依赖于 yarn link 等工具。Yarn link 命令可将本地软件包相连接,让开发者可以跨项目运行未合并的代码。但我们也发现由于我们依赖图谱的复杂性,使得这种工具不再可靠,因为我们不止连接一两个,而是常常同时连接三四个代码库。这也就意味着依赖管理也变得困难,版本升级和迁移需要重复的模板修改次数完全依赖我们有多少包受到影响。此外,我们也非常依赖自动化工具来发布程序包更新,但由于每天都有十几次代码提交被合并,即使是自动化都赶不上我们的步伐。考虑到这一问题的规模,我们需要在定制自动化工具上投入更多资金,才能跟得上我们前进的步伐。
因为代码库有各自的版本号,开发者们常常需要在生态系统中多次编写紧密耦合的拉取请求(PR)才能提交一次变更,等到这些 PR 全部发布后才能进一步整合。我们工具管道的多重周期意味着功能需要更长时间才能到达生产环境。我们分析发现,对于直接影响应用的 20 个上层代码库而言,PR 到达部署管道(也被称作是“提交 - 发布”指标)的 P90(第 90 个百分点)测量为 39 小时,且不包含代码审查所需的时间。开发人员需要等待多个工作日才能将一个变更提交到生产之中,至于依赖关系网中的低级库而言,所需的时间甚至更长。
我们的管道分析表明,在管理复杂的依赖关系网上,单独程序包发布时的自动升级还不够,我们还要重复三次以上地执行部分测试组。举例来说,在合并库的 PR 之后需要执行一次测试组以确认上游兼容性,随后的自动化工具再执行一次测试组以确认 PR 可以被创建,随后还要再运行至少两次,一次是在拉取请求时,另一次是确保合并后能一切都能通过。
随着我们代码库数量的增加,自动化工具在处理更新时的压力也在增长。每天都有大量程序发包,PR 更新常常会遇到冲突需要协调。渐渐地,自动升级的 P90 不断上涨,最后我们甚至需要整整 24 个小时才能完成一个程序包的升级。这种程度的延迟非常严重,因为我们的工具需要有每月超过两千次更新,每天大约有一百次版本升级,才能保持所有代码库的依赖实时性。随着我们的需求不断扩张,这种请求很有可能会继续恶化,因此我们对架构重新进行评估,希望能改善这些痛点。
Yarn 工作区
我们在过去几年中一直在关注各家提供的工作区(workspace),其中包括 npm、pnpm,以及我们目前所选择的 yarn,这些程序包管理技术能为新型单体架构提供一流支持。与我们项目最初的单体架构不同,工作区可以容纳同一个代码库中相互引用的多个不同项目。也就是说,我们无需 yarn link 即可保持清晰的代码所有权和多库架构的构建隔离。除此之外,程序包内所有的代码变更对应用程序和工作区内其他消费都是即时可见的。
将代码库迁移到工作区后,我们也不用再单独给程序包加版本号和发布,并且更新提交也不用再多次重复执行测试组。通过将代码全部放在同一个代码库,让我们可以在执行应用程序测试的同时,执行程序包的测试,以确保其可兼容性。程序包单独执行自身测试平均需要 18.63 分钟 P90,但现在我们可以用一次工作区构建取而代之,其中应用测试与所有程序包测试平行执行,初始时间为 44.2 分钟 P90。在移除掉发布和更新单独程序包所需要的一般步骤后,我们的平均发包步骤降低了 85.96 分钟 P90,应用内自动化更新程序包降低了 1.55 天 P90。
即使对于小型库来说运行应用测试要远比测试组昂贵,但取消中间的构建步骤可以节省大量时间,且不会影响我们快速捕捉回归的能力。在合并拉取请求到主分支之前及之后,都会执行这两个测试组以确保足够的测试覆盖率。在这个精简后的管道基础上,我们将库的提交至发布的 P90 估计为 125.1 分钟,削减了近 95% 的时间!
我们编写了一套可以自动迁移所有代码库的脚本以创建我们的工作区。脚本首先会根据包的名称将其克隆到一个临时目录下,随后删除任何对工作区而言不必要的文件(如.gitignore,.npmignore,以及 yarn.lock)。通过 git mv 命令将文件移至工作区内新的目的地后,再将克隆的目录添加为临时 remote,利用 git merge 的–allow-unrelated-histories flag 将外部库合并至应用程序的 git 历史中。
最后,再将新增的软件包添加至应用程序的代码库 root 上的 package.json 中,从而完成注册并确保声明所有可能需要的额外依赖。此外,我们也采取了工作区内同步依赖版本的策略,以确保所有库都是针对生产上部署到供应商的捆绑包构建并测试的。
重新思考构建
然而,代码迁移本身既不是项目的开始也不是项目的结束。即使是在采用工作区之前,我们的应用规模就已经突破了测试基础设施的极限。在短短一年的增长中,单次测试的执行时长增长了 100%,即使是在多台主机并行执行的情况下,执行时长也从 45 分钟 P90 增长至近 100 分钟。考虑到在构建过程中新增上千个库的测试会带来的影响,我们目前的状态是很难持续下去的。
为解决这一需求,我们转型为分布式构建。既然每个程序包都已支持隔离构建和测试,我们可以在不同主机上为每个库和应用程序自身生成测试。在这种情况下,我们的唯一瓶颈将会是单独构建中最慢的,也就是应用程序测试组。我们进一步分散了应用程序构建中的无关步骤以削减瓶颈的影响。如此一来,我们在迁移之前的测试执行时间缩减超过 50%,而即使是在工作区迁移至代码库之后,我们的构建时间仍能够保持不变,甚至会随着分布式构建能力的提升而略有下降。
执行分布式测试仅仅是解决方案的一部分。除去代码库中已有的核心应用测试,我们还引入了被迁移的程序包各自的测试。随着构建中数量逐渐壮大的测试用例,不稳定的测试或基础设施的错误导致构建失败的几率大大增加。为此,我们采用了最小化动态测试,跳过不受变更影响的程序包的用例。同时,我们的核心应用测试继续在每次构建时执行,避免对多库测试覆盖范围的回归。实际应用中,程序包中的一处变动会触发其本身以及所有依赖于该包的所有测试,其中也包括应用内原始的测试用例组。这些正是最初代码库分家时会运行的测试。
在每次拉取请求中,我们的工具都会从 GitHub 的变更集中读取文件列表,并于其所属的程序包名称相匹配。此外,我们也会遍历应用程序的依赖关系网,借此创建工作区内相互引用的包列表。自此,我们建立了一个受影响的程序包过滤列表,其中包含了被修改的包以及依赖网中间接过直接受影响的包。我们可以根据这个列表明确一个特定 PR 的分布式构建,并最大限度地缩减发布变更所需要的测试覆盖面。
结论
在采用 yarn 工作区后,我们目前已经迁移了 28 个代码库至工作区内,领英人才解决方案的内部开发者们工作生产力有了很大的变化。在我们的初步分析中,代码交付预计会有 95% 的提升,这点在当时看来似乎有些野心过了头,但在项目的最后,我们成功到达了目标分数线。目前在我们的六周追踪中,应用的提交至发布 P90 时间为 70 分钟,相较于外部库在迁移之前的可比指标缩减了 97%。我们的新架构平均每月去除了超过两千次的版本更新,显著减少了自动化工具的压力。开发工程师们也不用等待数个工作日才能看到代码上传至部署管道,绝大部分代码都可以在当天准备完成!
工作区也带来了代码质量上的提升(比如,代码集中在同一个库中提高了代码可发现性),这为多库中同时应用代码修正和迁移提供了更多机会,让工作更加高效。通过将所有程序包的版本统一,不仅简化了依赖管理,也确保了依赖版本能时刻保持最新。工程师们在知晓本地开发环境始终会与部署到生产的版本号一致后,也能收获信心与安心。
如今我们的工作区已经实施了好几个月,我们已经能看到这个新平台上开发者满意度、效率以及生产力的影响。最近的研究调查显示,人才解决方案项目内的工程师们对这套工作区架构好评如潮。当被问及这个项目是否提升了开发者体验时,54.1% 的受访者表示强烈同意,另外 29.7% 的受访者则表示同意。
重 点
我们的应用程序架构在 yarn 工作区支撑下迎来了发展的同时,也没有牺牲掉先前多发布策略的优势。根据分布式构建通过重组测试配置,让拉取请求的测试时间缩短了 50% 以上,借助动态最小测试策略进一步精简,使测试覆盖率维持不变。我们的核心战略使加快人才解决方案工程师们的代码交付速度,而将“提交至发布”的 P90 时间缩短 97% 使我们朝着这个目标更近了一大步!
随着代码库规模的增长和团队需求的变动,重新评估应用架构是很重要的。某种方法可能只是一时非常有效,但万金油的解决方案并不存在。对于领英人才解决方案团队来说,工作区是应对我们当前以及未来项目规模的绝佳工具,这点已经由领英中其他更大型的项目采用证明了。有了这套架构,我们能够继续收获构建隔离和单独代码库中明确所有权的好处,也能获得更牢靠的本地开发经验和精简的测试管道。我们欢迎任何团队或组织能够考虑这些优势,并思考向工作区的转变是否适合自身的开发需求。
原文链接:
Accelerating Code Delivery By 97% With Yarn Workspaces(https://engineering.linkedin.com/blog/2022/accelerating-code-delivery-by-97--with-yarn-workspaces?accessToken)
声明:本文为 InfoQ 翻译,未经许可禁止转载。
今日好文推荐
选择“网红语言”值不值?使用Go和Rust的数据库公司七年经验总结
OpenAI回应ChatGPT不向所有中国用户开放;字节改节奏,双月OKR改季度;马斯克称今年底卸任推特CEO|Q资讯
背负着整个现代网络,却因“缺钱”放弃开源,core-js 负责人痛诉:“免费开源软件的根基已经崩塌了”
评论