ci: fit the Rust caches back inside the 10GB quota (#5532)

The repository has 18 rust-cache families of 1.2-3.1GB each against GitHub's
fixed 10GB per-repo quota. Measured usage sat at 9.72GB, 9.96GB and 11.63GB on
three samples, so LRU eviction is continuous and the main lanes lose: ci-test
and ci-e2e were repeatedly absent from the surviving entries. That is what
makes "Setup Rust environment" bimodal — 0.7-3.4 minutes warm against
11.8-20.9 minutes cold.

Four changes, all cache-only. No job builds or tests anything different.

Collapse ci.yml's nine cache sites into four keys, each with exactly one
writer: ci-dev (test-and-lint writes; ILM, debug-binary, e2e-tests and e2e-full
read), ci-feat-rio (rio-v2 lint writes, its debug-binary reads), ci-feat-proto
(the swift leg writes for both protocol legs), and ci-uring, which stays alone.
e2e-full is easy to miss here: it already shared ci-e2e with e2e-tests and both
saved, so without an explicit 'false' it would have become a second unnamed
writer of ci-dev. rio/swift/sftp are deliberately NOT merged — measured at
2365/2307/1200MB they are not near-identical, and none is a superset of the
others.

Give each writer a superset warm-up on main. A writer's own steps are not
automatically a superset of its readers': clippy emits metadata only, the
nextest pass excludes e2e_test, and no lint lane enables e2e-test-hooks, whose
different feature resolution yields a different -Cmetadata. Without these
builds the readers would restore a cache missing precisely what they need.
Guarded to main, so the PR critical path is unaffected.

Stop writing tag-scoped caches in build.yml. A cache saved on refs/tags/X can
only be restored by a re-run of that same tag, so each release cycle wrote up
to 12 unreadable 1-2GB entries that evicted the hot lanes. Tag builds still
restore the main-scoped cache. The one real cost is that re-running a failed
leg of the same tag now falls back to main's cache.

Flip the composite action's cache-save-if default to 'false' and make the input
mandatory in practice. audit.yml was relying on the old "true" default: every
PR touching Cargo.toml or Cargo.lock saved a second, PR-scoped copy (~843MB
measured, job 91048468127) that pushed main-scoped lanes out of the quota. The
fail-safe direction is a cold cache, not a stolen quota slice.
scripts/security/check_cache_save_if.sh now asserts every call site states it,
wired into audit.yml next to the existing pin check.

While there, cargo-deny stops pulling the full setup composite. It compiles
nothing, so apt, protoc, flatc and nextest were pure overhead — but it does run
cargo metadata, and Cargo.toml pins datafusion and s3s as git dependencies that
must be materialised into ~/.cargo/git, so the cache itself stays.

Refs: rustfs/backlog#1598, rustfs/backlog#1600
This commit is contained in:
Zhengchao An
2026-08-01 11:06:31 +08:00
committed by GitHub
parent 790bdc0e63
commit 7354a5663d
5 changed files with 153 additions and 20 deletions
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Every `uses: ./.github/actions/setup` must state `cache-save-if` explicitly.
#
# The composite action's default is "false" (fail-safe: a forgotten input costs
# a cold cache, not a slice of the repository-wide 10GB Actions cache quota).
# But a silent default in either direction is how audit.yml ended up saving a
# ~843MB PR-scoped entry on every dependency change, evicting the main-scoped
# lanes that the expensive jobs restore from. Requiring the input to be spelled
# out keeps the decision visible in review.
#
# Usage: scripts/security/check_cache_save_if.sh
set -euo pipefail
cd "$(dirname "$0")/../.."
status=0
for file in .github/workflows/*.yml .github/workflows/*.yaml; do
[ -e "$file" ] || continue
# Walk the file, and for every `uses: ./.github/actions/setup` scan the rest
# of that step for cache-save-if. `uses:` and `with:` are siblings at the
# same indentation, so the step ends only at a line indented *less* than the
# `uses:` line (the next `- name:` item, or a dedent out of the steps list).
awk -v file="$file" '
function flush() {
if (pending && !found) {
printf "%s:%d: `uses: ./.github/actions/setup` without an explicit `cache-save-if:`\n", file, uses_line > "/dev/stderr"
bad++
}
pending = 0; found = 0
}
{
line = $0
sub(/[[:space:]]+$/, "", line)
if (line ~ /^[[:space:]]*#/ || line ~ /^[[:space:]]*$/) next
match(line, /^[[:space:]]*/)
indent = RLENGTH
if (pending && indent < uses_indent) flush()
if (line ~ /uses:[[:space:]]*\.\/\.github\/actions\/setup[[:space:]]*$/) {
flush()
pending = 1; found = 0
uses_indent = indent
uses_line = NR
next
}
if (pending && line ~ /^[[:space:]]*cache-save-if:/) found = 1
}
END { flush(); exit (bad > 0) }
' "$file" || status=1
done
if [ "$status" -ne 0 ]; then
echo "" >&2
echo "Add an explicit cache-save-if to each call above, e.g." >&2
echo " cache-save-if: \${{ github.ref == 'refs/heads/main' }} # this lane owns the key" >&2
echo " cache-save-if: 'false' # this lane only reads it" >&2
echo "Exactly one lane per shared-key may save; see rustfs/backlog#1600." >&2
exit 1
fi
echo "OK: every ./.github/actions/setup call states cache-save-if explicitly"