Repository navigation
Size CI test jobs on RunsOn runners to the parallelism they can use - #75833
Merged
Merged
Conversation
Jobs that run tests with --run-in-parallel start one serial pytest per test type, so they never use more CPUs than they have test types. On RunsOn, splitting core test types into two jobs only doubles the jobs: each half still lasts as long as its slowest test type, and the out-of-memory risk the split guards against on public runners does not apply to the larger RunsOn runners. The lowest-dependencies provider jobs left their 32 CPUs partly idle, and the integration-core and system tests ran a single pytest on 32-CPU runners. Generated-by: Claude Opus 5
potiuk
requested review from
amoghrajesh,
bugraoz93,
choo121600,
ephraimbuddy,
jason810496,
jedcunningham,
jscheffl and
vatsrahul1001
as code owners
October 28, 2026 01:19
Grouping all providers but the long-running ones into one test type left the provider jobs waiting on that group's single serial pytest while the other CPUs idled. The Non-DB jobs run every provider in one xdist pytest, where splitting test types only adds entries, so they get the providers unsplit. Generated-by: Claude Opus 5
…runs Each test type of a parallel test run is a docker compose project with its own network. Docker's default address pools fit about 30 networks, so provider jobs running a test type per CPU on 32-CPU and larger runners failed with "all predefined address pools have been fully subnetted". Generated-by: Claude Opus 5
…unsOn Once every provider runs as its own test type, a job lasts as long as its slowest provider, and amazon, google and standard each take far longer than the rest as a single serial pytest. Splitting them lets the large RunsOn runners work on their parts at once. Lowest-dependencies jobs are sized by test-type count, so a change touching a few providers keeps one job. Generated-by: Claude Opus 5
Contributor
|
The re-run of the jobs stopped by EC2 Spot interruptions finished: attempt 2 passed. |
Docker Compose project names and GitHub artifact names reject the colon that names a part of a split provider. Generated-by: Claude Opus 5
A split provider part selects a folder inside the provider's tests, and pytest runs an explicitly selected path even when its provider folder is ignored, so a skipped or excluded provider's parts would still run. Generated-by: Claude Opus 5
…and memory use Several jobs ran a single process, or a minute or two of work, on 32-CPU or 8-CPU runners, and fourteen Helm unit test jobs each spent more time preparing the image than running their tests. Charting each job's CPU and memory use lets the size classes be set from measurements rather than from how the jobs are expected to behave. Generated-by: Claude Opus 5
The check for an empty run treated the first remaining argument, or any path-like argument, as a test selection, so option values such as the "8" of "-n 8" or a path passed to --rootdir decided whether tests ran. It now compares the ignored folders with the paths the test type itself selects, and passes paths the user names explicitly to pytest as they are. Generated-by: Claude Opus 5
Under xdist every test type runs in one pytest process, where the --ignore arguments of a provider's other part would silently be dropped or would remove its sibling parts, so the wrong tests would run. Generated-by: Claude Opus 5
Test suites such as All-Postgres add pytest options with values, which were taken for selected test paths, so a run whose every test folder was ignored still started pytest with nothing selected. Generated-by: Claude Opus 5
Each worker kept the whole list of test types, so with --skip-db-tests every worker selected the folders of all of them. Generated-by: Claude Opus 5
A few dozen MySQL servers running at once exhaust the default fs.aio-max-nr, and InnoDB then fails with "io_setup() failed with EAGAIN". Generated-by: Claude Opus 5
Each test type of a compatibility test installs an Airflow version and the providers before running, so listing every provider as its own test type multiplied those installs and the jobs ran out of time. Generated-by: Claude Opus 5
shahar1
approved these changes
Oct 28, 2026
Contributor
Backport successfully created: v3-4-testNote: As of Merging PRs targeted for Airflow 3.X In matter of doubt please ask in #release-management Slack channel.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Most test jobs on the self-hosted RunsOn runners can't use the CPUs they get. Jobs that run with
--run-in-parallel(DB, lowest-dependencies and special tests) start one serial pytest per test type, so a job never uses more CPUs than it has test types. Logs from canary run 40517104168 show this:The core test types are split into two matrix jobs to keep the Non-DB xdist collection inside the 16 GB of a public runner. On RunsOn that job gets 128 GB. The DB halves each still last as long as their slowest test type, so on RunsOn the split only doubles the job count. It also ran the single system test once per slice.
Changes, for RunsOn only (public runners are unchanged):
8+16/32+64size class, the same as the provider integration tests.providers-xdist-test-types-list-as-strings-in-jsonoutput that never splits the providers, since xdist runs them all in one pytest anyway.Providers[amazon:hooks], withProviders[amazon:other]running the rest of the provider, so a provider job does not wait on one long serial pytest. Lowest-dependencies jobs on RunsOn are sized at about 40 test types each: 3 jobs for all providers, one for a change touching a few./24subnets before the tests start. Each parallel test type creates its own compose network, and the default pools fit only about 30, fewer than the large runners run at once.8+16or4+8size classes, and on RunsOn the Helm unit tests run all their packages in one job per Kubernetes version instead of one job per package.metrics: cpu,memory), so the size classes can be set from measurements.fs.aio-max-nr, which a few dozen parallel MySQL servers exhaust.providers-split-test-types-list-as-strings-in-jsonoutput.The selective-checks doc covers the new sizing and slicing rules, and a test covers the slice counts on both runner types.
Checks run locally: the selective-checks test suite (passes), the fast static checks (pass, except the docker and network hooks the sandbox blocks), and a roborev review (no findings). This PR has the
canaryandfull tests neededlabels so the full matrix runs on RunsOn and the new job durations can be compared with the canary run above.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Opus 5 following the guidelines
🤖 Generated with Claude Code