Skip to content

ci: parallelize workspace checks to cut PR build time - #353

Merged
norrietaylor merged 6 commits into
mainfrom
claude/build-performance-modularity-i5nzr
Jun 5, 2026
Merged

ci: parallelize workspace checks to cut PR build time#353
norrietaylor merged 6 commits into
mainfrom
claude/build-performance-modularity-i5nzr

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jun 5, 2026

Copy link
Copy Markdown
Member

Problem

The build and build-in-minimal jobs each recompiled the whole 24-crate workspace several times per PR:

  • build ran cargo buildcargo testcargo clippy sequentially. These three don't share compiled artifacts (clippy uses clippy-driver; build/test use different target metadata), so it was ~3 near-full compiles — and cargo build was redundant since cargo test already builds every target.
  • build-in-minimal ran minimal run ci = cargo test + cargo clippy again — ~2 more full compiles.

5 sequential full compiles per PR, behind weak caching (actions/cache keyed only on Cargo.lock, so target/ was only re-saved when deps changed). The slowness was redundant recompilation, not crate count.

Changes

Split the monolithic build job into parallel single-purpose jobs so wall-clock ≈ one compile instead of the sum:

Job Command Compiles
fmt cargo fmt --all -- --check 0
clippy cargo clippy --workspace --all-targets -- -D warnings 1
test cargo nextest run --workspace + cargo test --workspace --doc 1
  • Drop the redundant standalone cargo build (subsumed by cargo test).
  • Caching: replace the Cargo.lock-keyed actions/cache with Swatinem/rust-cache (registry/git state) + sccache (GHA backend) as a compiled-object cache shared across the clippy and test jobs, so the split doesn't blow up total minutes.
  • dogfood (was build-in-minimal): reduced to minimal run build-smoke — still proves minimal run works end-to-end without re-running the full test+clippy suite. Adds a build-smoke task (cargo build -p minimal) to .minimal/minimal.toml.
  • ci-success: aggregator job for branch protection to gate on. The docs-skip stub job is renamed buildci-success to keep docs-only PRs unblocked.

Release jobs, cargo-deny, and minimal-check are unchanged.

⚠️ Required before/at merge

The branch-protection ruleset on main must be updated to require ci-success instead of build / build-in-minimal. Otherwise PRs will block waiting on the old build check that no longer reports.

Verification

Validated locally: both workflows + minimal.toml parse, the job graph is consistent, minimal is a real package, and cargo fmt --all -- --check passes. The end-to-end payoff (parallel wall-clock, sccache hit rates) shows on this PR's CI run.


Generated by Claude Code

Summary by CodeRabbit

  • Chores
    • Split CI checks into separate format and linter jobs and improved caching for faster runs.
    • Updated test pipeline to use enhanced test runner, workspace doctests and a smoke build task.
    • Introduced a synthetic CI success gate to centralize release promotion logic.
    • Added a lightweight docs-only workflow that reports a successful status for markdown-only changes.

claude added 2 commits June 5, 2026 04:44
The build and build-in-minimal jobs each recompiled the whole 24-crate
workspace several times per PR: build ran cargo build, then cargo test,
then cargo clippy (each a near-full compile since the three don't share
artifacts), and build-in-minimal re-ran test + clippy via `minimal run
ci`. That is ~5 sequential full compiles.

Split the work into parallel single-purpose jobs so wall-clock time is
roughly one compile instead of the sum:

- fmt: cargo fmt --all -- --check (no compilation)
- clippy: cargo clippy --workspace --all-targets
- test: cargo nextest run --workspace plus a doctest pass

Drop the redundant standalone cargo build (cargo test already builds
every target). Replace the Cargo.lock-keyed actions/cache with
Swatinem/rust-cache for registry/git state and sccache (GHA backend)
as a compiled-object cache shared across the clippy and test jobs.

Reduce the dogfood job (was build-in-minimal) to `minimal run
build-smoke` so it still proves `minimal run` works without a second
full test+clippy pass, and add the build-smoke task.

