Skip to main content

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.
build:
  stage: build
  script:
    - echo "Building..."
  
test:
  stage: test
  needs: ["build"]
  script:
    - echo "Testing..."
dependencies Specifies which artifacts to download from previous jobs. If not specified, all artifacts are downloaded.
deploy:
  stage: deploy
  needs: ["build"]
  dependencies:
    - build
stage Groups jobs into stages. Jobs in the same stage run in parallel.
build:
  stage: build
  script:
    - echo "Building..."

test:
  stage: test
  script:
    - echo "Testing..."

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 parallel to 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.key with branch and commit to ensure cache consistency
  • Limit cache.paths to only necessary directories
  • Use fallback_keys for 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: true in jobs that are critical to pipeline success.
  • Apply fail-fast to stages where early failure prevents downstream waste.
  • Combine with allow_failure for 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.

Key Metrics for Pipeline Duration Monitoring

  • Total Pipeline Time - End-to-end duration of the pipeline execution.
  • Job Duration - Individual job completion times for performance insights.
  • Stage Duration - Time spent in each stage to optimize parallelism.

Was this article helpful?