Twenty-two minutes from push to green. Nobody had measured where it went, so nobody had fixed it. When we finally instrumented the pipeline, the result was embarrassing: about four minutes of real work and eighteen minutes of waiting.
Where the time actually went
| Stage | Before | After | What changed |
|---|---|---|---|
| Checkout + setup | 3m 40s | 0m 50s | Dependency cache |
| Install | 6m 10s | 1m 20s | Lockfile-aware cache key |
| Lint + typecheck | 2m 05s | 1m 10s | Ran in parallel with tests |
| Unit tests | 5m 30s | 1m 40s | Sharding across 4 runners |
| Build image | 4m 20s | 0m 55s | Layer cache + multi-stage |
| Total | 22m | 6m |
The four changes, in order of payoff
1. Cache the dependencies, keyed on the lockfile. Installing from scratch on every run is the single most common waste in CI. Key the cache on the hash of package-lock.json (or go.sum, or poetry.lock) so it invalidates exactly when it should.
2. Parallelise anything independent. Lint, typecheck and unit tests do not depend on each other. Running them sequentially is a choice, not a law. Fan out, then join.
3. Shard the test suite. If tests take five minutes, four runners take about eighty seconds. Most frameworks support sharding natively now; GitHub Actions matrix builds make it a few lines of YAML.
4. Stop rebuilding the image from cold. The image build was re-downloading base layers every run. A registry mirror plus a layer cache cut it from four minutes to under one.
The part nobody expects: queue time
We measured runner queue time separately and found another three minutes hiding in "waiting for an available runner" during peak hours. Autoscaling the runner pool removed it entirely — and it does not show up in the per-stage table above, because it happens before stage one.
If you cannot see queue time, you are measuring the pipeline, not the developer's wait.
Six minutes is not a magic number. The point is that the wait was measurable, and once it was measurable it was mostly trivial to remove. Measure first; the fix is usually boring.
Conversation
1 comment