Skip to content

ci: fix rust caches and cut test wall time#17

Merged
krishna-santosh merged 2 commits into
mainfrom
ci/fix-caches
Jul 3, 2026
Merged

ci: fix rust caches and cut test wall time#17
krishna-santosh merged 2 commits into
mainfrom
ci/fix-caches

Conversation

@krishna-santosh

@krishna-santosh krishna-santosh commented Jul 3, 2026

Copy link
Copy Markdown
Member

Problem

Every rust CI job recompiled all ~490 crates on every run (~15-19 min wall) even though rust-cache logged full match: true. Two verified causes:

  1. Wrong cache directory. The cargo workspace root is the repo root (Cargo.lock + target/ live there), but every rust-cache step used workspaces: apps/backend — caching a target/ that never exists. Saved caches were 117MB of registry only. Warm-run logs showed 484 (Linux) / 493 (Windows) Compiling lines right after a full-match restore.
  2. Poisoned shared key. lint and the 3 db test lanes shared shared-key: linux. GH caches are immutable; lint (2.5 min) always saved first, freezing clippy check-artifacts into the key. Test lanes restored a useless cache and could never save their own.

Fix

  • workspaces: ". -> target" on every rust-cache step (ci + release — release builds were cold on every tag too)
  • split keys: linux-lint vs linux-test (the 3 db lanes build identical binaries, DB is chosen at runtime, so sharing among them is safe)
  • cache-on-failure: true so failed runs still seed the dep cache
  • CARGO_PROFILE_DEV_DEBUG=0 (CI env only): ~30-40% less compile+link, smaller caches
  • cargo-nextest in test lanes: runs all 4 test binaries in one parallel pool (cargo test ran them sequentially — 5.5 min of pure runtime on the Windows lane). pg/mysql keep the 4-concurrent-test cap.

Why nextest is safe now

nextest failed before because it is process-per-test: the once-per-process leftover-DB sweep in tests/common/test_db.rs ran concurrently in every test process and dropped cms_test_* databases of still-running tests. The sweep is now age-gated using the UUIDv7 timestamp embedded in each database name — only databases older than 5 minutes (i.e. leaks from aborted runs) are reclaimed. Bonus: process-per-test also removes the known env-var race between paths/config unit tests.

Expected

Warm runs: Linux test lanes ~10 min → ~3-5 min; Windows ~14-18 min → ~5-7 min. Will verify with a follow-up push once this run has seeded the caches.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved test database cleanup to avoid removing databases still in use during concurrent test runs.
    • Fixed database test execution so test arguments are passed correctly across the test matrix.
    • Updated CI to use a faster, more reliable Rust test runner, which should reduce flakiness and improve build performance.
  • Chores

    • Adjusted CI and release caching to better reuse build artifacts and speed up pipeline runs.

…xtest

Every rust job recompiled all ~490 crates on every run despite "full
match: true" cache restores, because rust-cache pointed at the workspace
member (apps/backend) while cargo writes artifacts to the repo-root
target/. The saved 117MB caches held only the registry.

- rust-cache: workspaces ". -> target" everywhere (ci + release)
- split lint/test cache keys: caches are immutable and lint always won
  the save race, freezing clippy check-artifacts into the key the test
  lanes restored; tests could never save codegen artifacts
- cache-on-failure: true so red runs still seed the dep cache
- CARGO_PROFILE_DEV_DEBUG=0 in CI: ~30-40% less compile+link, smaller
  caches; only cost is line numbers in CI backtraces
- cargo-nextest in test lanes: one parallel pool across the 4 test
  binaries (cargo test ran them sequentially - 5.5 min of runtime on
  the Windows lane alone); pg/mysql keep the 4-thread cap

nextest previously broke the suite: it is process-per-test, so the
once-per-process leftover-database sweep ran concurrently in every test
process and dropped databases of still-running tests. The sweep is now
age-gated - the UUIDv7 in each cms_test_* name embeds its creation time,
and only databases older than 5 minutes are reclaimed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@krishna-santosh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d3b2448-1bfd-4c0b-87c5-0590f391bb10

📥 Commits

Reviewing files that changed from the base of the PR and between 9f2db7c and 21930e1.

📒 Files selected for processing (2)
  • apps/backend/tests/common/test_db.rs
  • deny.toml
📝 Walkthrough

Walkthrough

CI and release workflows were updated to reconfigure rust-cache paths/keys, disable dev/test debug info, and switch Rust test execution from cargo test to cargo nextest run. Separately, the backend test database sweep logic now only drops cms_test_* databases older than a configurable minimum age.

Changes

CI/Release Workflow Updates

