构建变慢之后,我先拆开看缓存

仓库的持续集成从八分钟慢慢涨到二十多分钟,没人记得是从哪一次开始的。我没有先去换更大的构建机器,而是把每一步的耗时打出来,看时间到底堆在哪里。

时间不在编译,在反复安装

日志里,安装依赖占了将近一半。缓存看起来是开着的,命中率却很低。缓存键用了锁文件的哈希,这本身没问题。问题是安装脚本在装完之后又执行了一次会改写锁文件的命令,于是下一次构建的键和上一次对不上,缓存等于没开。

把会改写锁文件的步骤挪到本地整理依赖时执行,构建里只做按锁文件安装。缓存命中之后,这一步从九分钟降到一分多钟。

镜像分层把变化频繁的文件放前面了

另一个服务用镜像构建。Dockerfile 里先复制整个仓库,再安装依赖。只要改一行业务代码,依赖层就会失效。正确的顺序是先复制清单和锁文件,安装依赖,最后再复制源码。

1
2
3
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

源码变化时,只有最后一层需要重建。依赖层可以留在缓存里。这个调整不改变构建结果,只改变失效范围。

测试不是都要在同一步跑

有一组端到端测试依赖完整环境,每次都要起数据库。它们只占测试数量的一小部分,却占了大部分时间。我把它们和单元测试拆开。合并请求先跑单元测试和静态检查,端到端测试放到主干和夜间构建。

拆开的前提是单元测试确实覆盖了主要分支。如果单元测试只是空转,拆开只会让问题更晚被发现。我补了几个原来只在端到端里间接覆盖到的分支,再做的拆分。

变慢要有记录

我在构建脚本末尾打印三个数:安装耗时、测试耗时、镜像构建耗时。超过阈值时在日志里标出来。没有这三个数的时候,二十分钟只是一种感觉,没法判断该看哪一层。

机器规格可以以后再加。先确认缓存真的命中,分层没有被频繁失效,这通常比加一倍 CPU 更便宜。