Test balancing API

  • Tier: Ultimate
  • Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
  • Status: Experiment

The availability of this feature is controlled by a feature flag. For more information, see the history. This feature is available for testing, but not ready for production use.

Use this API to distribute tests across the nodes of a parallel: CI/CD job based on test durations, so that every node finishes at roughly the same time.

A test split is a unit of work to distribute across nodes. When you split by file, each test split is a test file. Nodes seed a shared pending pool with their static test split, then repeatedly request duration-budgeted batches of test splits as they finish. Faster nodes absorb more work. When a node is retried, it receives the exact set of test splits it was originally assigned.

Both endpoints:

  • Must be called from the running parallel job, authenticated with a CI/CD job token (CI_JOB_TOKEN).
  • Return 422 Unprocessable Entity when the calling job does not use the parallel: keyword.
  • Return 422 Unprocessable Entity for pipelines created more than 30 days ago, because test balancing data, including retries, is retained for 30 days from pipeline creation.

Initialize test balancing for a parallel job

Seeds the job group’s shared pending pool with the caller’s static test split and claims a first batch of test splits. If the node already has claimed test splits (for example, when a job is retried or recovered from a crash), the previously claimed test set is returned unchanged and the test_splits parameter is ignored.

This endpoint returns 422 Unprocessable Entity when the seed would push the job group’s shared pool past 50,000 test splits.

POST /job/test_balancing/initialize

Supported attributes:

AttributeTypeRequiredDescription
test_splitsarray of hashesYesThe test splits of the static split assigned to this node. Maximum 1,000 entries.
test_splits[].pathstringYesThe path of the test split, relative to the repository root. Maximum 1024 characters.
test_splits[].expected_durationfloatNoThe expected duration of the test split, in seconds. Defaults to 300 when not given.

If successful, returns 201 Created and the following response attributes:

AttributeTypeDescription
modestringseed when the pool was seeded and a first batch claimed, or retry when a previously claimed test set was replayed.
test_splitsarrayThe test splits the node should run. On retry, the full original test set.
test_splits[].pathstringThe path of the test split, relative to the repository root.
test_splits[].expected_durationfloatThe expected duration at seed time, in seconds. Defaults to 300 when not given.

Example request:

curl --request POST \
  --header "JOB-TOKEN: $CI_JOB_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"test_splits": [{"path": "spec/models/user_spec.rb", "expected_duration": 12.5}]}' \
  --url "https://gitlab.example.com/api/v4/job/test_balancing/initialize"

Example response:

{
  "mode": "seed",
  "test_splits": [
    {
      "path": "spec/models/user_spec.rb",
      "expected_duration": 12.5
    }
  ]
}

Request the next batch of tests

Atomically claims a duration-budgeted batch of pending test splits for the calling node, slowest first. An empty test_splits array means the queue is drained and the node should stop requesting batches.

The server chooses the batch size automatically based on the total remaining expected duration in the shared pool. When a large amount of duration remains, the server returns larger batches to reduce the number of requests. As the pool drains, the server returns smaller batches so that work stays balanced across nodes.

POST /job/test_balancing/request

This endpoint does not take any attributes.

If successful, returns 201 Created and the following response attributes:

AttributeTypeDescription
test_splitsarrayThe test splits the node should run. Empty when the queue is drained.
test_splits[].pathstringThe path of the test split, relative to the repository root.
test_splits[].expected_durationfloatThe expected duration at seed time, in seconds. Defaults to 300 when not given.

Example request:

curl --request POST \
  --header "JOB-TOKEN: $CI_JOB_TOKEN" \
  --url "https://gitlab.example.com/api/v4/job/test_balancing/request"

Example response:

{
  "test_splits": [
    {
      "path": "spec/features/login_spec.rb",
      "expected_duration": 210.4
    }
  ]
}