Skip to content

perf(cli): parallelize package scanning with rayon #431

@BryanFRD

Description

@BryanFRD

Package
Which package does this affect? [x] cli [ ] action [ ] schema

Problem / Motivation

In `src/monorepo.rs`, `run_release_logic` iterates over `config.packages` sequentially (line 438: `for (pkg_idx, pkg) in config.packages.iter().enumerate()`). For each package it:

  1. Calls `crate::git::find_highest_semver_tag` (walks all tags via `tag_foreach`).
  2. Calls `get_commits_since_last_stable_tag` / `get_commits_since_last_tag` (revwalks the entire commit history from HEAD).
  3. Calls `read_version` (file I/O + parsing per format).

These are independent per-package: no shared mutable state, no inter-package ordering until the cascade phase that runs afterwards. On a 200-package monorepo (see `Fixtures/fixtures/examples/large-monorepo.json`) the wall time is dominated by the per-package revwalk, which is embarrassingly parallel.

Benchmarks (`Benchmarks` repo, fixture `mono-large-ferrflow-binary-check`) currently have absolute thresholds of 2000ms for check / 500ms for version on the large monorepo — there's headroom to claim a 4-8× speedup on multi-core CI runners.

Proposed solution

  1. Add `rayon = "1"` to `cli` feature deps.
  2. Extract the per-package analysis loop into a pure function: `(pkg, &config, &repo_root, &all_tags) -> PackagePlan { name, current, next, bump, commits, tag }`.
  3. Wrap with `config.packages.par_iter().map(...).collect::<Result<Vec<_>>>()`.
  4. Keep the write/commit/tag/hook side-effect phase sequential — it must remain deterministic.

`git2` repository handles are not Send, so each rayon thread opens its own `Repository` (cheap — discover only). Pre-compute `all_tags` once on the main thread before fanout, so the parallel work doesn't repeat `collect_all_tags`.

Alternatives considered

  • `tokio` async — `git2` is sync, no benefit.
  • Don't parallelize — fine for repos with <10 packages, but the benchmark fixture sized at 200 packages is unrealistic to keep serial.

Additional context

Severity: P2. Closes / aligns with #417 (wire benchmarks for regression tracking) — once parallelisation lands the benchmark numbers become the gate against future regressions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions