Why Your CI/CD Pipeline is Probably Too Slow (And How to Fix It)
Is your team waiting too long for builds? Discover proven strategies to optimize your CI/CD pipeline and reduce build times by up to 70%.
Is your team waiting minutes (or hours) for builds to complete? Here's how to dramatically speed up your CI/CD pipeline without sacrificing quality.
The Common Problems
# Typical slow CI pipeline
name: Build and Test
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Build
run: npm run build
This simple pipeline can take 5-10 minutes. Let's fix that.
Quick Wins
1. Cache Dependencies
# GitHub Actions example
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
2. Parallel Test Execution
jobs:
test:
strategy:
matrix:
chunk: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- run: |
npx jest --shard=${{ matrix.chunk }}/4
Advanced Optimizations
1. Docker Layer Caching
# Bad: Everything changes with code
FROM node:alpine
COPY . .
RUN npm install
RUN npm run build
# Better: Layer optimization
FROM node:alpine
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
2. Selective Testing
jobs:
determine_changes:
runs-on: ubuntu-latest
outputs:
backend_changed: ${{ steps.filter.outputs.backend }}
frontend_changed: ${{ steps.filter.outputs.frontend }}
steps:
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
backend_tests:
needs: determine_changes
if: ${{ needs.determine_changes.outputs.backend_changed == 'true' }}
runs-on: ubuntu-latest
steps:
- run: npm test
Real World Examples
Before Optimization
Total Pipeline Time: 15 minutes
- Checkout: 30s
- Install deps: 3m
- Lint: 2m
- Test: 5m
- Build: 3m
- Deploy: 1.5m
After Optimization
Total Pipeline Time: 4 minutes
- Checkout: 30s
- Install deps: 30s (cached)
- Lint: 1m (parallel)
- Test: 1.5m (parallel)
- Build: 30s (cached)
- Deploy: 1m
Implementation Strategy
1. Caching Configuration
# .github/workflows/build.yml
jobs:
build:
steps:
# NPM cache
- uses: actions/cache@v3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
# Build cache
- uses: actions/cache@v3
with:
path: .next
key: next-${{ github.sha }}
2. Test Splitting
// jest.config.js
module.exports = {
maxWorkers: process.env.CI ? 2 : '50%',
testSequencer: './testSequencer.js'
};
// testSequencer.js
class CustomSequencer {
sort(tests) {
const copyTests = Array.from(tests);
const chunkSize = Math.ceil(copyTests.length / 4);
const chunk = process.env.JEST_CHUNK || 1;
return copyTests.slice(
(chunk - 1) * chunkSize,
chunk * chunkSize
);
}
}
Monitoring Build Times
1. Build Time Dashboard
// build-metrics.js
const metrics = {
recordBuildTime: (job, duration) => {
console.log(`${job}: ${duration}ms`);
// Send to monitoring system
}
};
// Usage in CI
console.time('build');
// ... build steps
console.timeEnd('build');
2. Trend Analysis
SELECT
date_trunc('day', timestamp) as day,
avg(duration) as avg_duration,
count(*) as build_count
FROM ci_builds
GROUP BY day
ORDER BY day DESC
LIMIT 30;
Best Practices
1. Smart Test Running
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Get changed files
id: changed
run: |
echo "::set-output name=files::$(git diff --name-only HEAD^ HEAD)"
- name: Run affected tests
if: contains(steps.changed.outputs.files, 'src/')
run: npm test -- --findRelatedTests ${{ steps.changed.outputs.files }}
2. Dependency Management
{
"scripts": {
"preinstall": "node ./scripts/check-lock-file.js",
"postinstall": "node ./scripts/clean-deps.js"
}
}
Common Pitfalls to Avoid
- Running all tests always
- Not using build caches
- Sequential job execution
- Unnecessary installations
- Large Docker images
Results
Our clients have seen:
- 70% reduction in build times
- 50% less CI runner costs
- Improved developer productivity
- Faster deployments
- Better test reliability
Conclusion
Fast CI/CD pipelines are possible with:
- Smart caching
- Parallel execution
- Selective testing
- Build optimization
- Regular monitoring
Remember: Every minute saved in CI is multiplied by your team size and daily builds.