Skip to content

perf(cache): cross-run cache for per-package walk#585

Merged
BryanFRD merged 1 commit into
mainfrom
feat/cross-run-cache
Jun 16, 2026
Merged

perf(cache): cross-run cache for per-package walk#585
BryanFRD merged 1 commit into
mainfrom
feat/cross-run-cache

Conversation

@BryanFRD

Copy link
Copy Markdown
Contributor

Closes #508

What

Adds a cross-run cache for the dominant cost of ferrflow check: the per-package last-tag → HEAD commit walk and bump decision. On CI a PR loops lint → build → test → check on the same commit, recomputing the same result every time. This caches the computed result so repeat invocations on an unchanged (HEAD, tags, config) return instantly.

What is cached

The rendered dry-run result of check: the JSON string (for --json) or the text output lines (default). The value type is cache::CachedRun { json: Option<String>, text_lines: Vec<String> }, written by capturing the RunOutput that the compute path now returns instead of printing inline. A cache hit reprints these verbatim, so hit output is identical to a fresh compute by construction.

Cache key

Hash of HEAD commit sha + a sha256 of the sorted refs/tags/* → oid lines + a sha256 of the config file bytes, plus an output variant marker (text/json) so check and check --json don't collide. Filename: <head>-<tags_hash>-<config_hash>-<variant>.json under .git/ferrflow-cache/ (resolved via repo.git_dir()).

Which commands use it

  • check (default and --json) reads and writes the cache.
  • status does NOT — it has a separate, simpler compute (PackageStatus), not the shared per-package walk, so wiring it in would not be the same result. Left clean.
  • version / tag are already cheap; uncached on purpose.
  • release MUST NOT and does NOT touch the cache (always recomputes — mutating on stale data is unsafe). This bounds the blast radius: a cache bug can only affect advisory check output.

Caching is also skipped when --verbose, a --channel override is set, or the config has hooks/publishers configured (those produce side-effecting dry-run previews that aren't part of the cached value).

Edge cases

  • Read-only .git: every write is best-effort (atomic temp + rename) and silently no-ops on any IO error — never errors.
  • Corrupt/garbage cache file: ignored, falls back to recompute, no panic.
  • Freshness: entries older than 5 minutes (mtime) are treated as a miss.
  • Prune on write: drop entries older than 7 days and cap at 50 (best-effort, errors ignored).

New subcommand

ferrflow cache clear deletes .git/ferrflow-cache/.

Tests

  • cache.rs: write→read round-trip, tamper→miss, read-only parent→no-op no-error, freshness window, prune by age + by cap, ignore non-json files, filename varies per key component, and key busts on new HEAD / new tag / config edit (against real temp repos).
  • monorepo/tests.rs: end-to-end — first check writes the cache, second hits with the same key and identical lines; a new commit busts the key.

Verified manually in a temp repo: two check runs on the same commit (miss then hit, identical output), a new commit busts it, and cache clear removes the dir. release --dry-run creates no cache dir.

Follow-ups (separate repos, out of scope here)

  • Public changelog entry (FerrLabs/Changelog) for the new cache clear subcommand and the perf win.
  • Docs/site mention of cache clear on ferrflow.com (FerrFlow-Cloud).

@BryanFRD BryanFRD enabled auto-merge (squash) June 16, 2026 20:51
@BryanFRD BryanFRD merged commit a074656 into main Jun 16, 2026
39 checks passed
@BryanFRD BryanFRD deleted the feat/cross-run-cache branch June 16, 2026 20:53

@github-actions github-actions 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.

Benchmark

Details
Benchmark suite Current: 93bff76 Previous: 0b5fe57 Ratio
config_loading/single 12239 ns/iter (± 235) 10516 ns/iter (± 92) 1.16
config_loading/mono_10 17395 ns/iter (± 247) 15635 ns/iter (± 739) 1.11
config_loading/mono_50 39196 ns/iter (± 1272) 40629 ns/iter (± 432) 0.96
config_loading/mono_100 65742 ns/iter (± 878)

This comment was automatically generated by workflow using github-action-benchmark.

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.

perf: cross-run cache for tag→commits walk under .ferrflow/cache/

1 participant