Sharding, parallelism and runtime budgets
A forty-minute suite gets skipped; a four-minute one gets trusted. Splitting work across workers, balancing shards, and knowing what your pipeline costs per run. This is often the highest-impact work available to a test engineer.
Three different things speed up a suite, and they are often confused.
Parallelism runs several tests at once on one machine, usually one worker per CPU core. Cheap, and limited by that machine.
Sharding splits the suite across several machines, each running a slice. Machine three runs shard three of five. Slower to start, and it scales as far as your budget does.
Deletion removes tests that do not earn their runtime. Free, immediate, and the option people reach for last.
A runtime budget is the number that forces the conversation: this suite must finish in ten minutes, so adding a test means making something faster or removing something else.
The terms you will hear
- Worker. One parallel process running tests on a machine.
- Shard. One slice of the suite, run on its own machine.
- Fan-out and fan-in. Splitting work across machines, then merging the reports.
- Runtime budget. An agreed maximum wall-clock duration for a stage.
- Critical path. The slowest shard, which sets the total time regardless of the others.
- Test isolation. Each test owning its own data, which is what makes any of this safe.
What each one buys
Parallelism is the first thing to turn on and it costs nothing. Four workers on a four-core runner typically cuts a suite to a third, not a quarter, because of setup overhead.
Sharding is next, and it is bounded by two things. Every shard pays the setup cost again, so twenty shards of thirty seconds each is mostly setup. And the total is set by your slowest shard, so a badly balanced split wastes most of what you paid for.
Deletion beats both on cost. Removing 200 tests that have never failed and cover a retired feature is faster than any infrastructure change, per choosing what stays.
Why it matters
Because time-to-feedback governs how the team works, and a growing suite consumes it silently.
For example, a suite of 900 tests grows by 40 a month. At some point the required stage passes twelve minutes, people start batching their merges, reviews get bigger, and diagnosing a failure gets harder. Nobody decided any of that. The suite simply grew and nothing pushed back.
A budget is what pushes back.
How to do it properly
- Turn on parallelism first, with workers matched to cores. Measure before and after.
- Fix the state collisions it exposes. Per-test data, no shared fixtures, no order dependence.
- Measure per-test duration and look at the slowest twenty. There is usually a handful of tests taking a disproportionate share.
- Shard by measured time, not by file count. Most runners support it. The difference is four balanced shards, rather than one that takes twice as long as the rest.
- Merge the reports. A run split into five pieces needs one readable result, or nobody looks.
- Cut the setup cost per shard. Reuse containers, cache dependencies, seed reference data once.
- Set the budget and defend it. When the stage exceeds it, something moves later or gets deleted.
A worked budget
For example, here is one suite brought back inside its window.
SUITE 1,180 tests. required stage budget: 6 minutes. actual: 21 minutes.
STEP 1 measure where the time goes
unit + component 312 tests 95s
api + integration 128 tests 2m10
e2e 38 tests 17m30 <-- 83 percent of the time
slowest 5 e2e tests carried 6m20 between them
STEP 2 delete what does not earn it (free)
9 e2e tests for the retired wishlist feature -3m10
4 duplicates of the checkout journey -1m50
2 that had never failed in 2 years, low consequence -55s
now: 23 e2e tests, 11m35
STEP 3 move what does not need to gate
17 e2e journeys moved to the advisory on-merge stage -8m
6 remain as gates: buy, buy with gift card, refund,
login, password reset, mobile checkout
now: required stage 6 minutes 20
STEP 4 parallelism (was 1 worker)
4 workers on the same runner
exposed 11 state collisions on 3 shared seeded accounts
fixed with per-test builders
now: required stage 2 minutes 40
STEP 5 shard the advisory stage
the 17 moved journeys, 4 shards balanced by measured duration
8m wall clock -> 2m50. shard times 2m40, 2m50, 2m35, 2m45.
balanced by time, not by count, so nothing waits on one slow shard.
RESULT
required stage 21m -> 2m40, well inside the 6-minute budget
advisory stage 2m50, reported not gating
infrastructure cost 4 runners instead of 1, on merges only
tests deleted 15, none missed in six months
ORDER OF SAVINGS
deletion and re-staging bought 14 minutes. parallelism bought 3m40.
sharding bought 5m on the stage that no longer blocks anybody.The order at the bottom is the lesson. The free changes bought most of the time, and the infrastructure changes were worth doing only after that.
How to show you know it
- A time breakdown by stage. Knowing that 83 per cent sat in 38 tests is what makes the next decision obvious.
- The collisions parallelism exposed. Eleven of them, fixed with per-test data, is a genuine engineering result.
- Shards balanced by duration. Quoting the four shard times shows you understand the critical path.
- A defended budget. "Six minutes, so this new test displaces one." That sentence keeps a suite alive for years.
Questions
How many workers should I use?
Start at the number of cores on the runner and measure. More workers than cores usually makes it slower, and memory limits bite before CPU does on browser tests.
Is sharding worth it for a small suite?
Rarely. Each shard pays setup again, so below a few minutes of test time the overhead dominates. Parallelism and deletion get you further.
Our tests fail when run in parallel. What now?
They were never isolated, and running them in sequence was hiding it. Give each test its own data. It is work, and it fixes flakiness as well as speed.
Should the whole suite run on every commit?
No. The stable, fast, meaningful part gates; the rest reports later or overnight, which is the staging in CI for test suites.