From 74f3234736ad87568831ed51c34a7e289dc668e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 29 Oct 2026 07:53:07 +0000 Subject: [PATCH] ci: one regression shard writes the test image cache (#6729) * ci: one regression shard writes the test image cache, so it stops evicting the rest * test(ci): each image cache has one writer, shard 0 of a main push --- .github/workflows/fast-video-validation.yml | 2 +- .github/workflows/regression.yml | 7 ++-- scripts/ci-cache-workflows.test.mjs | 43 ++++++++++----------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/.github/workflows/fast-video-validation.yml b/.github/workflows/fast-video-validation.yml index f1ada4bd15..6c80db3fd9 100644 --- a/.github/workflows/fast-video-validation.yml +++ b/.github/workflows/fast-video-validation.yml @@ -46,8 +46,8 @@ jobs: file: Dockerfile.test load: true tags: hyperframes-producer:test + # regression.yml owns this cache's writes. cache-from: type=gha,scope=regression-test-image - cache-to: type=gha,mode=max,scope=regression-test-image - name: Validate fast-capture video (drawElement + BeginFrame) run: | diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 8aef6a4589..80ae838280 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -156,10 +156,9 @@ jobs: load: true tags: hyperframes-producer:test cache-from: type=gha,scope=regression-test-image - # PR matrices can fan out across many stacked branches at once. Let - # them consume the shared cache, but keep a single writer on main so - # concurrent exports cannot exhaust the Actions cache service. - cache-to: ${{ github.event_name == 'push' && 'type=gha,mode=max,scope=regression-test-image' || '' }} + # One writer: the first shard of a main push. Every shard rebuilds the same layers with + # different digests, so each export added ~1.3 GB and evicted the rest of the 10 GB cache. + cache-to: ${{ github.event_name == 'push' && strategy.job-index == 0 && 'type=gha,mode=max,scope=regression-test-image' || '' }} - name: "Run regression shard: ${{ matrix.shard }} (${{ matrix.mode }})" run: | diff --git a/scripts/ci-cache-workflows.test.mjs b/scripts/ci-cache-workflows.test.mjs index 7401874d2b..78c7b55ef7 100644 --- a/scripts/ci-cache-workflows.test.mjs +++ b/scripts/ci-cache-workflows.test.mjs @@ -10,29 +10,28 @@ const load = (path) => parse(readFileSync(path, "utf8")); const workflows = readdirSync(workflowsDir) .filter((file) => /\.ya?ml$/.test(file)) .map((file) => ({ file, doc: load(join(workflowsDir, file)) })); -const events = (on) => - typeof on === "string" ? [on] : Array.isArray(on) ? on : Object.keys(on ?? {}); +const cacheWrites = (list) => + list.flatMap(({ file, doc }) => + Object.entries(doc.jobs ?? {}).flatMap(([name, job]) => + (job.steps ?? []) + .map((step) => String(step.with?.["cache-to"] ?? "")) + .filter((to) => to.includes("type=gha")) + .map((to) => ({ id: `${file} ${name}`, to, matrix: Boolean(job.strategy?.matrix) })), + ), + ); -const dispatchOnly = ["fast-video-validation.yml"]; -const ungatedCacheWrites = (list) => - list - .filter(({ file }) => !dispatchOnly.includes(file)) - .flatMap(({ file, doc }) => - Object.entries(doc.jobs ?? {}).flatMap(([name, job]) => - (job.steps ?? []) - .map((step) => String(step.with?.["cache-to"] ?? "")) - .filter((to) => to.includes("type=gha") && !to.includes("github.event_name == 'push'")) - .map(() => `${file} ${name}`), - ), - ); - -// The Actions cache holds 10 GB; per-PR image exports evict the bun cache and leave installs cold. -test("only pushes to main write image layers to the Actions cache", () => { - for (const file of dispatchOnly) { - const { doc } = workflows.find((w) => w.file === file); - assert.deepEqual(events(doc.on), ["workflow_dispatch"], `${file} must stay dispatch-only`); - } - assert.deepEqual(ungatedCacheWrites(workflows), []); +// The Actions cache holds 10 GB. Per-PR exports evicted the bun cache; every shard of a main push +// exporting its own copy of the same layers evicted the release's prebuilt ffmpeg. +test("one job of a push to main writes each image cache", () => { + const writes = cacheWrites(workflows); + const ids = (list) => list.map((w) => w.id); + assert.deepEqual(ids(writes.filter((w) => !w.to.includes("github.event_name == 'push'"))), []); + assert.deepEqual( + ids(writes.filter((w) => w.matrix && !w.to.includes("strategy.job-index == 0"))), + [], + ); + const scopes = writes.map((w) => w.to.match(/scope=([\w-]+)/)?.[1]); + assert.deepEqual(scopes, [...new Set(scopes)]); }); test("preflight installs without downloading ffmpeg or Chrome", () => {