大扫除:我们终于把 GitHub 的缓存用对了 🧹
在上一篇技术文章里,我们测量了构建和检查流水线到底花了什么:不是钱,而是等待时间和存储空间。那篇文章的结尾是“清理工作已经计划好,但还没完成”。结果比预想的快:从今天中午起,它已经进入主分支了。下面是数字。
先看结果。我们仓库的缓存用量,从 63 个条目共 5.32 GB,降到了 13 个条目共 2.46 GB。以前每个拉取请求都要跑的 C# 安全分析,现在在那里只需要 4 秒,而不是 4 分半,同时仍然检查所有会发布出去的内容。这一切都没有去掉任何一项保护你的检查。
简单说:缓存是干什么的?每次构建都从一台全新的空机器开始。为了不每次都从零开始,它可以存下一些中间结果,下次直接下载,而不用重新生成。每个仓库有 10 GB 的额度。满了以后,GitHub 会清掉最久没用的东西。而这正是我们的问题:空间被占满了,只是被错误的东西占着。
错误一:缓存键里没有平台。Unity 构建时会生成一个巨大的中间文件夹。有四个构建要用它:Windows、Linux、macOS、WebGL,每个都需要自己的那份。Windows 构建是唯一一个缓存键里没有平台标记的。所以它匹配到了第一个能找到的条目,恰好是 WebGL 的:下载了 1.7 GB,Unity 又正确地认出“这个不属于这里”,然后从头重新导入。日志里是:导入花了 257 秒,而不是 85 秒。现在缓存键是 Library-Windows-,这个怪事就结束了。
错误二:根本没人保存过它。这个发现更让人脸红。四个构建都很认真地尝试恢复这个中间文件夹,但没有任何一个工作流写入过它。唯一存在的那个条目来自一个要手动启动的工具。也就是说,项目里最重要的缓存是靠偶然维持的。🙈 现在它是有意写入的了。
省空间的决定:只有关键路径才算数。最显而易见的做法是把四个平台全部缓存。但看一下真实发布的耗时就知道那有多荒谬:四个构建同时开始,Windows 11 分钟后完成,Linux 和 macOS 是 13.7 分钟,WebGL 则是 31.6 分钟。只要 WebGL 还在跑,其他的反正都在等。所以只有它的缓存能缩短等待;另外三个会占掉我们 10 GB 额度里大约 4.5 GB,却一分钟都省不了,准确说是零。所以我们只缓存一个。
错误三:Docker 吃掉了剩下的。构建服务器镜像时,以前每一个中间层都会被存成缓存,大约是 24 个条目共 3.27 GB,占了整个额度的 64%,而且每次推送都会刷新。这股洪流稳定地把那个唯一重要的 Unity 缓存挤了出去。现在只存最终阶段,我们的四个镜像也各自有了自己的命名空间,以前它们共用一个,不停地互相挤掉对方。3.27 GB 变成了大约 150 MB。
安全分析:在关键的地方检查。CodeQL 是 GitHub 用来发现安全漏洞的工具,它是我们第二大的时间消耗:八天里共 230 分钟,几乎全是 C# 部分,因为它为了分析要重建整个项目,而且每个拉取请求都要来一遍。现在它在合并进主分支时完整运行,另外每周运行一次;拉取请求只做快速检查。改完后的实测:拉取请求里 4 秒,主分支上依然是不变的 4 分多钟。所以每一行真正会送到你手里的代码依然被检查,只是不会检查三遍了。
积少成多的小事。.NET 包现在会被缓存,但只在有人要等待的路径上。用于外来处理器架构的模拟器只有在我们真的为它构建时才会设置。一条在刚启动的机器上无事可做、却让每次构建多花最多 1.4 分钟的清理命令,被删掉了。发布构建的中间文件现在 7 天后清除,而不是 90 天,之前已经堆了 703 个,共 7.4 GB。
我们刻意没有做的事。我们自己分析里的两个提案,在测量之后被放弃了,而不是被包装成更新日志里的漂亮一行。把测试拆到两个并行任务里,只会省下四秒(一部分要 2:54,另一部分 4 秒),还会改掉一个必需检查的名字,让每个拉取请求永远卡在“等待中”。在发布前跳过大型测试关卡也什么都省不了,因为它早在 WebGL 还在构建时就跑完了。一个什么都没赢到、却拆掉了安全网的优化,不叫优化。
然后真的挖出了一个漏洞。在这次清理的过程中,每个拉取请求突然都超时了:有一个测试用了 212 秒,而允许的只有 120 秒。同一次运行里第二慢的是 10.5 秒。原因是我们服务器里的一条错误路径,对一个损坏的 HTTP 请求只是直接掐断,而不是干净地关闭。在 Linux 上,系统只会在每两分钟一次的清扫里回收这种连接,而关机一直在耐心等待它。在 Windows 上从没出现过,所以它只在构建服务器上咬过我们。现在服务器会用一个诚实的错误码回应这种情况,并自己关闭连接:测试用了二十来秒就结束了,真实的玩家碰到这种小状况时,也会得到一个看得懂的错误,而不是被切断的连接。一个持续集成大扫除日找出一个线上漏洞,这是美好的一天。🐛
这一切对你没有任何直接的改变,没有新方块,也没有新动物。它只是缩短了“写完代码”和“你能安装”之间的路。它也证明了和上次同一句话:先测量,再动手。🚀
留言