我们现在为我们的单体应用执行大约10,000个测试。我们将这些测试分配到多台机器上,以便在合理的时间内完成这些测试。
根据CI的数据显示,我们的工作流程大约需要13至16分钟,其中测试占了大约9至11分钟。
在这篇文章中,我们将探讨我们当前的瓶颈以及我们能做什么来使我们的管道在未来指日可待(🤞)更易于扩展!
2024年12月中旬的功能分支工作流程持续时间情况
🪵 庞大的测试文件是瓶颈,让测试很慢! 多加机器真的有帮助吗?为了加快我们的CI工作流程,我们将测试拆分到多台机器上。直觉上,这应该能缩短工作流程的时间,然而,如下面的数据所示,为每个工作流程增加了50%的资源配置时间,时间却没有显著缩短 😧 — 这是为什么呢?
时间上没有差别吗?
以下由 Circle CI 提供的 CPU 使用率图表能给我们提供更多有用的信息:
Circle CI的CPU使用率界面
这表明有些机器比其他机器完成得更早。表面上看,测试似乎没有被正确地“分配任务”或分割。然而,我们更仔细地看运行时间较长的任务,会发现最长运行时间的机器上只运行了一个测试,而其他机器在一半时间内完成则运行了许多文件。
查看机器的测试日志
为了优化作业运行时间,Circle CI 根据之前的计时数据,将测试分配到已分配的机器上。然而,我们可以猜到,一些 spec 文件可能足够大,影响分配。让我们看看具体情况 👇
测试时长和分布的可视化图为了确定哪些文件可能很大到影响工作流程,我们需要注意:
- 确定每个测试文件的执行时间。
- 模拟 Circle CI 使用的调度方式。
每个 spec 文件的中位运行时(规格文件)
由机器分发的这些文件,给定的中位数运行时间
有的文件真的太大了!请注意:所有信息均来自十二月上半月到下半月的工作流程数据。测试文件的分发是通过LPT Algorithm模拟的。
从第一组图表中我们可以看到,某些文件的运行时间要比其他 spec 文件长得多。因为 Circle CI 按文件而不是按样本来分割测试,这将形成一个严重瓶颈!
如我们在分布模拟中所见,这些长时间运行的规范在整个任务周期中独占一台机器——因此,它们是造成我们当前测试流程瓶颈的原因之一。
✋ 多样性也是一个难题!等一下!我们还没完成呢。我在没有使用大规格的情况下运行了一些工作流程,发现还有优化的余地!在我的测试中,我注意到有些机器比其他机器早2到3分钟完成。举个例子:
检查示例:包含20台机器的测试任务示例
变动性是一个问题,仅仅是因为这项工作仍然依赖于运行时间最长的机器。如果几台机器导致工作进度滞后,那么有些机器没有充分利用也意味着这项工作未能更早结束。我试着在下面解释一下:
所以当我创建机器分布图来模拟调度时,我惊讶于分布的“平滑”程度。理论上,机器应该差不多同时完成。但在现实中却不是这样。那么实际情况又如何呢?更具体地说,这些变异的来源是什么?
为什么这么平滑呢?
这种变化到底是从哪里来的?- 初始化 overhead
在我们运行测试之前,我们必须做大量的准备工作——这里是一个简化的版本:启动容器实例 → 从缓存中加载 → 初始化数据库 → 运行测试。真是累死人了! 🥱 才开始真正的测试呢!
变异性可能来自初始化过程的任何环节!我会在这里具体说明几个。
- 如果承载容器的主机没有在之前的运行中缓存我们的镜像,它就需要通过网络下载一个超过1GB的镜像来启动我们的测试容器!这就要多花我们大约30秒的时间。
- 如果我们使用了一个过时的缓存,包可能需要下载和编译。这通常会使我们的运行时间再增加30秒——好在这种情况发生的次数越来越少。
这一切都说明,这些初始化工作仅仅为了运行测试所需的,是造成变异性的重要因素。
2. 运行时差异
另一个数据来源是测试运行时的数据。为了传达这一点,我收集了针对39个工作流的数据,从而可以确定四分位数并绘制箱线图。请参见下文:
机器运行时间的变化性
配置文件的运行时变化
在第一个图表中,尽管各机器的中位数运行时间大致相同,但整体来说,运行时间可能有很大差异。从第二个图表中可以看出原因。这些规格文件引入了运行时的变异性,从而导致机器分布的运行时间更加复杂,进一步增加了机器运行时间的分布复杂性。
比如说,我们就可以看到,spec 运行时也是整体变化波动的一个主要因素。
为什么时间分割不管用?注意:机器分配的分位数是通过将分配给它们的各个规格文件对应的分位数相加来确定的——我们可以做得更严谨一些,但这已经足够了。
我们当前正在使用[Circle CI的运行时拆分功能],该功能通过收集先前运行的时间数据,来决定将文件批次分配给每台计算机。
问题在于所用的时间数据信息没有考虑到运行时的任何变异性!它不知道在特定的工作流程中,机器A是否会出现不佳的状态(缓存未命中等),而机器B则顺利地完成初始化——时间拆分无法适应这些变化!这仅仅在你能消除所有变异性的情况下才有效——而这在分布式系统中几乎是不可能实现的。
既然希望不是策略,我们就需要更可靠的方法来减少这些不可避免的变异性对CI运行的影响。
🤗 嘿,随性消费 —— 只在你方便的时候才出手!在线上,人们通过使用竞争消费者模式(Competing Consumers 模式)解决了这个问题,将你的机器视为竞争消费者,共享同一个队列。测试文件被视为“任务”来处理。具体操作如下 👇
这解决了由于变异性导致的问题。
- 首先,如果有某台机器启动特别快,那么它们将首先收到最复杂的测试文件!
- 其次,如果某个测试用时较长(因为我们是随机分配测试),那么那台机器不需要承担更多的测试——其他更快的机器会接手。
我们可以自己实现(例如:https://github.com/skroutz/rspecq gem),或者利用像 knapsack pro(https://knapsackpro.com/)这样的付费服务。
💡 在队列中安排测试顺序时,有一个很酷的额外细节。那些已知运行时间最长的测试文件会排在队列的最前面。这样最先启动的机器就能优先运行这些测试。
⚡ 看看结果怎么样!!研究方法:
- 将最大的测试文件拆分成三个部分(每部分大约500到600行)。
- 使用rspecq gem实现了竞争队列的模式。我配置了一个Redis服务器充当外部队列。
结果:
- 我们成功将工作流程时间缩短至9到10分钟!(原本需要13到16分钟)
平均来说,我们的工作流程提升了约 35%,同时无需增加更多的计算资源!
下面的图表带标注,展示了与我们目前表现相比的情况:
根据Circle CI提供的数据,我们的工程团队每90天大概运行10,000次工作流程。如果一年计算下来,大约有40,000次工作流程运行。
如果我们为每次运行节省5分钟,那总共大约是20万分钟或3300小时(相当于一个全职工程师的150%)!🤯
共同学习,写下你的评论
作者其他优质文章
推荐试试 Apifox,好用很多。接口文档+接口调试+数据 Mock+接口测试,功能齐全。 接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档定义完全一致。高效、及时、准确!