Pipeline Performance Tuning Basics in Jenkins
Learn how to make Jenkins pipelines finish sooner and cost less: where the time really goes, how to measure it, and the everyday fixes for checkouts, caches, tests, agents, logs and the controller itself, without guesswork.
A slow pipeline is a tax on every developer, every day. A change that should take five minutes to verify takes forty. People push less often, batch their work into large risky changes, switch to other tasks while they wait, and forget what they were doing when the result finally arrives. Multiply that by a whole team and the loss is enormous, even though nobody ever sees it as a line on a bill.
The good news is that most slow pipelines are slow for a small number of ordinary reasons. Time is wasted downloading the same libraries again and again, cloning more history than needed, running steps one after another that could run side by side, waiting in a queue for a machine, or running work that nobody needed. These problems are easy to understand and often easy to fix, once you can see them.
This guide starts from zero. Every new word is explained when it first appears, everyday pictures make ideas stick, and the later chapters cover the basics that experienced engineers apply first: measuring before changing, finding the critical path, speeding up checkouts and caches, ordering and splitting tests, protecting the controller, keeping logs lean and watching for regressions over time.
1What Is Pipeline Performance Tuning?
A plain-language first look before any details.
Jenkins is a free, open-source automation server that builds, tests and delivers software. Its work is written as a pipeline, a recipe stored in a text file called the Jenkinsfile. A pipeline is divided into stages, such as Build, Test and Deploy, and each stage contains steps, the single actions Jenkins performs.
Performance tuning means changing how a pipeline is written and how Jenkins is set up so that the same work finishes sooner, uses fewer resources, or both. It is not about clever tricks. It is about finding where time goes, removing waste, and doing independent things at the same time.
Think of getting ready for work in the morning. If you boil water, then wait, then toast bread, then wait, then shower, the whole routine takes an hour. If you start the water, toast the bread while it heats and shower while the toast cooks, it takes twenty minutes. You did not move faster; you stopped waiting for things one after another. You also stopped walking to the shop for milk every day and bought a week’s worth. Pipeline tuning is the same kind of work: overlap the waits, and stop repeating the shopping.
Never tune by guessing. A change that feels clever but does not touch the slowest part of the pipeline saves nothing. The first skill in performance work is measuring, and this guide comes back to that again and again.
The same ideas apply whether you run a small team on two machines or a large organization on hundreds. The numbers differ, but the method is identical: measure, find the biggest cost, fix it, and measure again.
2Why Speed Matters
The hidden costs of slow pipelines.
Feedback delayed is feedback lessened
The value of a build is the information it gives a developer: “your change is fine” or “your change broke something.” That information is most useful while the developer still remembers the change. After ten minutes the memory is fresh. After an hour, the developer has moved on, and coming back costs time to reload the context.
Slow pipelines change behavior
When feedback is slow, people push large batches of changes to avoid waiting several times. Large changes are harder to review and more likely to hide bugs. When the pipeline is fast, people push small changes, and small changes are safer.
Money and machines
Every minute a build runs holds a work slot, an executor, on a machine. On cloud machines billed by time, slower builds cost more. In a busy installation, slow builds also lengthen the queue for everyone else.
Nobody notices
The pipeline starts fast. Each new test or check adds a minute here and there.
Drift
After a year, the pipeline that took eight minutes takes thirty-five. No single change was to blame.
Complaints
Developers grumble, batch their changes and start skipping checks.
Tuning
Someone measures and finds that a few causes account for most of the time.
Guarding
Duration is tracked, so drift is caught early next time.
3Core Concepts You Need First
A handful of small ideas that make everything else easy.
Wall-clock time
Wall-clock time is the time on the clock from the moment a build is requested to the moment it finishes. It is what the developer experiences, and it includes waiting.
Queue time and run time
Queue time is how long a build waits for a free machine. Run time is how long it takes once it starts. Tuning run time does nothing for a build that spends most of its life waiting in the queue, so always look at both.
Critical path
The critical path is the longest chain of steps that must happen one after another. Steps that run in parallel do not add up; the longest of them decides the total. Only shortening the critical path shortens the pipeline. Speeding up something that is not on it changes nothing.
Latency and throughput
Latency is how long one build takes. Throughput is how many builds the system can complete per hour. Some changes improve one but hurt the other, for example running many builds at once on one machine raises throughput but slows each build.
Cache
A cache is a saved copy of something expensive to produce, kept so it does not have to be produced again. Downloaded libraries and compiled code are the usual candidates.
Controller and agent
Jenkins has a main server, the controller, and worker machines called agents. Heavy work belongs on agents. The pipeline’s own decision-making runs on the controller and should stay light.
| Term | Simple meaning | Why it matters |
|---|---|---|
| Wall-clock time | Time the developer waits | The number that people feel |
| Queue time | Waiting for a machine | Needs capacity fixes, not code fixes |
| Run time | Time from start to finish on a machine | Where most tuning happens |
| Critical path | Longest chain of dependent steps | The only path worth shortening |
| Latency | Duration of one build | Developer experience |
| Throughput | Builds completed per hour | Team capacity |
| Cache | A saved result to reuse | Removes repeated work |
4Architecture: Where the Time Goes
The parts of a Jenkins setup that each add a little delay.
A pipeline run is the journey of a change through several components. Each hop can add delay, and knowing the route tells you where to look.
flowchart LR
DEV["Developer pushes code"] --> SCM["Source Control Server"]
SCM -->|"webhook"| CTRL["Jenkins Controller"]
CTRL --> Q["Build Queue"]
Q --> AGT["Agent with free executor"]
AGT --> CO["Checkout"]
CO --> DEP["Download dependencies"]
DEP --> BLD["Compile and package"]
BLD --> TST["Run tests"]
TST --> PUB["Publish artifacts and reports"]
PUB --> NOTI["Notify and update status"]
CTRL -.->|"runs pipeline logic"| AGT
AGT -.->|"streams logs"| CTRL
Read the diagram from left to right. The push reaches the controller through a webhook, a message sent automatically when something happens. The build waits in the queue for an agent. On the agent, it checks out code, downloads libraries, compiles, tests and publishes. Meanwhile the controller runs the pipeline’s own logic and receives the log lines the agent sends. Time is spent in each box, and the biggest box is usually not the one people suspect.
Queue
Time before the build even starts. Depends on capacity and label choices.
Checkout
Downloading the source code. Large repositories and full history make this slow.
Dependencies
Downloading libraries and images. Often the largest hidden cost on fresh agents.
Build and test
The real computation. Can be sped up with caches, incremental builds and parallel work.
Publishing
Uploading artifacts and reports. Large files through the controller cause slowdowns.
Controller logic
The pipeline script itself. Small when simple, expensive when it does heavy work.
5Measure First: The Tools You Already Have
You cannot fix what you cannot see.
Jenkins gives you several ways to see where time goes, and most cost nothing to switch on.
| Tool | What it shows | Best used for |
|---|---|---|
| Timestamps in the log | The time of every log line | Spotting gaps and slow commands within a stage |
| Stage view or pipeline graph | Duration of each stage across many builds | Finding the slowest stage and its trend |
| Pipeline steps page | Duration of individual steps in one build | Drilling down inside a slow stage |
| Build history trend | Total duration of recent builds | Seeing drift over weeks |
| Queue information | Why and how long builds wait | Separating waiting from working |
| Monitoring plugins | Numbers exposed to dashboards such as Prometheus | Long-term trends and alerts |
| Agent resource graphs | Processor, memory, disk and network on the machine | Finding out whether a step is limited by computing or by waiting |
Switch on timestamps
The timestamps option prefixes each log line with the time. It costs almost nothing and turns a wall of text into a timeline. With it, a gap of four minutes between two lines is immediately visible, and you can ask what happened there.
Separate queue time from run time
The duration shown for a build usually counts only the time after it started running. A build that shows twelve minutes may have waited fifteen minutes before that. Always check the queue separately, or you may spend a week tuning code when the real problem is a shortage of machines.
Comparing one fast build with one slow build and deciding what changed. Builds vary naturally. Look at at least twenty recent runs, and use the middle value and the slow tail, not a single sample.
Example: what timestamps reveal
A team believes their tests are slow. Timestamps show that the test stage starts eleven minutes after the build begins. The gap is a full dependency download on a fresh agent. The tests themselves take four minutes. Adding a package proxy removes ten of those eleven minutes without touching a single test.
6The Tuning Loop
A repeatable method that keeps you honest.
flowchart LR
M["Measure: collect durations of many runs"] --> F["Find the biggest cost on the critical path"]
F --> H["Form a simple hypothesis"]
H --> C["Change one thing"]
C --> V["Verify with new measurements"]
V --> Q{"Improved?"}
Q -->|"Yes"| K["Keep it and note the gain"]
Q -->|"No"| R["Revert and try another idea"]
K --> M
R --> F
Measure
Collect durations for the last twenty or more runs. Record queue time and run time separately.
Find the biggest cost
List the stages and steps by duration. Mark which are on the critical path.
Hypothesize
Say in one sentence why it is slow, such as “we download everything on every build.”
Change one thing
Make one change, not five. Otherwise you will not know which one helped.
Verify
Run enough builds to be sure. Compare the middle and slow values, not only the best one.
Record
Write down what you changed and what it gained, so the team learns and nobody undoes it by accident.
Amdahl’s insight
A simple idea from computer science says that the benefit of speeding up one part is limited by how much of the total that part represents. If tests are ten percent of the pipeline, making them twice as fast saves five percent. If dependency downloads are forty percent, halving them saves twenty. This is why the first step is always to find the biggest piece.
Attack the biggest piece on the critical path first. The best first fixes are usually boring: caching, shallow checkouts, running independent work in parallel and removing work nobody needs.
7The Anatomy of a Pipeline’s Duration
Where the minutes usually hide.
flowchart TD
T["Total wall-clock time"] --> W["Waiting: queue and agent start-up"]
T --> S["Setup: checkout and dependencies"]
T --> B["Build: compile and package"]
T --> X["Test: unit, integration, end to end"]
T --> P["Publish: artifacts, reports, notifications"]
T --> O["Overhead: step start-up, logging, pipeline logic"]
| Bucket | Typical share in an untuned pipeline | Common fixes |
|---|---|---|
| Waiting | 10 to 40 percent at busy times | More or better-labeled agents, warm pools, cancel redundant builds |
| Setup | 15 to 40 percent | Shallow checkouts, package proxies, caches, small images |
| Build | 10 to 25 percent | Incremental builds, build caches, tool parallelism |
| Test | 20 to 50 percent | Split, order, remove sleeps, share fixtures |
| Publish | 3 to 10 percent | Upload only what is needed, use repositories |
| Overhead | 2 to 10 percent | Fewer steps, lighter scripts, quieter logs |
These shares are only typical ranges, not rules. Your own pipeline will differ, which is exactly why measuring matters. But the table shows a useful pattern: setup and waiting together often outweigh the actual building and testing. Many teams focus on the compiler and the test framework when the bigger wins are in the boring parts around them.
Illustration: the surprising breakdown
A thirty-eight-minute pipeline turns out to have this breakdown: 9 minutes waiting in the queue, 11 minutes of checkout and downloads, 4 minutes of compiling, 12 minutes of tests and 2 minutes of publishing. The team’s plan to buy a faster compiler would have addressed four minutes. Fixing the queue and the downloads addresses twenty.
8Queue Time: Waiting for a Machine
The delay before your code is even looked at.
When all the executors on suitable agents are busy, new builds wait in the build queue. Queue time is often the most annoying delay because it is pure idleness: nothing is happening for your change. It is also the delay that no amount of code tuning can fix.
Causes of long queues
- Too little capacity for the number of builds at busy times.
- Labels that split the fleet into small pools, so one pool is full while another sits idle.
- Builds that hold executors while waiting, for example an approval prompt inside an agent block.
- Redundant builds for commits that have already been superseded by newer ones.
- Slow builds that hold slots longer, which lengthens the queue for everyone.
| Symptom | Likely cause | First fix |
|---|---|---|
| Queue long at the same hours daily | Peak not covered by capacity | Scheduled warm capacity or a higher agent ceiling |
| Builds wait but agents look idle | Label mismatch or throttle rules | Read the queue reason; simplify labels |
| Queue grows steadily over months | Builds have become slower | Tune the pipelines; measure duration trends |
| Many queued builds for the same branch | Redundant commits piling up | Cancel older builds when a newer one arrives |
Aim for most builds to start within a minute of being queued. If the median queue time is much longer than that, fix the queue before anything else, because every other improvement is wasted on builds that spend their lives waiting.
Illustration: cancelling the outdated
A developer pushes five small commits in ten minutes. Without any rule, five builds are queued and run, and four of them test code that no longer matters. Cancelling older builds of the same branch when a newer commit arrives leaves one build, saves four builds’ worth of capacity and gives the developer the useful result sooner.
9Keep the Controller’s Work Light
The pipeline script runs on the controller, not on the agent.
This surprises many beginners. When a pipeline runs, the steps that execute commands run on the agent. But the pipeline script itself, the small program that decides what to do next, runs on the controller. Jenkins runs it with a special engine that can pause and resume, so that a pipeline can survive a restart. This engine is slower than ordinary code, and it uses controller memory and processor time.
flowchart LR
subgraph CT["Controller"]
SC["Pipeline script logic"]
LG["Log receiving and storage"]
ST["State saving to disk"]
end
subgraph AG["Agent"]
CM["Commands: compile, test, package"]
end
SC -->|"sends step"| CM
CM -->|"output lines"| LG
SC --> ST
Cheap for the controller
- Choosing which stage to run.
- Reading a few variables.
- Calling a command that does the real work.
- Short loops over a handful of items.
Expensive for the controller
- Looping over thousands of items in the script.
- Parsing very large data files in the script.
- Complex string building in big loops.
- Hundreds of tiny steps in a row.
- Very many parallel branches at once.
The rule
Let agents do the heavy work through ordinary commands and small programs, and keep the pipeline script thin: a coordinator, not a calculator. If a script needs to process a large list or file, write that processing as a normal tool that runs in a command on the agent, and let the pipeline read the short result.
Illustration: the report parser
A pipeline parses a test report with twenty thousand entries inside its own script, summarizing them in a loop. On a quiet system, it takes two minutes and hardly anyone notices. On a busy morning with fifty such pipelines, the controller’s processor is saturated and the whole system slows. Moving the summary into a one-line command on the agent takes three seconds and puts no load on the controller.
10Durability Settings: Trading Safety for Speed
How often a pipeline saves its progress to disk.
To survive a controller restart, a pipeline saves its progress to disk as it goes. Frequent saving is safe but costs disk activity, and on a busy controller with many pipelines the disk can become the bottleneck. Jenkins offers a durability setting that lets you choose how careful to be.
| Setting | How it saves | Speed | If the controller crashes |
|---|---|---|---|
| Maximum survivability (default) | Saves after every step, atomically | Slowest | Running pipelines resume |
| Survivable but not atomic | Saves regularly but with less strict guarantees | Faster | Usually resume; small risk of trouble |
| Performance optimized | Saves only when the controller shuts down cleanly | Fastest | Running pipelines cannot resume and must be rerun |
Who should use which
- Performance-optimized suits most ordinary build and test pipelines, which can simply be rerun if the controller crashes. Many teams see a noticeable reduction in disk load and faster pipelines.
- Maximum survivability suits long-running pipelines that would be expensive to restart, such as those with long approvals or lengthy deployments.
The setting can be chosen per pipeline in its options, or as a default for the whole installation. A good approach is a fast default, with the safe setting for the few pipelines that need it.
With the fastest setting, a controller crash loses the progress of all running pipelines. That is acceptable when controller crashes are rare and rerunning is cheap. It is not acceptable for a pipeline that waits three days for approval. Choose deliberately.
11Step Overhead: Fewer, Bigger Steps
Every step has a fixed cost, however small its work.
Each pipeline step involves some bookkeeping: the controller records it, sends it to the agent, waits for it, saves state and records the result. For a big step, this cost is invisible. For a thousand tiny steps, it adds up to minutes. A pipeline that runs many small commands one by one is slower than one that runs the same commands as a single script.
| Pattern | Effect | Better |
|---|---|---|
| Many one-line shell steps in a row | Fixed overhead paid each time | Group related commands into one script step |
| Loop in the pipeline that calls a shell step per item | Overhead multiplied by the number of items | Do the loop inside one shell script on the agent |
| Reading a small file many times through steps | Round trips between controller and agent | Read once and keep the value |
| Echo statements for every line of progress | Each one is a step and a log line | Print summaries, not every item |
The balance
This is not a call for one giant, unreadable script. Steps also give you visibility: when a stage shows steps with names and durations, you can see where time goes. The balance is to group steps that belong together, and keep separate steps where separate timing or separate failure handling helps you.
If a loop in your pipeline runs more than a couple of dozen times, suspect that it belongs inside a script on the agent. Loops in the pipeline are for coordinating; loops on the agent are for working.
Illustration: a hundred tiny steps
A pipeline validates 200 configuration files by running one shell step for each. With a fraction of a second of overhead per step plus start-up of the validator each time, the stage takes seven minutes. Rewriting it as one step that validates all files in a single run takes forty seconds.
12Faster Checkouts
Do not download the whole history to build one snapshot.
The first thing a build does is fetch the source code. By default, Git downloads the full history of the repository: every commit, every branch, every tag, going back years. For a large, old repository, this can be gigabytes, and on a fresh agent it happens on every build. Yet most builds need only the latest snapshot.
flowchart TD
A["Build needs the source code"] --> B{"Which checkout style?"}
B -->|"Full clone"| C["Download all history, branches and tags"]
B -->|"Shallow clone"| D["Download only recent commits"]
B -->|"Reference repository"| E["Reuse a local copy, fetch only new commits"]
B -->|"Sparse checkout"| F["Download only needed folders"]
C --> S1["Slowest, most complete"]
D --> S2["Fast for most builds"]
E --> S3["Very fast on warm agents"]
F --> S4["Fast for monorepos"]
Shallow clone
Fetch only the last few commits. Often cuts clone time from minutes to seconds. Some tools need more history, for example to compute version numbers or changed files, so choose a depth that covers them.
Skip tags and extra branches
Download only the branch being built, and skip tags unless needed. Less data to transfer and process.
Reference repository
Keep a shared local copy on the agent or a shared volume, so a checkout only fetches what is new.
Sparse checkout
In very large repositories, check out only the folders this build needs.
Check out once per build
Multi-stage pipelines with an agent per stage may check out again in every stage. Skip the default checkout, fetch once, and pass the files onward.
Handle large files carefully
Repositories using large-file storage can be slow to fetch. Fetch large files only when the build truly needs them.
A shallow clone can break steps that inspect history: computing a version from tags, finding the merge base for a pull request or listing changed files against another branch. If such a step fails after you shorten the history, increase the depth or fetch just what that step needs.
Illustration: the eight-minute clone
A ten-year-old repository takes eight minutes to clone in full on a fresh agent. Switching to a shallow clone of the last fifty commits, without tags, brings it down to thirty seconds. With 400 builds a day, that returns about fifty hours of agent time daily.
13Dependency Caching
The same libraries, downloaded again and again.
Most projects depend on many outside libraries. On a fresh agent, the build tool downloads every one of them before compiling. A project with hundreds of libraries can spend more time downloading than building. Since the libraries rarely change between builds, this is wasted effort. Caching removes it.
flowchart LR
B["Build needs a library"] --> L{"In local cache on the agent?"}
L -->|"Yes"| U["Use it: instant"]
L -->|"No"| P{"In package proxy near the agent?"}
P -->|"Yes"| PD["Download from proxy: fast"]
P -->|"No"| N["Download from the internet: slow"]
N --> PS["Proxy stores a copy"]
PS --> LS["Agent stores a copy"]
PD --> LS
| Layer | Where it lives | Strength | Weakness |
|---|---|---|---|
| Local cache on the agent | The agent’s disk | Fastest possible | Lost when the agent is ephemeral |
| Persistent cache volume | A storage volume attached to agents | Ephemeral agents start warm | Needs care against corruption and growth |
| Package proxy | A server inside your network | Shared by all agents; protects against outside outages | Another system to run |
| Container image registry mirror | A nearby registry | Much faster image pulls | Storage and upkeep |
Keys and freshness
A cache is only safe if it is keyed by exactly what it contains. If dependencies are recorded in a file with exact versions, use a fingerprint of that file as the cache key. When the file changes, the key changes, and a fresh cache is built. Reusing a cache built for different versions causes confusing bugs.
The package proxy is often the single best investment for teams using ephemeral agents. It needs no change to any pipeline beyond pointing the build tool at it, and it helps every project at once.
Illustration: the proxy effect
A build downloads 600 megabytes of libraries from the internet at about 10 megabytes a second, which takes a full minute. From a proxy on the local network at 200 megabytes a second, it takes three seconds. Multiplied by hundreds of builds a day, the difference is many hours of waiting removed.
14Build Tool Tuning
Letting the compiler and packager do less, and do it in parallel.
The build tool, whether Maven, Gradle, npm, a C++ compiler or another, does the actual compilation and packaging. Each has its own options for speed, and many teams never turn them on. Jenkins cannot make a slow tool fast, but it can give the tool a fair chance.
Rebuild only what changed
Modern tools skip unchanged modules. This only works if the previous build’s outputs are available, which needs a warm workspace or a shared build cache.
Shared build cache
Some tools can store compiled results on a shared server keyed by their inputs. Another agent building the same code fetches the result instead of compiling.
Parallel module builds
Many tools can build independent modules at once, using several processor cores. Turn it on, and check that the agent has the cores.
Long-lived build processes
Some tools keep a helper process running to avoid start-up costs. This helps on warm agents, but gives no benefit on throw-away containers.
Skip what a stage does not need
Do not build documentation, run code formatting or create packages in a stage that only runs tests.
Right-sized memory
Too little memory makes tools slow through constant cleanup or swapping. Give builds enough, and watch memory graphs on the agent.
Match the tool to the agent
A build tool that runs eight parallel workers on an agent with two cores will not go faster; it will fight itself. Set parallelism to the number of cores available to the build, which in a container may be limited by its resource settings. Check the resource limits, not just the machine size.
Incremental builds and caches make pipelines faster but can hide problems that a clean build would reveal. Keep pull request builds fast with caches, and run a fully clean build on a schedule, such as nightly, to prove that the caches did not mask anything.
15Speeding Up Tests
Tests are usually the biggest block of real work.
Once the setup is lean, tests are often the largest share of a pipeline’s time. Speeding them up is a mix of running them smarter, running them side by side and removing waste inside them.
flowchart TD
S["Whole test suite: 40 minutes"] --> SP["Split by past duration into balanced groups"]
SP --> G1["Group 1 on agent A: about 10 minutes"]
SP --> G2["Group 2 on agent B: about 10 minutes"]
SP --> G3["Group 3 on agent C: about 10 minutes"]
SP --> G4["Group 4 on agent D: about 10 minutes"]
G1 --> J["Join results into one report"]
G2 --> J
G3 --> J
G4 --> J
| Technique | How it helps | Watch out for |
|---|---|---|
| Split into balanced groups | Runs parts side by side; a plugin can compute groups from past test durations | Repeated setup per group; uneven groups waste time |
| Run fast tests first | Quick failures come early | Ordering must be maintained as tests change |
| Remove fixed sleeps | Tests wait for a condition, not a guessed time | Needs care to avoid flakiness |
| Share expensive fixtures | Start a database once per group, not once per test | Tests must not depend on each other’s data |
| Use lighter test doubles | Replace slow outside services with fast fakes in most tests | Keep a few real end-to-end checks |
| Run only affected tests | Skip tests unrelated to the change | Requires accurate dependency knowledge |
| Move slow tests off the pull request | Keeps feedback quick; run heavy suites after merge or nightly | Some bugs are found later |
The test pyramid
Many fast unit tests at the bottom, fewer integration tests in the middle and a handful of slow end-to-end tests at the top give the best speed for the confidence they provide. A pipeline with an inverted pyramid, mostly slow end-to-end tests, will always be slow and flaky.
Illustration: the hundred sleeps
A suite contains a hundred tests that each wait five seconds “to be safe” for a background job. That is over eight minutes of pure waiting. Replacing the fixed wait with a check that continues as soon as the job is done shortens each test to under a second, and the stage drops from fourteen minutes to six.
16Stage Order: Fail Fast
If something is going to fail, let it fail early and cheaply.
The order of stages affects how long a failing build wastes. If a formatting mistake is caught after forty minutes of tests, forty minutes were spent for nothing. If the cheap check runs first, the mistake is reported in thirty seconds.
flowchart LR
A["Format and lint: 30 seconds"] --> B["Compile: 2 minutes"]
B --> C["Unit tests: 5 minutes"]
C --> D["Integration tests: 12 minutes"]
D --> E["Security scan: 6 minutes"]
E --> F["Package and publish: 3 minutes"]
A -.-> X1["Fails here: wasted 30 seconds"]
C -.-> X2["Fails here: wasted 7.5 minutes"]
D -.-> X3["Fails here: wasted 19.5 minutes"]
How to order
- Put cheap checks first: formatting, linting, compilation and quick unit tests.
- Put likely-to-fail checks early if they are cheap, since they save the most time when they trigger.
- Put expensive checks late, once the cheap ones have passed.
- Run independent checks in parallel, for example static analysis and unit tests, so neither waits for the other.
Stopping parallel work early
When several branches run in parallel, the fail-fast option stops the remaining ones as soon as one fails, freeing their agents. Without it, the other branches run to the end, and the developer waits for results that no longer change the verdict.
Failing fast can hide other problems until the first is fixed, which means more rounds. For most teams the time saved on failing builds far outweighs this. For checks where developers want the full list of problems at once, such as linting, run them together in parallel instead.
17Skipping Work Nobody Needs
The fastest step is the one that does not run.
Many builds do work that cannot affect the result. A change to a documentation file does not need the full test suite. A change in one service of a large repository does not need every other service rebuilt. Declarative pipelines have conditions, written in a when directive, that decide whether a stage runs, and one of them looks at which files changed.
flowchart TD
C["Change arrives"] --> Q{"Which files changed?"}
Q -->|"Only documentation"| D["Skip build and tests; run link check"]
Q -->|"Only service A"| A["Build and test service A only"]
Q -->|"Shared library"| ALL["Build and test everything that depends on it"]
Q -->|"Pipeline definition"| FULL["Run the full pipeline"]
| Technique | Saves | Risk |
|---|---|---|
| Run a stage only if certain paths changed | Whole stages for unrelated changes | A wrong path list can skip a needed check |
| Branch-based conditions | Deploy and slow stages on ordinary branches | Conditions must be explicit and safe by default |
| Skip builds for tag-only or documentation commits | Whole builds | Rules must be reviewed regularly |
| Reuse results of unchanged modules | Rebuilding untouched parts | Needs a dependable build cache |
| Cancel outdated builds | Builds for superseded commits | Careful with builds that must always finish |
Skipping is a risk trade. Always run the complete pipeline on the main branch and on releases, so that anything the shortcuts missed is caught before customers see it. Shortcuts belong on pull requests, where quick feedback matters most.
Illustration: the monorepo
A repository holds twelve services. Every change used to rebuild and test all of them, taking forty minutes. By running each service’s stages only when its own folder or a shared dependency changed, the typical change now takes eight minutes, and only shared changes trigger the full forty.
18Parallelism: How Much Is Enough?
Doing things side by side helps, until it does not.
The parallel step runs several branches at the same time. Used well, it shortens the critical path. Used carelessly, it wastes resources, overloads the controller and even slows the pipeline down.
Find independent work
Only steps that do not depend on each other can run in parallel. Look for stages that read the same input but produce separate outputs.
Check the setup cost
If each branch needs its own agent, checkout and dependency download, the extra setup may outweigh the gain for short tasks.
Balance the branches
The stage lasts as long as the slowest branch. Aim for branches of similar length.
Check the capacity
Four branches need four executors. On a busy fleet, they may wait in the queue and finish no sooner than one after another.
Limit the count
Hundreds of parallel branches in one pipeline burden the controller. Group them into a manageable number.
| Approach | Good when | Bad when |
|---|---|---|
| Parallel branches on separate agents | Tasks are long and independent | Tasks are short; setup dominates |
| Parallel work on one agent | The agent has spare cores and the tasks are light | Tasks compete for the same processor or memory |
| Tool-level parallelism | The build tool can use many cores | Limits are set higher than the cores available |
| Very many small branches | Never, in most cases | Controller overhead grows, results are hard to read |
Illustration: too much of a good thing
A team splits their tests into sixty parallel branches. Each takes forty seconds of setup for ten seconds of tests, and the controller struggles to track sixty branches. Regrouping into six branches of ten groups each cuts the total from eleven minutes to four, and the controller load drops sharply.
19Agents, Workspaces and Reuse Within a Pipeline
Every time a stage asks for a new agent, something is set up again.
A pipeline can request an agent at the top, for the whole run, or separately for each stage. Each choice has costs and benefits. Requesting a new agent for every stage frees machines between stages, but every request may wait in the queue, and every new workspace needs a fresh checkout and dependencies.
| Design | Advantage | Cost |
|---|---|---|
| One agent for the whole pipeline | One checkout, warm workspace, no repeated queue waits | Holds the machine even during idle stages |
| An agent per stage | Machine freed between stages; each stage may use a different environment | Repeated queue waits, checkouts and file transfers |
| Agent per stage with reuse of the same machine | Different tools per stage without new queue waits | Slightly more complex definition |
| No agent while waiting | Executors freed during approvals and long external waits | Files must be stashed or archived before |
Container agents and the reuse option
When each stage runs in its own container image, such as one image for building and another for testing, a naive setup asks for a new machine for each stage. Declarative pipelines offer a reuse option for container agents inside a stage: it runs the container on the same machine and in the same workspace that the pipeline already holds, avoiding a new allocation and a new checkout.
Image size and pulling
Starting a container requires its image to be present on the machine. A large image takes time to download the first time. Keep images small, share common base layers, keep popular images on each machine or in a nearby mirror, and avoid pulling fresh copies on every run unless you need to.
Keep the pipeline on one agent while the work is continuous, and release the agent only when a long wait begins, such as a manual approval. Do not release and re-acquire agents between short stages, because each round trip costs more than it saves.
20Moving Files: Stash, Archive and Artifacts
Files that travel through the controller slow everyone down.
When a pipeline moves from one agent to another, files must follow. Jenkins offers stash and unstash for short hops within one build, and archiving for keeping files with the build record. Both normally send data through the controller, which is fine for small files and painful for large ones.
| Method | Suits | Performance note |
|---|---|---|
| Stash and unstash | Small sets of files passed to the next stage | Goes through the controller; large stashes are slow |
| Archive artifacts | Reports and small packages worth keeping | Stored with the build; adding file fingerprints costs extra |
| External artifact repository | Large files, releases, images | Agents talk to it directly and bypass the controller |
| Object storage through an artifact manager plugin | Large archives that still appear in the Jenkins interface | Frees the controller’s disk and network |
Practical rules
- Stash only what the next stage needs, using precise include patterns, not the entire workspace.
- Send big files to a repository, and pass only a version or address between stages.
- Archive reports, not everything. Do not archive dependency folders or intermediate files.
- Publish test results selectively. Extremely large result files slow down the reporting step and the page that shows them.
Illustration: the whole-workspace stash
A pipeline stashes the entire workspace, 1.2 gigabytes including dependency folders, so that the test stage can unstash it on another agent. The stash and unstash take four minutes and load the controller. Stashing only the compiled outputs, 40 megabytes, takes six seconds.
21Log Volume and Reporting
Every line of output travels to the controller and is stored there.
Commands running on an agent send their output to the controller as they run, and the controller stores it. A build that prints hundreds of thousands of lines creates real load: network traffic, disk writes and slow-loading log pages that few people can read anyway.
Use quiet modes
Most tools have options that reduce output to warnings and errors. Turn them on for routine builds.
Avoid progress bars
Download progress bars print thousands of updates. Disable them in non-interactive builds.
Print summaries, not every item
Report counts and failures instead of listing thousands of passing items.
Write details to files
Send verbose output to a file that is archived only when the build fails, so the log stays readable.
Debug output off by default
Turn on detailed logging only when investigating, and never leave it on in routine pipelines.
Keep timestamps
Timestamps cost little and pay off in every investigation. Keep them on.
Reporting costs
Publishing test results, coverage and analysis reports is useful, but each report costs time to process, especially when files are large or numerous. Publish what people actually read, publish once at the end of a parallel group rather than per branch when possible, and watch the duration of the reporting steps themselves.
The more output you produce, the more chances a secret or sensitive value has to slip into the log. Lean logs are faster and safer.
Illustration: the chatty download
A dependency download prints a progress line for every one of 3,000 files, and 200 builds a day do this. That is 600,000 log lines daily from one step. Switching the tool to quiet mode cuts it to a few lines per build, shrinks log storage and makes real errors easy to find.
22Shared Libraries and Start-up Overhead
Reusable pipeline code has a cost at the start of every build.
A shared library is a reusable module of pipeline code that many Jenkinsfiles can call. It keeps pipelines short and consistent, which is excellent. But loading a library takes time. At the start of each build, Jenkins fetches the library from its source repository and prepares it, and a large library can add several seconds or more to every run. On a busy system with many pipelines, this also burdens the controller and the source server.
| Practice | Effect on speed |
|---|---|
| Pin to a fixed version | Allows caching, and avoids surprises from library changes |
| Enable caching of fetched versions on the controller | Avoids fetching the library from source control on every build |
| Keep the library small and focused | Less to load and compile per run |
| Avoid heavy logic in library functions | Heavy logic runs on the controller and slows every pipeline |
| Load libraries only where used | Skip loading libraries in pipelines that do not need them |
| Load libraries implicitly only when truly universal | Implicit loading adds cost to every job on the controller |
Pipeline start-up in general
Before the first stage begins, Jenkins parses the pipeline definition, loads libraries, fetches the Jenkinsfile and prepares the run. For very large Jenkinsfiles with many stages and thousands of lines, this can take noticeable time. Keep the Jenkinsfile lean by moving repeated logic into libraries or scripts, and split enormous pipelines into smaller ones that call each other.
If your pipelines take a long time between “started” and the first stage, suspect start-up overhead: library fetching, a huge Jenkinsfile or a slow source server. Timestamps at the very top of the log will show it.
23Concurrent Builds, Waiting and Polling
Three quiet ways to waste capacity.
Concurrent builds of the same branch
When commits arrive faster than builds finish, several builds of the same branch run or queue at once. Only the newest matters. Options exist to disable concurrent builds, or to abort the previous build when a new one starts, and a milestone step can make older builds stop when a newer one passes a checkpoint. These settings free capacity for useful work and give developers the freshest answer.
flowchart TD
C1["Commit 1 starts build"] --> R1["Running"]
C2["Commit 2 arrives"] --> Q{"Rule for older build?"}
Q -->|"Let it run"| W["Both run: old result is stale"]
Q -->|"Abort previous"| A["Old build stops; new build runs"]
A --> F["Fresh result sooner, capacity saved"]
Waiting on agents
A build waiting for an outside event, such as an approval or an external system, should not hold an executor. Place long waits outside any agent block, so the slot returns to the pool. Files needed afterwards should be stashed or stored elsewhere.
Polling efficiently
Steps that poll, checking again and again until something is ready, should back off. The waitUntil step increases the pause between checks, which reduces load. Wrap polling in a timeout, avoid very short fixed sleeps in loops and print only occasional progress lines.
| Waste | Cost | Fix |
|---|---|---|
| Builds for superseded commits | Capacity and stale results | Abort previous builds or use milestones |
| Approvals inside an agent block | An idle executor for hours | Wait outside any agent |
| Tight polling loops | Load on controller and target system | Back off between checks |
| Fixed long sleeps | Time always spent even when ready earlier | Poll for readiness with a deadline |
Illustration: the fixed sleep
A deployment step sleeps two minutes to “let the service start.” The service is usually ready in twenty seconds. Replacing the sleep with a poll that checks readiness every few seconds, with a five-minute deadline, saves an hour of pipeline time across thirty deployments a day and catches real failures sooner.
24Monitoring, Metrics and Regression Alerts
Keeping the gains you worked for.
Performance work fades unless someone watches it. A pipeline that took eight minutes after tuning drifts back to twenty over a year as tests are added and caches go stale. Track the numbers, and raise an alarm when they drift.
| Metric | Why it matters | Warning sign |
|---|---|---|
| Median and 95th percentile duration | Typical and worst-case experience | Slow upward drift; a widening gap between the two |
| Queue time | Separates waiting from working | Median above a minute |
| Duration per stage | Finds the stage responsible for drift | One stage growing week after week |
| Cache hit rate | Shows whether caches actually work | Falling rate after a change to dependencies or agents |
| Flaky test rate | Reruns waste time | Builds routinely rerun to get green |
| Executor utilization | Shows over- or under-capacity | Sustained near 100 percent or near zero |
| Controller memory and disk activity | Shows whether the controller is the bottleneck | Frequent long pauses; heavy disk waits |
| Cost per build | Ties speed to money | Sudden jumps |
Regression alerts
Set an alert when a stage’s typical duration rises more than, say, twenty percent above its recent average, or when the 95th percentile crosses a chosen line. Catching a slowdown within a day means it can be traced to a single recent change. Catching it after six months means an archaeological dig.
A performance budget
Many teams agree a budget for each stage: ten minutes for pull requests, thirty for the main branch. When a change would break the budget, the team pays for it by speeding something else up, moving the check elsewhere or explicitly raising the budget with a reason. The budget turns performance from wishful thinking into a shared rule.
Illustration: the drift alarm
A team’s alert notices that the median test stage rose from 11 to 14 minutes in one week. Looking at the changes from that week, they find a new set of end-to-end tests added to the pull request pipeline. They move those tests to the main branch, and the median returns to eleven minutes before anyone had time to grumble.
25Useful Design Patterns
Proven ways of arranging fast pipelines.
Pattern 1: Cheap checks first, in parallel
Run formatting, linting, compilation and unit tests at the start, side by side where independent. Failures surface within a couple of minutes.
Pattern 2: Fast pull request lane, full main lane
Give pull requests a lean pipeline of quick checks, and run the slow suites on the main branch after merge or on a schedule. Developers get quick feedback and the full safety net still exists.
Pattern 3: Shallow clone, package proxy, warm cache
Combine three simple setup improvements: a shallow checkout, a nearby package proxy and a persistent cache volume. Together they usually remove most of the setup time on ephemeral agents.
Pattern 4: Change-aware pipelines
Use path conditions so each build runs only the stages relevant to what changed, with a full run on the main branch and on releases.
Pattern 5: Balanced test splits
Divide slow test suites into groups balanced by recorded durations, run them in parallel and merge the reports into one verdict.
Pattern 6: Thin script, heavy tools
Keep the pipeline script a coordinator, and move data processing into commands on agents. The controller stays responsive.
Pattern 7: Abort the outdated
Cancel older builds of a branch when a newer commit arrives, so capacity goes to the results that matter.
Pattern 8: Budgets and drift alerts
Agree stage duration budgets and alert on drift, so performance is protected as the code grows.
26Anti-patterns to Avoid
Habits that look fine at first and hurt later.
What people do
They tune by intuition, changing several things at once without measuring.
Why it hurts
Nobody knows what helped, some changes make things worse, and effort goes to parts that were never slow.
Better approach
Measure many runs, change one thing, verify, and record the result.
What people do
They do heavy data processing inside the pipeline script.
Why it hurts
It runs slowly on the controller, uses its memory and processor, and slows every other pipeline.
Better approach
Run processing as a command on the agent and read the short result.
What people do
They clone the full history and re-download every dependency on every build.
Why it hurts
Setup dominates the run time on fresh agents, and the same bytes cross the network hundreds of times a day.
Better approach
Use shallow checkouts, package proxies and caches keyed by dependency versions.
What people do
They stash the whole workspace or archive everything.
Why it hurts
Large transfers through the controller slow builds and fill its disk.
Better approach
Stash only what the next stage needs, and use artifact repositories for large files.
What people do
They split work into dozens of tiny parallel branches.
Why it hurts
Setup repeats in each branch, the controller tracks too much, and the queue may make the branches run one after another anyway.
Better approach
Use a small number of balanced branches sized to the available capacity.
What people do
They use long fixed sleeps and tight polling loops.
Why it hurts
Time is wasted when the thing is ready early, and load is created when checks are too frequent.
Better approach
Poll with backoff and a deadline, and continue the moment readiness is confirmed.
What people do
They skip checks aggressively everywhere to gain speed.
Why it hurts
Bugs reach the main branch and production, and the time saved is repaid many times over in incidents.
Better approach
Skip work on pull requests where safe, and run the complete pipeline on main and releases.
What people do
They fix performance once and never look at it again.
Why it hurts
The pipeline drifts back to its old slowness, and the effort is lost.
Better approach
Track durations, set budgets and alert on drift.
27Best Practices and Common Mistakes
A checklist to apply to any Jenkinsfile today.
Do this
- Turn on timestamps and read the timeline.
- Measure queue time and run time separately.
- Find the critical path and attack the biggest piece.
- Use shallow checkouts and check out once per build.
- Put a package proxy and caches in front of downloads.
- Order stages so cheap checks fail first.
- Run independent work in parallel with balanced groups.
- Skip unneeded work on pull requests, run everything on main.
- Keep the pipeline script thin and logs lean.
- Track durations and alert on drift.
Avoid this
- Tuning without measuring.
- Optimizing steps that are not on the critical path.
- Heavy work in the pipeline script on the controller.
- Hundreds of tiny steps in a row.
- Stashing or archiving entire workspaces.
- Dozens of tiny parallel branches.
- Fixed sleeps and unbounded polling.
- Verbose logs and progress bars in routine builds.
- Skipping checks on the main branch.
Mistakes people make when they are new
Blaming the code when the queue is the problem. Check whether builds wait before they run.
Judging by one run. Builds vary. Use at least twenty runs and look at the middle and the slow tail.
Forgetting that shallow means less history. Steps that read history may fail after the change.
Trusting stale caches. Key caches by exact dependency versions and run a clean build on a schedule.
Over-parallelizing. More branches do not always mean faster, especially with repeated setup and a busy fleet.
Ignoring the controller. A slow controller slows every pipeline, no matter how well each one is written.
28Advantages, Disadvantages and Trade-offs
An honest look at tuning, its rewards and its costs.
Rewards of tuning
- Faster feedback and happier developers.
- Smaller changes, since waiting is short.
- Lower cloud bills and better use of machines.
- Shorter queues for everyone.
- A calmer, more responsive controller.
- Better understanding of how the pipeline works.
Costs and risks
- Engineering time spent that could build features.
- Caches and shortcuts can hide real problems.
- More moving parts, such as proxies and shared volumes.
- Parallel and skipped work makes pipelines harder to reason about.
- Fast settings, such as relaxed durability, reduce safety.
- Gains fade without monitoring.
Trade-offs to weigh
| Choice | Benefit | Cost |
|---|---|---|
| Shallow clone | Much faster checkout | Steps that need history may fail |
| Persistent caches | Warm starts on fresh agents | Risk of stale or corrupted caches |
| Performance-optimized durability | Less disk load, faster pipelines | Running pipelines lost on a controller crash |
| More parallel branches | Shorter critical path | More setup and controller load |
| Skipping unrelated stages | Faster pull requests | A wrong rule can miss a needed check |
| Agent per stage | Flexible environments, freed machines | Repeated queue waits and setup |
| Quiet logs | Faster and safer | Less detail when debugging |
| Strict budgets | Speed is protected | Occasional friction when adding checks |
There is no universally right answer. The right settings depend on how often builds run, how expensive your machines are, how much risk you accept and how much your developers value quick answers. Choose on purpose, measure the result and write down why.
29Real-World and Industry Examples
How the ideas look in the kinds of organizations that run Jenkins at scale.
An online retailer
Retail teams with hundreds of builds a day treat pipeline duration as a product metric. They publish dashboards of median and slow-tail durations per service, set budgets for pull requests and investigate whenever a stage drifts.
A company with a large monorepo
Organizations that keep many services in one repository rely on change-aware pipelines, sparse checkouts and shared build caches so that a small change does not rebuild the world.
A mobile studio
Mobile builds are heavy and machines are costly. Studios cache dependencies aggressively, split device tests across many agents, and run the slowest suites only on release branches.
A bank
Regulated teams cannot skip checks on release branches, so they tune elsewhere: fast lanes for pull requests, proxies for dependencies, quiet logs and balanced test splits, keeping the full gate quick enough to run often.
An open-source project
Public projects with volunteer capacity make every minute count. They use shallow clones, cache dependencies, cancel outdated pull request builds and keep the default pipeline small.
A small startup
A team of eight adds timestamps, a shallow checkout and a package proxy in one afternoon. Their pipeline drops from twenty-six minutes to twelve without touching test code.
30A Complete Story: From Forty-five Minutes to Eleven
Follow one imaginary team through a tuning project.
A team’s pull request pipeline takes about forty-five minutes. Developers complain, and the queue grows at busy hours. Instead of guessing, they start by measuring.
Measure
They switch on timestamps and collect the last thirty runs. The median is 45 minutes: 8 waiting in the queue, 12 in checkout and downloads, 5 compiling, 17 testing and 3 publishing.
Biggest cost on the critical path
Setup, at twelve minutes, is nearly as large as compiling and publishing combined, and tests are the largest block. Queue time is next.
Fix setup
They switch to a shallow clone without tags and add a package proxy. Setup falls from twelve minutes to about ninety seconds.
Fix the queue
They cancel outdated builds when a new commit arrives and keep some warm agents for the morning peak. Median queue time drops from eight minutes to under one.
Split the tests
They divide the 17-minute suite into four balanced groups on parallel agents. The stage falls to about six minutes, including setup.
Order the stages
They move linting and unit tests to the front and run static analysis in parallel with compilation. Failing builds now report in under three minutes.
Trim the rest
They stash only compiled output, switch tools to quiet mode and set performance-optimized durability for pull request pipelines.
Guard
They set a ten-minute budget for pull requests, plot the median per stage and alert on twenty percent drift. Six months later the median is still about eleven minutes.
The team never rewrote a test or bought a faster machine. They measured, found the boring causes, fixed them one at a time and kept watching. The result was four times faster feedback and lower cloud costs.
31Troubleshooting Guide
Symptoms, likely causes and calm fixes.
| Symptom | Likely cause | What to do |
|---|---|---|
| Long gap before the first stage | Library fetching, large Jenkinsfile or slow source server | Check timestamps at the top; cache libraries and slim the file |
| Long gap after start on a fresh agent | Checkout and dependency downloads | Shallow clone, package proxy, persistent cache |
| Build durations look fine but developers wait | Queue time not counted in the duration | Measure queue time separately |
| Parallel stage no faster than serial | Branches waiting in the queue or sharing one small agent | Check capacity; reduce branch count; balance groups |
| Controller sluggish under load | Heavy script logic, many steps or disk pressure | Slim the scripts; consider durability setting; check disk and memory |
| Builds slower after adding caches | Cache download or restore costs more than it saves | Measure hit rate; cache only large, stable items |
| Version step fails after shallow clone | Step needs tags or history | Fetch tags or increase depth for that step |
| Stash or unstash takes minutes | Stashing too much through the controller | Narrow includes; use an artifact repository |
| Log page slow to load | Enormous log output | Quiet modes; write details to files |
| Pipeline slowly gets slower over months | Drift as tests and dependencies grow | Track per-stage durations; set budgets and alerts |
A calm debugging routine
- Separate queue time from run time for the last twenty or more builds.
- Read the timestamps of a typical slow run and mark the biggest gaps.
- Check the critical path, and ignore anything not on it.
- Check the agent’s graphs to see whether the step is limited by processor, memory, disk or network.
- Change one thing at a time, measure again and record the result.
32Plain-English Glossary
Every important word in this guide in one place.
| Word | Simple meaning |
|---|---|
| Jenkins | A free automation server that builds, tests and delivers software. |
| Pipeline | A written recipe of the steps Jenkins follows. |
| Jenkinsfile | The text file holding the pipeline. |
| Stage | A named phase of the pipeline. |
| Step | One single action inside a stage. |
| Controller | The main Jenkins server that runs the pipeline logic. |
| Agent | A worker machine or container that runs the commands. |
| Executor | A slot on an agent that runs one build at a time. |
| Wall-clock time | The time on the clock the developer actually waits. |
| Queue time | Time a build waits for a free machine. |
| Critical path | The longest chain of dependent steps that decides total time. |
| Latency | How long one build takes. |
| Throughput | How many builds finish per hour. |
| Cache | A saved result reused to avoid repeating work. |
| Shallow clone | Fetching only recent history of a repository. |
| Package proxy | A local server that stores downloaded libraries for reuse. |
| Durability setting | How often a pipeline saves its progress to disk. |
| Fail fast | Stopping early when something fails. |
| Stash | Temporary storage of files passed between stages. |
| Percentile | The value below which a given share of measurements fall. |
| Performance budget | An agreed maximum duration for a stage or pipeline. |
33Frequently Asked Questions
Short answers to the questions that come up most often.
34Summary and Key Takeaways
The whole story in a few lines.
Pipeline performance tuning is the disciplined removal of waste. Start by measuring: timestamps, many runs, and queue time separated from run time. Find the critical path and attack its biggest piece. The usual culprits are ordinary ones: queue waits, full clones, repeated downloads, tests run in a poor order or one after another, oversized file transfers, chatty logs and heavy scripting on the controller. Fix them one at a time, verify each change, and keep watching with budgets and drift alerts so the gains last.
Key Takeaways
- Measure before you change. Use timestamps and at least twenty runs, and separate queue time from run time.
- Shorten the critical path. Only the longest chain of dependent steps matters, so attack the biggest piece first.
- Fix setup first. Shallow checkouts, package proxies and keyed caches often remove the largest hidden delays.
- Order and split the work. Run cheap checks first, run independent work in parallel with a small number of balanced branches, and skip unneeded work on pull requests.
- Protect the controller. Keep pipeline scripts thin, use fewer and bigger steps, choose the durability setting deliberately and keep logs and file transfers lean.
- Do not waste capacity. Abort outdated builds, never hold an executor during long waits, and poll with backoff and a deadline.
- Keep the gains. Track medians and slow tails per stage, set duration budgets and alert on drift.