Add a ci-success aggregator job for branch protection to gate on, and
rename the docs-skip stub job to match.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP
Use the current marketplace-recommended release of
mozilla-actions/sccache-action instead of v0.0.9.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@norrietaylor, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 60 minutes and 55 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: accddff1-9010-47db-8aed-b66c9270fdb8

📥 Commits

Reviewing files that changed from the base of the PR and between 5905f31 and 4e6b2be.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .minimal/minimal.toml
📝 Walkthrough

Walkthrough

The PR restructures GitHub Actions CI to separate quality-gate jobs (fmt, clippy, test) with improved caching, introduces a synthetic ci-success status aggregator to simplify release gating, aligns the docs-only skip workflow naming, and adds a build-smoke task for dogfood validation.

Changes

CI Workflow and Status Gating

Layer / File(s) Summary
Main CI workflow restructuring and status aggregation
.github/workflows/ci.yml
Separated fmt and clippy jobs, split test and dogfood into dedicated jobs, added cargo fetch --locked and cargo nextest in test, changed release to depend on ci-success, and added a ci-success job that always runs and fails if any gating job failed/cancelled.
Docs-only workflow alignment
.github/workflows/ci-docs-skip.yml
Renamed docs-only no-op job from build to ci-success and updated documentation/comments and the echo step to reference the ci-success required check.
Build smoke test task
.minimal/minimal.toml
Added build-smoke task that runs cargo build -p minimal with inherit_cwd = true to support the dogfood smoke test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Gates split, the runner hums a tune,
fmt, clippy, tests beneath the moon,
A single check now watches all,
build-smoke hops in, answers the call,
Pipelines tidy — CI sings soon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: refactoring CI to parallelize workspace checks to reduce PR build time, which is directly reflected in the workflow splits and caching improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Empty commit to measure run-over-run sccache/rust-cache improvement.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP

Copy link
Copy Markdown
Member Author

CI timing — measured results (baseline)

Measured on real Actions runs. "Old CI" = a recent main run (build + build-in-minimal, warm actions/cache). "New" = this PR's runs (fmt/clippy/test/dogfood in parallel).

Wall-clock (PR critical path)

Old CI New — cold cache New — warm cache
Total ~32 min ~17.3 min ~13.8 min
fmt (2s, inside build) 5s 11s
clippy (8.9m, inside build) 13.1m 10.7m
test (3.5m, after 11.8m cargo build) 17.3m 11.1m
dogfood (was build-in-minimal) 30.1m 15.1m 13.8m

Old jobs for reference: build = 32.3 min (serial cargo build 11.8m → test 3.5m → clippy 8.9m), build-in-minimal = 30.1 min.

Takeaways

  • ~32 → ~14 min (~57%), almost entirely from parallelizing the jobs and dropping the redundant cargo build (cargo test already builds every target).
  • The sccache layer under-delivered. The warm clippy job hit a 90% cache rate yet only improved 13.1 → 10.7 min, because (from the sccache stats in the logs):
    • ~138s spent downloading 875 cached objects from the GHA cache (per-object round-trips: cache_read_hit_duration: 137.96s);
    • 184 compilations are non-cacheable every run (proc-macros, crate-type=bin, and build scripts — this repo is protobuf-heavy via prost-build/tonic-prost-build, and sccache never caches build-script output or linking);
    • 239 cache write errors on the cold run (parallel clippy+test write to one shared sccache namespace and collide) → 97 forced misses on the warm run.
  • dogfood builds -p minimal through the sandboxed minimal tool and barely warms (15.1 → 13.8 min); it is the warm long pole. (Tracked separately; not addressed in this round.)

Next: replacing sccache with Swatinem/rust-cache target caching + trimming Free Disk Space

Incoming in this PR. Expectation: one tarball restore instead of 875 object reads, build-script/dep artifacts cached directly, no cross-job write contention. Re-measured numbers to follow in a reply.


