Each commit is a discrete-event job flowing through five stages: build, then lint and unit test run in parallel, a join waits for both, then UI test, then deploy. Every stage has a fixed number of parallel runners (workers); when all runners are busy, arriving commits wait in that stage's queue — this is the classic multi-server queueing model (M/M/c) applied to a build farm.
stage done → roll(failProb)
fail → commit blocked, drops out
pass → next stage (or join point)
join(lint, unit) → proceed only if both passed
- Commit rate — how often new commits arrive; push it past what the runners can drain and queues grow without bound (backpressure).
- Runners per stage — parallel capacity at every stage; the build stage is usually the bottleneck since everything must pass through it first.
- Failure probability — per-stage chance a commit is blocked; UI tests fail roughly 1.6× as often as lint in this model, matching real-world flakiness.
- Lead time — wall-clock time from commit arrival to either a successful deploy or a stage failure; it grows sharply once any stage saturates.
Real-world relevance: this is exactly why teams shard test suites across more runners, gate merges on fast checks (lint) before slow ones (UI tests), and treat a red build as blocking — a single overloaded stage throttles every commit behind it.