Layer / File(s) Summary
Build profile and lint caching
.github/workflows/ci.yml
Adds CARGO_PROFILE_DEV_DEBUG: 0 and reconfigures the lint job's rust-cache to use workspace root mapping and a linux-lint cache key with cache-on-failure: true.
Test matrix nextest adoption
.github/workflows/ci.yml
Adjusts MySQL test thread args, updates caching for DB and cross-platform test jobs, installs cargo-nextest, and replaces cargo test with cargo nextest run.
Release build cache path
.github/workflows/release.yml
Updates rust-cache mapping from apps/backend to repository root with clarified comments.

Test Database Sweep Gating

Layer / File(s) Summary
Age-gated leftover sweep
apps/backend/tests/common/test_db.rs
Adds SWEEP_MIN_AGE_MS and is_stale to parse UUIDv7 timestamps in database names, filters the sweep drop loop to only remove stale databases, and updates related documentation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as ci.yml
  participant Cache as rust-cache
  participant Nextest as cargo-nextest
  Workflow->>Cache: restore cache (. -> target, linux-test)
  Workflow->>Nextest: install cargo-nextest
  Workflow->>Nextest: run nextest run --no-default-features --locked
  Nextest-->>Workflow: test results
Loading

Related Issues: None specified.

Related PRs: None specified.

Suggested labels: ci, testing

Suggested reviewers: None specified.

Poem

A rabbit hops through workflows deep,
Caching keys it now will keep,
Nextest runs where cargo stood,
Sweeping stale, not live, for good.
Root and branch, the paths align—
Hop, hop, hooray, the build's just fine! 🐇

🚥 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 captures the main CI focus on Rust cache fixes and reduced test runtime.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/fix-caches

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
apps/backend/tests/common/test_db.rs (1)

200-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

is_stale has no unit tests.

This function encodes a somewhat fragile string-slicing/parsing contract (prefix strip + fixed-width hex slice) that's easy to get subtly wrong (see hyphen concern above). A couple of unit tests asserting behavior for a fresh vs. old vs. malformed name would catch regressions cheaply.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/tests/common/test_db.rs` around lines 200 - 205, Add unit tests
for is_stale covering the parsing contract around DB_PREFIX stripping and the
fixed-width UUID hex slice. Include cases for a fresh database name, an
old/stale name, and malformed inputs that fail prefix/length parsing so the
early return path is exercised. Place the tests near is_stale and use the same
DB_PREFIX-based naming pattern to validate behavior stays correct if the string
slicing logic changes.
.github/workflows/ci.yml (1)

203-205: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pin the cargo-nextest binary version.

The action is SHA-pinned, but tool: cargo-nextest still lets the installed nextest version drift, which can change CLI behavior or scheduling defaults between CI runs. Pin the validated cargo-nextest version in both jobs.

Also applies to: 247-249

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 203 - 205, The CI jobs using
taiki-e/install-action for cargo-nextest are only pinning the action SHA, not
the installed binary version, so nextest can still drift between runs. Update
the install configuration in both affected jobs to pin the validated
cargo-nextest version explicitly in the install-action inputs, keeping the
version consistent wherever cargo-nextest is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 203-205: The CI jobs using taiki-e/install-action for
cargo-nextest are only pinning the action SHA, not the installed binary version,
so nextest can still drift between runs. Update the install configuration in
both affected jobs to pin the validated cargo-nextest version explicitly in the
install-action inputs, keeping the version consistent wherever cargo-nextest is
used.

In `@apps/backend/tests/common/test_db.rs`:
- Around line 200-205: Add unit tests for is_stale covering the parsing contract
around DB_PREFIX stripping and the fixed-width UUID hex slice. Include cases for
a fresh database name, an old/stale name, and malformed inputs that fail
prefix/length parsing so the early return path is exercised. Place the tests
near is_stale and use the same DB_PREFIX-based naming pattern to validate
behavior stays correct if the string slicing logic changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4241d973-0d20-46f8-91d2-e51fac42c244

📥 Commits

Reviewing files that changed from the base of the PR and between f146855 and 9f2db7c.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • apps/backend/tests/common/test_db.rs

cargo-deny was already red on main: RUSTSEC-2026-0194/0195 (quick-xml
< 0.41 XML-parsing DoS). No upgrade path exists - even object_store
0.14 pins quick-xml ^0.40.1 - and the parser only sees XML responses
from the operator-configured S3 endpoint, not user input. Ignored in
deny.toml with justification; revisit when object_store allows >= 0.41.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@krishna-santosh krishna-santosh merged commit 87e196f into main Jul 3, 2026
12 checks passed
@krishna-santosh krishna-santosh deleted the ci/fix-caches branch July 3, 2026 06:31
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