0
周三下午,你的团队正在赶一个紧急需求。开发者A刚推了一版代码,测试开始跑。三分钟后,他发现有个变量名拼错了,顺手又推了一版。现在GitHub Actions里两个工作流同时在转——第一个跑的是你已经不关心的旧代码,第二个才是你真正想要的。
问题就在这里:GitHub按分钟计费,不管你需不需要结果。那个"垃圾"工作流会完整跑完测试、构建、lint,全程烧你的钱。放大到整个团队、一整天、几十个PR,这种浪费能占到总Runner时间的30%到50%。
解决方案简单到离谱。在YAML文件里加三行:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
原理不复杂。group按"工作流名+分支"建队列,同一组里只允许一个实例运行。cancel-in-progress在新提交进来时,立刻杀掉同组的旧任务。不是等它跑完,是立刻终止。
有团队实测过这个数字。他们给所有PR触发的工作流加上这个配置,月度Runner分钟数从约4.5万降到约2.7万——40%的节省,配置时间不到五分钟。
适用场景很明确:PR检查(测试、lint、构建)、同一分支的push触发工作流、只需要最新结果的部署任务。这些场景的共同点是"旧提交的结果对我已经没意义了"。
但也有雷区。发布工作流不能用,每个版本都得完整跑完。定时任务如果逻辑有重叠也要小心。矩阵构建如果确实需要并行执行多个配置,强制串行会拖慢整体进度。
一个进阶玩法是分支级保护:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: ${{ github.ref_name != 'main' }}
这条规则的意思是:特性分支随便取消,但main分支的构建保护起来。既省钱,又不影响核心流程的稳定性。
CI账单的优化通常让人想到换更贵的Runner、改并行策略、或者重写测试提速。但这三行配置的成本几乎为零,收益却立竿见影。如果你正在为GitHub Actions的分钟数付费,这是账单上最低垂的果实。