近,我们在 Gusto 创下了一个新纪录:6 分 29 秒。
这是我们为 Gusto 最大的一个应用程序,一个 Rails 单体程序运行测试套件所花费的时间。6 分 29 秒是公司的持续集成(CI)流水线启用以来的最快纪录。上一次 CI 套件跑出这样的成绩,公司的规模还很小,而现在我们共有全球数百名工程师在使用这个 Rails 单体应用,为全美 1% 的小型企业提供支持。
对 Gusto 而言,高速 CI 流水线并不只是做做样子,我们把它视为一种竞争优势,代码部署越快,那么客户的业务开展也会越快。随着 CI 速度的提升,工程师的生产率也在提高,CI 时间每缩短一分钟,Gusto 每位工程师每周可增加 2%的拉取请求。
我们的目标很简单,希望让测试套件的速度成为一个参数的函数,这个参数就是:我们愿意花多少钱?将基础架构简化到这个层面后,就更容易做成本效益分析,例如如果想要将构建速度从 7 分钟提升到 5 分钟,那么需要花费 1 美元。
这篇文章介绍了我们是怎样加快测试套件速度的,其中涉及一个 Rails 单体程序和一个主要用 React 编写的 JavaScript 单页应用程序(SPA),这些经验适用于所有速度较慢的测试套件。
我的同事 Kent 说,构建软件有 3 个步骤:
让它跑起来(Make it work)
让它走上正轨(Make it right)
让它跑得更快(Make it fast)
“让它跑起来”指的是做出不会随便崩溃的软件。在这一步代码可能晦涩难懂,但足以为客户提供价值,并且通过了测试,让我们能信任它。没有测试,就很难判断“它能行吗?”
“让它走上正轨”指的是要让代码可维护,且易于更改。代码不仅能在计算机上运行,更要让人容易理解。新来的工程师可以轻松向代码添加功能,代码中的缺陷也应该很容易隔离和纠正。
“让它跑得更快”指的是要提升软件性能。为什么它会是最后一步呢?对于像 Gusto 这样的金融科技公司来说,如果只关注速度却无视质量,那么我们的客户和我们自己就离破产不远了。并非每段代码都需要优异的性能,如果一段代码每天可能只执行一次,那么就算它有 " 高性能 " 水平,却难以阅读和理解,那也是一段失败的代码。
我们把这套原则应用在 CI 套件的提速优化过程中。
首先需要做的事情是消除测试套件中的不可靠测试(test flakes)。不可靠测试(flaky test)指的是结果不确定的测试,它有时会通过,有时会失败。速度飞快但不可靠的测试套件并不能让你确信代码可以正常运行,这只是在抛硬币赌运气而已。
为了让一个规模庞大的工程团队消除不可靠测试,我们采用并执行了以下政策:
在 master 分支上所有失败的测试都将视为不可靠的。这些测试将标记为已跳过(skipped)。负责不可靠测试的团队可以在空闲时修复它们并取消跳过标记。
这个做法不仅能让测试套件一直亮绿灯,同时也让各个团队决定何时编写更多确定性测试。他们可以立刻开始编写,也可以选择等到再次处理这个功能时再行动。这种方法减少了一个团队的不确定测试给其他团队带来的损害。
当然,这种方法也存在质疑,“如果我们跳过了一项重要的测试该怎么办?”是最常见的问题。没错,这个问题很重要,但我们需要搞清楚问题的背景。一个测试之所以会被标记为已跳过,是因为它会随机失败,首先要考虑的是我们对这个测试和功能到底有多大的信心。很多时候,测试会出现不可靠情况是因为生产环境中的确存在错误!
通过这种方式,我们在主分支上的构建绿灯率从约 75%增至 98%!
随着时间的流逝,我们逐渐偏离了运行 RSpec 测试的默认路径。遵守默认值是很难的。下面是 RSpec 测试的一些默认值:
重新采用这些默认值的过程并不轻松。确保每个测试用例都重置其状态(数据库、Redis 值、缓存等),都会带来新的不可靠测试。根据其性质,我们可以修复更改或将之前正常的测试标记为不可靠。
我们慢慢重新引入了 RSpec 默认值,这为测试提速奠定了基础。
我们的测试是不平衡的。有些测试文件只需几毫秒就能执行完毕,还有些则需要花费数十分钟时间。耗时几分钟的测试是集成测试,涉及我们应用程序中最重要的一些流程。我们希望这些测试的速度能更快,但并不想移除它们。
因为测试套件是分布在多个节点上并行执行的,所以很快就遇到了测试提速的瓶颈。
我们的测试套件速度取决于最慢的测试文件,因此实施了一项新政策:
任何测试文件的执行时间都不能超过 2 分钟。
这个门槛是凭空拉出来的,但似乎很实用。我们只有 40 多个耗时超过 2 分钟的文件。
确定界限之后,我们开始处理速度缓慢的测试,试图让它们通过新的门槛,之前 40 个文件的时间都降到了阈值以下。之后,每个团队都有责任确保其测试文件的执行时间不超过 2 分钟,而执行时间超过 2 分钟的测试文件会被标记为已跳过。
现在我们有了一个可靠的测试套件,只是速度很慢,它可以按任何顺序执行测试,但是将测试分配给节点的方法是随机的。有些节点只需几秒钟就完成了,而另一些节点则需要数十分钟。我们怎样才能让它们平衡呢?
我们面临的最后一个问题是测试平衡。我们在这一步评估了两种解决方案:
我们采用了记录与分桶的方法将测试分配到各个节点上,因为它非常适合 knapsack 。测试运行期间,这种方法也不会在许多不同的并行作业之间共享状态。这是很重要的,因为一个共享队列可能有数百个节点,每个节点每秒为一个构建可以请求数千次工作。
我们建立了一个 MySQL 实例来记录所有文件的测试时间。在每次 CI 流程开始时,它会根据每个测试文件的第 99 个百分位时间生成一个 knapsack 文件。在每次 CI 流程结束时,它将上传新的结果。
为什么是第 99 个百分位?由于我们在共享硬件(AWS)上运行 CI,因此无法控制基础架构,各个测试文件的测试时间会大相径庭。我们无法将这些波动与使用的 EC2 实例类型,或者其他任何可以衡量的参数关联起来。
我们没有进一步完善构建基础架构,而是让系统具备了弹性。我们使用第 99 个百分位来组织测试,从而保证了测试的性能表现有一个下限,而不是在获得较好的平均性能时却存在明显偏低的个例。即便底层硬件发生变化或基础架构层出现故障,CI 管道依旧能保障可预期的性能水平。
这套策略实施之后,我们就有了一个自平衡的系统。测试越多,系统也就越平衡。如果某些测试随着时间的推移变慢,则测试桶也会随之调整平衡状态。
现在到了有意思的地方:让测试速度真的变快。
这里的主要做法是增加并行度。项目开始以来,我们已经从 40 个并行作业增加到了 130 个。这稍稍增加了成本,但大幅提升了 CI 的运行速度。在 Gusto,我们使用 Buildkite 作为 CI 基础架构,但这种并行化的理念适用于所有主流 CI 产品。
虽然我们将并行度提高到了 3 倍以上,但 CI 费用却没有随之线性增长。为什么?因为我们更好地利用了已有的 CPU 时间,通过在各个节点之间平衡作业,总 CPU 时间并没有变化,但是实际运行时间大幅缩短了。
在过去几个月中,我们在一点点让 Gusto 主要应用程序的 CI 管道变得更坚实可靠,而且速度更快。
这种改进依旧是一项日常工作。在出现不可靠测试时我们还是会跳过它们,或者寻找新的优化策略来加快构建速度。无论你们现在使用的是什么技术,我们都希望这篇文章可以为你们的团队提供一个路线图参考,帮助改进你们的 CI 管道和软件发布架构。
关注我并转发此篇文章,私信我“领取资料”,即可免费获得InfoQ价值4999元迷你书!
<turbo-frame id="13">
在众多WEB框架中,国外大型科技公司广泛使用的两个主要框架是 Ruby on Rails 和 Django。 Django 是一种 Python 编程语言框架,许多企业在其应用程序和网站项目开发中使用它,例如 Dropbox、YouTube、Instagram 和 Spotify等,Ruby on Rails,它是一种 Ruby 编程语言框架,已经在包括 Shopify、Bloomberg、Airbnb 和其他领先公司在内的研发产品中使用。本文将对 Django 与 Ruby on Rails 将进行对比。
介绍
Django是一个开放源代码的Web应用框架,由Python写成。采用了MVT的软件设计模式,即模型Model,视图View和模板Template。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的。并于2005年7月在BSD许可证下发布。这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。Django的主要目标是使得开发复杂的、数据库驱动的网站变得简单。Django注重组件的重用性和“可插拔性”,敏捷开发和DRY法则(Don't Repeat Yourself)。在Django中Python被普遍使用,甚至包括配置文件和数据模型。
特点
Django的优点
Django的缺点
架构
Django已经成为web开发者的首选框架,是一个遵循 MVC 设计模式的框架。MVC是Model、View、Controller三个单词的简写,分别代表模型、视图、控制器。Django其实也是一个MTV 的设计模式。MTV是Model、Template、View三个单词的简写,分别代表模型、模版、视图 。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为 MTV模式。它们各自的职责如下:
层次 | 职责 |
模型(Model),即数据存取层 | 处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。 |
模板(Template),即表现层 | 处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。 |
视图(View),即业务逻辑层 | 存取模型及调取恰当模板的相关逻辑。模型与模板的桥梁。 |
介绍
Ruby on Rails (RoR) 是一个用 Ruby 写就的web开发框架,并且Ruby“famous”也经常被认为是归功于它调了约定大于配置和测试这两个方面。Rails 的约定大于配置(CoC)意味着几乎没有配置文件, 只有实现约定好的目录结构和命名规则。它的每一处都藏着很多小魔法: 自动引入, 自动向视图层传递控制器实体,一大堆诸如模板名称这样的东西都是框架能自动推断出来的. 这也就意味着开发者只需要去指定应用程序中没有约定的部分, 结果就是干净简短的代码了
特点
RoR的优点
RoR的缺点
架构
Rails的 MVC 架构
Ruby on Rails的模型-视图-控制器架构由以下各部分组成:
Rails 的组成模块
RoR 和 Django 是很好的 Web 开发框架,框架之间的主要区别在于语言方法。它们都可以提供干净、模块化的代码,并有助于减少在编码上花费的时间。这些框架在建模域、应用程序数据的呈现、用户交互等方面遵循相同的 MVC 原则。选择 RoR 还是选择 Django 取决于业务目标和对象。 Django 具有可扩展性且更易于理解。它有许多功能可以帮助简化您的开发过程。
*请认真填写需求信息,我们会在24小时内与您取得联系。