Optimizing Pipeline Performance
1. Using DAG Pipelines
DAG (Directed Acyclic Graph) pipelines in GitLab CI/CD allow you to define dependencies between jobs and run them in parallel, optimizing pipeline execution time.
| Property | Description | Example |
|---|---|---|
| needs | Defines job dependencies. Jobs listed in needs must complete before the current job starts. |
|
| dependencies | Specifies which artifacts to download from previous jobs. If not specified, all artifacts are downloaded. | |
| stage | Groups jobs into stages. Jobs in the same stage run in parallel. | |
Example: DAG Pipeline Configuration
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building application..."
artifacts:
paths:
- build/
test_job:
stage: test
needs: ["build_job"]
script:
- echo "Running tests..."
artifacts:
paths:
- test-results/
deploy_job:
stage: deploy
needs: ["test_job"]
script:
- echo "Deploying to production..."
Note: DAG pipelines are supported in GitLab CI/CD starting from version 15.0.
2. Implementing Parallel Execution
Example: Basic Parallel Jobs
jobs:
build:
stage: build
script:
- echo "Building..."
test:
stage: test
script:
- echo "Testing..."
deploy:
stage: deploy
script:
- echo "Deploying..."
| Property | Description | Values |
|---|---|---|
| parallel | Number of parallel jobs to run | 2, 4 |
| max_concurrent | Maximum concurrent jobs allowed | 10 |
Note: Parallel jobs must be defined within the same stage to run concurrently.
Example: Parallel Test Jobs
test:
stage: test
parallel: 4
script:
- echo "Running test suite $CI_NODE_INDEX"
Best Practices for Parallel Execution
- Use
parallelto distribute load across multiple runners - Ensure jobs in the same stage are independent to avoid race conditions
- Monitor resource usage to prevent overloading runners
3. Optimizing Cache Usage
| Cache Key | Values | Description |
|---|---|---|
| cache.key | cache-{{ .Branch }}-{{ .Commit }} |
Defines the cache key pattern to use for caching artifacts |
| cache.paths | ["node_modules/", "vendor/"] |
List of paths to cache and restore |
| cache.uncompressed | false |
Whether to compress cache files (default: true) |
Example: Using cache key with branch and commit
cache:
key: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
paths:
- node_modules/
- .npm/
Note: Cache keys are case-sensitive and must be unique per pipeline.
Warning: Large cache sizes can slow down pipeline execution. Consider using
cache.paths to limit what is cached.
| Cache Strategy | Description | Best Use Case |
|---|---|---|
| cache.key | Uses a custom key to define cache scope | When you want fine-grained control over cache invalidation |
| cache.paths | Defines which files/directories to cache | For dependency or build artifact caching |
| cache.uncompressed | Controls compression of cached files | For faster access in environments with limited CPU |
Example: Cache with fallback key
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- node_modules/
fallback_keys:
- "${CI_COMMIT_REF_SLUG}-"
Cache Optimization Tips
- Use
cache.keywith branch and commit to ensure cache consistency - Limit
cache.pathsto only necessary directories - Use
fallback_keysfor better cache reuse across branches - Avoid caching large files that change frequently
4. Reducing Docker Image Size
| Strategy | Description | Benefits |
|---|---|---|
| Multi-stage builds | Use separate stages for building and running, copying only necessary artifacts. | Significantly reduces final image size by excluding build tools and dependencies. |
| Alpine Linux base | Use Alpine as the base image instead of full OS images like Ubuntu or CentOS. | Smaller footprint, faster downloads, less storage usage. |
| Layer caching | Structure Dockerfile to leverage layer caching effectively. | Reuses unchanged layers, minimizing redundant downloads and builds. |
| Minimize package installations | Only install required packages and remove unnecessary ones (e.g., apt-get clean). |
Reduces image size and potential security vulnerabilities. |
Example: Multi-stage Dockerfile
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
Note: Alpine-based images typically reduce image size by 50-70% compared to standard distributions.
5. Using Interruptible Jobs
| Property | Values | Description |
|---|---|---|
| interruptible | true |
Allows job to be canceled if a new pipeline starts for the same branch |
Example: Enabling Interruptible Jobs
job_name:
interruptible: true
script:
- echo "This job can be interrupted"
Note: Interruptible jobs are particularly useful in merge request pipelines where a new commit might invalidate previous job results.
6. Skipping Unnecessary Jobs
| Option | Values | Notes |
|---|---|---|
| only | changes |
Run job only when specific files change |
| except | changes |
Run job unless specific files change |
| rules | changes |
Conditional logic to run job based on file changes |
Example: Using changes to skip jobs
build:
script:
- echo "Building..."
only:
changes:
- src/**/*
- lib/**/*
test:
script:
- echo "Testing..."
only:
changes:
- src/**/*
- test/**/*
deploy:
script:
- echo "Deploying..."
only:
changes:
- deploy/**/*
Example: Using rules for complex skip logic
build:
script:
- echo "Building..."
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request"'
changes:
- src/**/*
when: on_success
- if: '$CI_PIPELINE_SOURCE == "push"'
changes:
- src/**/*
when: on_success
- when: never
Note: The
changes keyword is evaluated at job runtime, not at pipeline creation time.
Warning: Using
changes with only or except can lead to unexpected behavior if not properly configured.
7. Optimizing Artifact Size
| Option | Values | Notes |
|---|---|---|
| artifacts.path | src/ |
Directory to include in artifacts |
| artifacts.exclude | **/*.log |
Patterns to exclude from artifacts |
| artifacts.when | on_success |
When to upload artifacts (on_success, always, on_failure) |
Example: Reducing Artifact Size
build:
script:
- npm run build
artifacts:
paths:
- dist/
exclude:
- "**/*.log"
- "**/*.tmp"
Note: Artifacts are stored in GitLab and count towards project storage limits. Minimize artifact size to reduce storage costs.
Warning: Large artifacts can cause pipeline timeouts. Consider using
artifacts.expire_in to automatically clean up old artifacts.
| Configuration | Description | Default |
|---|---|---|
| artifacts.expire_in | Time after which artifacts expire | 30 days |
| artifacts.max_file_size | Maximum file size in MB | 100 MB |
8. Using Resource Groups
Example: Define a resource group in a job
job_name:
resource_group: "shared_database"
script:
- echo "Running job with shared database access"
| Property | Description | Default |
|---|---|---|
| resource_group | Name of the resource group to use for job execution | None |
Note: Resource groups are used to limit concurrent job execution within a group, ensuring that only one job can run at a time for a specific resource group.
Warning: If a job is part of a resource group, it will wait for other jobs in the same group to complete before starting.
9. Implementing Fail-Fast Strategies
Example: Using fail-fast in a job
job_name:
script:
- echo "Running test suite"
- npm test
fail-fast: true
| Option | Values | Description |
|---|---|---|
| fail-fast | true | false |
If set to true, the job will stop immediately if any step fails. Default is false. |
Note: Setting
fail-fast to true ensures that pipeline execution stops as soon as a job fails, preventing unnecessary resource consumption.
Example: Using fail-fast in a stage
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building application"
fail-fast: true
test_job:
stage: test
script:
- echo "Running tests"
fail-fast: true
Best Practices for Fail-Fast
- Use
fail-fast: truein jobs that are critical to pipeline success. - Apply
fail-fastto stages where early failure prevents downstream waste. - Combine with
allow_failurefor jobs that should not stop the pipeline but still benefit from fast failure.
10. Monitoring Pipeline Duration
| Metric | Description | Collection Method | Notes |
|---|---|---|---|
| pipeline_duration | Total time taken for a pipeline to complete from start to finish | GitLab API / UI | Includes all stages and jobs |
| job_duration | Time taken for an individual job to complete | GitLab API / UI | Can be used to identify bottlenecks |
| stage_duration | Time taken for a stage to complete | GitLab API / UI | Helps in optimizing stage-level parallelism |
Example: Fetching Pipeline Duration via API
curl --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/<project_id>/pipelines/<pipeline_id>"
Note: Pipeline duration includes time spent in queued state, which can be influenced by runner availability and resource constraints.