Generated by Claude Code

claude added 3 commits June 5, 2026 05:47
The sccache layer hit 90% on warm runs but barely helped: ~138s went to
downloading cached objects from the GHA cache one-by-one, build scripts
and proc-macros (184 compilations) are never cacheable by sccache, and
the parallel clippy+test jobs collided writing one shared namespace (239
write errors, causing forced misses on the next run).

Switch the clippy and test jobs to Swatinem/rust-cache with its default
target caching: one tarball restore of the registry plus dependency
build artifacts (including build-script output), keyed per job so the
two jobs no longer contend. Also trim the Free Disk Space step to the
fast rm-based removals, dropping the slow apt package purge.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP
Replace the single `minimal run build-smoke` dogfood step with a matrix
that runs the real fmt, clippy, and test work through `minimal run`, so
the tool is exercised on the same checks as the cargo jobs without a
serial pass.

Break the monolithic `[tasks.ci]` blob into `ci-fmt`, `ci-clippy`, and
`ci-test` sub-tasks aligned to the cargo jobs (`fmt --all`,
`clippy --workspace --all-targets`, `test --workspace`), and drop the
now-unused `build-smoke` task. `minimal` has no native parallel or
composite task support, so the GitHub Actions matrix provides the
parallelism.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP
This reverts commit 4e6b2be. The matrix legs run inside minimal's
sandbox, which does not use rust-cache/sccache, so each leg stayed a
full ~13-16 min sandboxed compile and parallelizing them did not move
the wall-clock. Restore the lightweight `minimal run build-smoke`
dogfood and the original task definitions.

https://claude.ai/code/session_01Xm385bunemjDpH9rz9AFZP

Copy link
Copy Markdown
Member Author

CI timing — after replacing sccache with Swatinem/rust-cache (target caching) + trimming Free Disk Space

Measured on real runs of identical config (5905f31 cold → 22a2515 warm), compared against the earlier sccache setup (d659ce6 cold → 663f553 warm). Per-job wall-clock; the Run column is the actual compile/lint/test step.

clippy

total Run Clippy step
sccache — cold 13.1m 10.8m
sccache — warm 10.7m 8.4m
rust-cache — cold 11.25m 9.9m
rust-cache — warm 2.5m 0.5m

test

total nextest step
sccache — cold 17.3m 14.1m
sccache — warm 11.1m 9.5m
rust-cache — cold 15.6m 12.8m
rust-cache — warm 4.4m 2.1m

Free Disk Space step (trimmed: dropped the slow apt package purge)

~1.8m → 44s–1.3m.

Why rust-cache wins

  • One tarball restore (~13–22s) instead of sccache fetching 875 objects one-by-one (~138s of cache_read_hit_duration).
  • Caches dependency .rlibs and build-script output directly → cargo skips deps entirely. This repo's protobuf build scripts (prost-build/tonic-prost-build) were in sccache's 184 "non-cacheable" compilations; rust-cache restores their output.
  • Per-job cache keys → no cross-job write contention (sccache had 239 write errors → 97 forced warm misses). rust-cache warm runs hit clean.

Net for the cached jobs, warm: clippy 10.7m → 2.5m, test 11.1m → 4.4m.

⚠️ Honest caveat: end-to-end PR wall-clock is unchanged

The PR's critical path is now gated by dogfood (~15–16m), which runs minimal run build-smoke inside minimal's sandbox using minimal's own state cache — it does not use rust-cache and barely warms. So clippy/test finishing in 2.5/4.4m now just means they idle while dogfood runs.

  • Compute/cost drops meaningfully (warm clippy+test went from ~22 job-min to ~7).
  • Wall-clock won't drop below ~15m until dogfood's sandboxed build is cached or slimmed — tracked separately; not in scope here.
Wall-clock (PR critical path) value bound by
Original CI (build + build-in-minimal) ~32m serial build job
This PR, warm ~15–16m dogfood (uncached)

Generated by Claude Code

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.

3 participants