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
+49 -14
View File
@@ -169,7 +169,7 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-test
cache-shared-key: ci-dev
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -183,6 +183,19 @@ jobs:
echo "started_at=$(date --utc --iso-8601=seconds)"
} > artifacts/test-and-lint/run-metadata.txt
# This job is the sole writer of the ci-dev cache, so what it builds is
# what every reader restores. Its own steps are not a superset of theirs:
# clippy only emits metadata, the nextest pass excludes e2e_test, and
# nothing here enables e2e-test-hooks (a different feature set means a
# different -Cmetadata). Without these two builds the readers restore a
# cache that is missing exactly the dependencies they need. Runs on main
# only, so it costs nothing on the PR critical path.
- name: Warm shared cache superset (main only)
if: github.ref == 'refs/heads/main'
run: |
cargo build --tests -p e2e_test
cargo build -p rustfs --bins --features e2e-test-hooks
# Clippy runs before the test pass: lint failures are the most common
# CI-only breakage and should surface in minutes, not after 20+ minutes
# of tests.
@@ -362,9 +375,9 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-ilm-serial
cache-shared-key: ci-dev
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-save-if: 'false'
# test_transition_and_restore_flows was re-enabled by rustfs/backlog#1303:
# its "missing xl.meta on disk2" was a test-util bug (open_disk hardcoded
@@ -400,10 +413,17 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-test-rio-v2
cache-shared-key: ci-feat-rio
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
# Sole writer of ci-feat-rio. The reader (build-rustfs-debug-binary-rio-v2)
# builds with rio-v2,e2e-test-hooks, which this job never enables, so warm
# that combination here or the reader restores a cache without it.
- name: Warm shared cache superset (main only)
if: github.ref == 'refs/heads/main'
run: cargo build -p rustfs --bins --features rio-v2,e2e-test-hooks
- name: Run rio-v2 clippy lints
run: cargo clippy -p rustfs -p rustfs-ecstore --all-targets --features rio-v2 -- -D warnings
@@ -442,9 +462,17 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-test-${{ matrix.features.name }}
cache-shared-key: ci-feat-proto
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-save-if: ${{ github.ref == 'refs/heads/main' && matrix.features.name == 'swift' }}
# The swift leg is the sole writer of ci-feat-proto, shared with the sftp
# leg. Build the other leg's feature set too so the saved target/ holds
# the union; running them in sequence is safer than `--features
# swift,sftp`, which is a combination no lane actually compiles.
- name: Warm shared cache superset (main only)
if: github.ref == 'refs/heads/main' && matrix.features.name == 'swift'
run: cargo clippy -p rustfs -p rustfs-protocols --all-targets --features sftp -- -D warnings
- name: Run clippy with ${{ matrix.features.name }}
run: |
@@ -470,8 +498,8 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-rustfs-debug-binary
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-shared-key: ci-dev
cache-save-if: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build debug binary
@@ -501,8 +529,8 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-rustfs-debug-binary-rio-v2
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-shared-key: ci-feat-rio
cache-save-if: 'false'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build debug binary with rio-v2
@@ -541,6 +569,13 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
# Keeps its own key rather than joining ci-dev. rust-cache's key is
# built from runner.os/arch plus rustc and lockfile fingerprints — it
# does NOT include the runner label or image. ubuntu-latest and
# sm-standard-4 are therefore indistinguishable to it, so sharing a key
# would let two different system images overwrite each other's
# artifacts, and would make a 2-core hosted runner unpack ci-dev's ~3GB
# instead of this lane's ~1.3GB.
cache-shared-key: ci-uring
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -591,9 +626,9 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-e2e
cache-shared-key: ci-dev
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-save-if: 'false'
# Download after the cache restore so the freshly built binary from the
# build job always wins over anything restored into target/debug.
@@ -672,9 +707,9 @@ jobs:
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-e2e
cache-shared-key: ci-dev
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
cache-save-if: 'false'
# Download after the cache restore so the freshly built binary from the
# build job always wins over anything restored into target/debug.