Skip to content

ci(docker): 层缓存改 mode=min + 固定 scope(9.52 GiB 的 buildkit 把配额吃满了) - #133

Merged
ReSerendipity merged 1 commit into
mainfrom
ci/docker-cache-budget
Sep 22, 2026
Merged

ReSerendipity merged 1 commit into
mainfrom
ci/docker-cache-budget

Conversation

@ReSerendipity

@ReSerendipity ReSerendipity commented Sep 22, 2026

Copy link
Copy Markdown
Owner

症状不在 docker,在别的作业

issue #118 的后续:性能门禁的基线缓存存进去两小时Cache not found。查配额时量到:

Actions 缓存 9.64 GiB / 配额 10 GiB,15 条
其中 buildkit-blob-* 9.52 GiB / 12 条,全挂 refs/heads/main
其余 trivy 82 + 42 MiB、一条 80 MiB、几条 0 字节
今天写出三份 ~3.2 GiB 快照的时间 11:41、11:46、11:52(main push / v2.2.4 tag push / main push)

元凶是 docker-publish.ymlcache-to: type=gha,mode=max 且不带 scope:mode=max 把每个中间层
都存一份,而且每次构建的 blob key 都不一样(快照含构建期元数据),于是旧快照变成没人引用但
仍计配额的近重复垃圾;默认 scope 按 ref 分桶,push / PR / tag 各存一份。
LRU 是按"最近使用"驱逐的,所以先被挤掉的是缓存 —— benchmark 基线 2.8 KB 与 12 平台矩阵的
setup-python pip 缓存。这也就是 #127 必须给门禁补 L3 仓内基线的原因。

改了什么

-          cache-from: type=gha
-          cache-to: type=gha,mode=max
+          cache-from: type=gha,scope=tts-mm
+          cache-to: type=gha,mode=min,scope=tts-mm

mode=min 只缓存"下一步要用到的"层(base 镜像与依赖层,正是耗时的那部分),不缓存中间产物;
固定 scope 让所有事件共用一个桶。代价说清楚:镜像构建会比 mode=max 慢一些(少数中间层
要重跑),换回来的是配额不再被 3 × 3.2 GiB 的快照吃掉。

  • 新增 tests/test_docker_cache_budget.py:核 mode=min + 两边 scope 一致且是写死的常量。
    只查生效行 —— 我那段解释注释里当然出现 mode=max 这个字符串(第一版就被自己的注释假红过一次)。
    变异复验:把 cache-to 退回 mode=max 并去掉 scope → 2 条红;还原 → 2 passed。
  • benchmarks/README.md 的 L1 那格把"两小时被驱逐"的元凶写清,不再只报总数。

与手工清理的关系

这条只防复发。当前占用的 9.64 GiB 要删旧的 buildkit 条目(owner 已批:删 11:41 / 11:46 两代、
保留 11:52 那一代,预计回收 ~6.4 GiB),删除的实际结果贴在本 PR 最后一条评论。

一个观察,不属于本 PR

docker-build.yml(PR 侧的 Docker Build)没有用 type=gha 层缓存,所以本仓只有这一条工作流
在写 buildkit 缓存;grep -rn "type=gha" .github/workflows/ 现在只命中改过的这两行。
守卫测试也因此只核一个步骤,并显式断言"带 cache-to 的构建步骤只有一条",多出来就会红。

实测:仓库缓存 9.64 GiB / 配额 10 GiB,其中 9.52 GiB 是 12 条 buildkit-blob,
全来自本工作流的 cache-to: type=gha,mode=max(无 scope)。mode=max 把每个中间层
都存一份快照,且每次构建的 blob key 都不同 → 旧快照成近重复垃圾但仍计配额;
默认 scope 又按 ref 分桶(main / PR / tag 各一份),等于把增长乘三。

症状不在 docker 上而在别的作业:LRU 先挤小缓存 —— 性能门禁的 benchmark 基线
(2.8 KB)存进去两小时就 Cache not found(issue #118 的后续),12 平台矩阵的
setup-python/pip 缓存也没了。这也是为什么 #127 给门禁补了 L3 仓内基线。

- cache-to: type=gha,mode=min,scope=tts-mm / cache-from 同 scope
- 新增 tests/test_docker_cache_budget.py:只核生效行(解释注释里当然有 mode=max),
  变异复验过(退回 mode=max 且去 scope → 2 条红;还原 → 2 passed)
- benchmarks/README.md 的 L1 那格把"两小时被驱逐"的元凶写清(不再只报总数)

Signed-off-by: ReSerendipity <zengyangc@outlook.com>
@ReSerendipity
ReSerendipity merged commit 007ec81 into main Sep 22, 2026
30 checks passed
@ReSerendipity

Copy link
Copy Markdown
Owner Author

手工清理已执行(owner 批准口径:删旧代、保最新)

时点 条目 占用
清理前(11:5x) 15 9.64 GiB
删掉 11:41 / 11:46 两代之后 23 12.49 GiB(不降反升)
再删 11:58 / 11:59 / 12:04 / 12:12 四代 15 4.84 GiB

中间那次"不降反升"是关键读数:我删完的 15 分钟里,在跑的两个 docker 构建又各写了一份 ~3.2 GiB 快照。
所以在 mode=max 下这不是"清理一次就完",而是每次构建 +3.2 GiB 的持续占用 —— 正是本 PR 要断掉的循环。

删掉的 14 条(gh api -X DELETE repos/…/actions/caches/<id>,逐条 rc=0):

  • 11:41 / 11:46 两代 + 一条 0 字节 index:7974325632 7974502333 7974502663 7974505042 7974671939 7974438166
  • 11:58–12:12 四代:7975033997 7975020028 7975075846 7975084333 7975084555 7975084723 7975235444 7975505809

保留:12:17 那一代(3215 + 28 + 27 MiB)与全部非 buildkit 条目。

一个正向副作用:配额腾出来之后,12 平台矩阵的 setup-python pip 缓存回来了
(12:06–12:07 写进 ~0.5 GiB × 3 条)—— 它们之前一直是被 LRU 先牺牲的那批;
trivy 的 82 + 42 + 42 MiB 也都在。

合并后的观察(不属于本 PR,记下来)

docker-publish.yml 的触发器是 push: tags: ["v*"], branches: [main],且没有 concurrency 组
今天 d4d80b7 同时起了 10:57 与 11:07 两条构建(分支 push + tag push),各自完整构建、各自导出缓存。
本 PR 只降"每次写多少"(mode=min)与"分几个桶"(固定 scope),没动"跑几次"。
要再省一档就是给它的 job 加 concurrency: docker-publish-${{ github.ref_name }}
cancel-in-progress: false,避免把正在推镜像的作业掐掉)—— 那是独立决定,没在这里顺手做。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant