[DO-NOT-MERGE] [incubator-kie-6854] Parallel CI Jobs - #6860
Conversation
c25cc5b to
1752375
Compare
|
(deleted) |
|
Confirmed sequential CI (original ci.yaml) and parallel CI have the same test coverage. |
|
(deleted) |
afa5fc2 to
344885e
Compare
Split PR CI into 4 parallel test jobsProblemThe monorepo PR build ran all ~850 modules sequentially in a single job, taking ~2-3h. SolutionSplit the PR test step into 4 concurrent jobs by project category: drools, optaplanner, kogito-runtimes, kogito-apps. Wall-clock time drops to ~1h15m (limited by the longest job, kogito-apps). Key changes
Golden files -- 48 new per-category expected files added across 12 test scenarios. Existing golden files updated for new modules on main. Cross-category dependency handlingEach test job installs all upstream categories' affected modules (without tests) before running its own:
Build time comparisonSequential CI (run
Parallel CI (run
|
| echo "3c9fb9ac5823ae5ab9d136dec08b896e2a0f6f6b313689b21f43c511b7c34b85 jbang.zip" | sha256sum -c - | ||
| unzip -q jbang.zip | ||
| echo "${{ github.workspace }}/jbang-0.138.0/bin" >> $GITHUB_PATH | ||
| run: mvn --batch-mode --no-transfer-progress -fae -Dsurefire.redirectTestOutputToFile=true -Dfull -Dreproducible install |
There was a problem hiding this comment.
On push, we still use the non-parallel full build.
| matrix: | ||
| os: [ubuntu-latest] | ||
| java: [17, 21] |
There was a problem hiding this comment.
In each parallel job, I don't use exclude, but exact ubuntu-latest and java 17 21 for conciseness. This should be enhanced when we accept mac and windows.
| pl=$(paste -sd, "$MAVEN_PL_UPSTREAM_FILE") | ||
| [ -z "$pl" ] && echo "No upstream modules. Skipping." && exit 0 | ||
| mvn -T 1C --batch-mode --no-transfer-progress -fae -DskipTests -DskipITs -Denforcer.skip=true -Dcheckstyle.skip=true -Dformatter.skip=true -Darchunit.skip=true -Dsurefire.redirectTestOutputToFile=true -pl "$pl" install | ||
| mvn --batch-mode --no-transfer-progress -T 1C -fae -DskipTests -DskipITs -Dquarkus.build.skip=true -Denforcer.skip=true -Dcheckstyle.skip=true -Dformatter.skip=true -Darchunit.skip=true -Dsurefire.redirectTestOutputToFile=true -pl "$pl" install |
There was a problem hiding this comment.
I added -Dquarkus.build.skip=true to solve the quarkus build ordering issue with -T 1C.
| [ -z "$pl" ] && echo "No affected modules. Skipping." && exit 0 | ||
| pl=$(paste -sd, "$MAVEN_PL_AFFECTED_DROOLS_FILE") | ||
| [ -z "$pl" ] && echo "No affected drools modules. Skipping." && exit 0 | ||
| mvn --batch-mode --no-transfer-progress -fae -Dsurefire.redirectTestOutputToFile=true -Dfull -Dreproducible -pl "$pl" install |
There was a problem hiding this comment.
I don't use -T 2 for the test phase because it will raise many issues, and they will likely require a long time to fix (See #6864).
| <!-- DO NOT remove the BEGIN/END comments. They are used by CiComputeBuildScopes for parallel CI job split | ||
| When you add a new module, put it in one of the 4 categories (drools, optaplanner, kogito-runtimes, kogito-apps) --> | ||
| <!-- BEGIN drools modules (auto) --> |
There was a problem hiding this comment.
CiComputeBuildScopes depends on these BEGIN END markers for 4 categories split. It's a little fragile, but when broken, CiComputeBuildScopes makes sure to exit(1) with an error, so we will be able to detect.
There was a problem hiding this comment.
I see you're putting some guardrails in place, but I think we can do better. The "partitions" files listing leaf modules which bring a larger dependency hierarchy with them will give us a lot more flexibility, and will allow us to not couple the pom.xml with a partitioning solution for PR checks and CI.
|
@tiagobento @yesamer @Rikkola @athirakm94 @kbowers-ibm Hi, this PR splits the PR check CI into four parallel jobs to reduce the overall CI time. Please have a look at the above summary. The Build time comparison is the "big impact" case where almost all modules are tested (e.g., root pom.xml change, drools-core change, etc.). But other use cases like only kogito-runtimes change would also benefit from the parallel CI. The CI logic ( Please review, thanks! |
|
Looks like this is also making the scripts a bit easier to read, but is there an easy way to run this locally? First to test this, but also to run my own nighties on my WIP topic branches. |
I haven't tried, but this seems to be for the purpose > https://github.com/nektos/act Btw, sometimes I use my fork repo to run GHA jobs (e.g., tkobayas#17) So it doesn't consume my local PC resource :) |
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR updates the CI scope computation so pull requests can run tests in parallel per module category (drools/optaplanner/kogito-runtimes/kogito-apps), and updates the workflow plus golden tests to match.
Changes:
- Partition “affected” modules by category in
CiComputeBuildScopesand emit category-specific affected lists. - Split PR CI into four parallel test jobs (category-based) while keeping a full build on push events.
- Update/extend compute-build-scopes golden scenarios and test harness to validate category partitioning.
Reviewed changes
Copilot reviewed 48 out of 61 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| script/ci/tests/scenarios-compute-build-scopes/12-quarkus-extension-pairing/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 12 |
| script/ci/tests/scenarios-compute-build-scopes/12-quarkus-extension-pairing/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 12 |
| script/ci/tests/scenarios-compute-build-scopes/12-quarkus-extension-pairing/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 12 |
| script/ci/tests/scenarios-compute-build-scopes/11-fully-removed-subtree/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 11 |
| script/ci/tests/scenarios-compute-build-scopes/11-fully-removed-subtree/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 11 |
| script/ci/tests/scenarios-compute-build-scopes/11-fully-removed-subtree/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 11 |
| script/ci/tests/scenarios-compute-build-scopes/11-fully-removed-subtree/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 11 |
| script/ci/tests/scenarios-compute-build-scopes/10-plugin-dep-as-dependency/expected-upstream.txt | Update golden upstream ordering for scenario 10 |
| script/ci/tests/scenarios-compute-build-scopes/10-plugin-dep-as-dependency/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 10 |
| script/ci/tests/scenarios-compute-build-scopes/09-nested-pom-under-src/expected-upstream.txt | Update golden upstream ordering for scenario 09 |
| script/ci/tests/scenarios-compute-build-scopes/09-nested-pom-under-src/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 09 |
| script/ci/tests/scenarios-compute-build-scopes/08-combined-mixed-changes/expected-affected.txt | Adjust golden affected ordering for scenario 08 |
| script/ci/tests/scenarios-compute-build-scopes/08-combined-mixed-changes/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 08 |
| script/ci/tests/scenarios-compute-build-scopes/08-combined-mixed-changes/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 08 |
| script/ci/tests/scenarios-compute-build-scopes/08-combined-mixed-changes/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 08 |
| script/ci/tests/scenarios-compute-build-scopes/08-combined-mixed-changes/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 08 |
| script/ci/tests/scenarios-compute-build-scopes/07-removed-module/expected-affected.txt | Adjust golden affected ordering for scenario 07 |
| script/ci/tests/scenarios-compute-build-scopes/07-removed-module/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 07 |
| script/ci/tests/scenarios-compute-build-scopes/07-removed-module/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 07 |
| script/ci/tests/scenarios-compute-build-scopes/07-removed-module/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 07 |
| script/ci/tests/scenarios-compute-build-scopes/07-removed-module/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 07 |
| script/ci/tests/scenarios-compute-build-scopes/06-aggregator-pom/expected-affected.txt | Adjust golden affected ordering for scenario 06 |
| script/ci/tests/scenarios-compute-build-scopes/06-aggregator-pom/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 06 |
| script/ci/tests/scenarios-compute-build-scopes/06-aggregator-pom/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 06 |
| script/ci/tests/scenarios-compute-build-scopes/06-aggregator-pom/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 06 |
| script/ci/tests/scenarios-compute-build-scopes/06-aggregator-pom/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 06 |
| script/ci/tests/scenarios-compute-build-scopes/05-upstream-downstream-pair/expected-affected.txt | Adjust golden affected ordering for scenario 05 |
| script/ci/tests/scenarios-compute-build-scopes/05-upstream-downstream-pair/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 05 |
| script/ci/tests/scenarios-compute-build-scopes/05-upstream-downstream-pair/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 05 |
| script/ci/tests/scenarios-compute-build-scopes/05-upstream-downstream-pair/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 05 |
| script/ci/tests/scenarios-compute-build-scopes/05-upstream-downstream-pair/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 05 |
| script/ci/tests/scenarios-compute-build-scopes/04-two-independent-leaves/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 04 |
| script/ci/tests/scenarios-compute-build-scopes/03-root-pom/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 03 |
| script/ci/tests/scenarios-compute-build-scopes/03-root-pom/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 03 |
| script/ci/tests/scenarios-compute-build-scopes/03-root-pom/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 03 |
| script/ci/tests/scenarios-compute-build-scopes/03-root-pom/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 03 |
| script/ci/tests/scenarios-compute-build-scopes/02-hub-module-source/expected-affected.txt | Adjust golden affected ordering for scenario 02 |
| script/ci/tests/scenarios-compute-build-scopes/02-hub-module-source/expected-affected-optaplanner.txt | Add golden output for optaplanner affected modules in scenario 02 |
| script/ci/tests/scenarios-compute-build-scopes/02-hub-module-source/expected-affected-kogito-runtimes.txt | Add golden output for kogito-runtimes affected modules in scenario 02 |
| script/ci/tests/scenarios-compute-build-scopes/02-hub-module-source/expected-affected-kogito-apps.txt | Add golden output for kogito-apps affected modules in scenario 02 |
| script/ci/tests/scenarios-compute-build-scopes/02-hub-module-source/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 02 |
| script/ci/tests/scenarios-compute-build-scopes/01-leaf-module-source/expected-affected-drools.txt | Add golden output for drools affected modules in scenario 01 |
| script/ci/tests/CiComputeBuildScopesTest.java | Teach scenario runner / golden updater to validate category-specific affected outputs |
| script/ci/CiComputeBuildScopes.java | Parse module category markers from root POM and emit per-category affected outputs |
| pom.xml | Add BEGIN/END markers around drools modules (auto) to support category partitioning |
| .rat-excludes | Simplify RAT exclusions for CI test scenarios via directory glob |
| .github/workflows/ci.yaml | Split PR checks into four parallel category jobs; keep full build on push; reuse ci-setup composite action |
| .github/actions/ci-setup/action.yaml | Add reusable composite action to standardize CI setup and build-scope computation |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private static String categorizeGa(String ga, Map<String, Path> gaToDir, | ||
| Map<String, Set<Path>> categories) { | ||
| Path dir = gaToDir.get(ga); | ||
| if (dir == null) return "drools"; | ||
| dir = dir.toAbsolutePath().normalize(); | ||
| for (var entry : categories.entrySet()) { | ||
| for (Path catPath : entry.getValue()) { | ||
| if (dir.equals(catPath) || dir.startsWith(catPath + "/")) { | ||
| return entry.getKey(); | ||
| } | ||
| } | ||
| } | ||
| return "drools"; | ||
| } |
| currentCategory = beginMatcher.group(1); | ||
| if (!categories.containsKey(currentCategory)) { | ||
| System.err.println("ERROR: unknown module category '" + currentCategory | ||
| + "' in pom.xml marker. Expected one of: " + EXPECTED_CATEGORIES); |
| for (String cat : categories.keySet()) { | ||
| Set<String> catAffected = affected.stream() | ||
| .filter(ga -> categorizeGa(ga, gaToDir, categories).equals(cat)) | ||
| .collect(Collectors.toCollection(LinkedHashSet::new)); | ||
| writeLines(partitionedPath(affectedOut, cat), catAffected); | ||
| } |
| for (String cat : categories.keySet()) { | ||
| long catCount = affected.stream() | ||
| .filter(ga -> categorizeGa(ga, gaToDir, categories).equals(cat)) | ||
| .count(); | ||
| sb.append(" affected-").append(cat).append("=").append(catCount); | ||
| } |
| <!-- DO NOT remove the BEGIN/END comments. They are used by CiComputeBuildScopes for parallel CI job split | ||
| When you add a new module, put it in one of the 4 categories (drools, optaplanner, kogito-runtimes, kogito-apps) --> |
tiagobento
left a comment
There was a problem hiding this comment.
Hi @tkobayas. First of all I would like to thank you immensely for the effort and time you're putting into making our infrastructure better. It's great to see such great initiatives coming together recently.
I left a few comments which I believe will steer the direction of this partitioning mechanism you're introducing here for the better. And I'd also like to bring a thinking point for the total time spent on GitHub Actions that such parallelization introduces.
One additional, general topic:
Total GitHub Actions resource usage
Recently, we had our own PR checks and CI jobs taking a lot of time to start. We reached out to Apache Infra and were told some projects sometimes end up using too much of the GitHub Actions quota. Since we share that with all of Apache, we need to be mindful of our growth too. Sequential builds are slower from start to finish, but they take on less resources too, as there's less repeated effort compared to Parallel builds.
Probably something for us to keep in mind is to have an easy-enough mechanism to revert PR checks to sequential builds if we ever reach a GitHub Actions usage scale that will be called out by Apache Infra.
Lastly, but not least important: maybe we should try to combine partitioning and a more aggressive -T configuration? Taking advantage of multi-core builds will probably help even more, now that we're aiming to have less scope to build on each partition.
Thanks @tkobayas!
| Pattern beginPattern = Pattern.compile("<!--\\s*BEGIN\\s+(\\S+)\\s+modules\\s+\\(auto\\)\\s*-->"); | ||
| Pattern endPattern = Pattern.compile("<!--\\s*END\\s+(\\S+)\\s+modules\\s+\\(auto\\)\\s*-->"); | ||
| Pattern modulePattern = Pattern.compile("<module>(.+)</module>"); |
There was a problem hiding this comment.
I'm a concerned with this parsing strategy. As it relies on comments being maintained in the top-level pom.xml file. This simply mimics the old structure we had with the repos, and doesn't to separate things logically per domain. I don't think we should carry forward the old, artificial grouping we had.
Can we try to make this partitioned by domain? We can have even more partitions, like Kogito Data-Index, Kogito Jobs Service, for example.
In kie-tools, when we needed to do a similar partitioning effort in order to keep PR checks build times in check, what I did was I created partitioning files which listed the leaf modules which represented good vertical partitions of the dependency DAG.
We don't have this anymore since the 10.3.x+ stream effort began, but you can see how it used to look like in 10.2.
https://github.com/apache/incubator-kie-tools/tree/10.2.0/.github/supporting-files/ci/partitions
Note that there are 2 partition files only, with an implicit 3rd partition which contained all the modules that were not listed explicitly in neither of those 2 partition files.
| <!-- DO NOT remove the BEGIN/END comments. They are used by CiComputeBuildScopes for parallel CI job split | ||
| When you add a new module, put it in one of the 4 categories (drools, optaplanner, kogito-runtimes, kogito-apps) --> | ||
| <!-- BEGIN drools modules (auto) --> |
There was a problem hiding this comment.
I see you're putting some guardrails in place, but I think we can do better. The "partitions" files listing leaf modules which bring a larger dependency hierarchy with them will give us a lot more flexibility, and will allow us to not couple the pom.xml with a partitioning solution for PR checks and CI.
| touch "${{ runner.temp }}/maven-pl-affected-drools.txt" | ||
| touch "${{ runner.temp }}/maven-pl-affected-optaplanner.txt" | ||
| touch "${{ runner.temp }}/maven-pl-affected-kogito-runtimes.txt" | ||
| touch "${{ runner.temp }}/maven-pl-affected-kogito-apps.txt" |
There was a problem hiding this comment.
Declaring 4 fixes partitions I believe will limit the flexibility of the whole mechanism, and further constrain us into fitting new modules into these 4 boxes. Can we try to make partitions more fluid? Adding/removing a new partition should be easy enough. We can use file names to name them, and list the leaf modules which belong in each partition inside the files. E.g.,
drools.txt
optaplanner.txt
kogito-runtimes.txt
kogito-data-index.txt
kogito-jobs-service.txt
and so on.
| test-kogito-runtimes: | ||
| if: github.event.action != 'closed' && github.event_name == 'pull_request' | ||
| name: 'kogito-runtimes :: ${{ matrix.os }}, Java ${{ matrix.java }}' | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-latest] | ||
| java: [17, 21] | ||
| steps: | ||
| - name: "SETUP :: Checkout to load local actions" | ||
| uses: actions/checkout@v7 | ||
|
|
||
| - uses: ./.github/actions/ci-setup | ||
| with: | ||
| ref: ${{ github.base_ref }} | ||
| java-version: ${{ matrix.java }} | ||
|
|
||
| - name: "PR CHECK :: BUILD :: Upstream + drools + optaplanner modules (tests skipped)" | ||
| shell: bash | ||
| run: | | ||
| [ ! -s "$MAVEN_PL_AFFECTED_KOGITO_RUNTIMES_FILE" ] && echo "No affected kogito-runtimes modules. Skipping upstream build." && exit 0 | ||
| upstream=$(paste -sd, "$MAVEN_PL_UPSTREAM_FILE") | ||
| drools=$(paste -sd, "$MAVEN_PL_AFFECTED_DROOLS_FILE") | ||
| optaplanner=$(paste -sd, "$MAVEN_PL_AFFECTED_OPTAPLANNER_FILE") | ||
| pl=$(echo "$upstream,$drools,$optaplanner" | sed 's/^,//;s/,,*/,/g;s/,$//') | ||
| [ -z "$pl" ] && echo "No modules to build. Skipping." && exit 0 | ||
| mvn --batch-mode --no-transfer-progress -T 1C -fae -DskipTests -DskipITs -Dquarkus.build.skip=true -Denforcer.skip=true -Dcheckstyle.skip=true -Dformatter.skip=true -Darchunit.skip=true -Dsurefire.redirectTestOutputToFile=true -pl "$pl" install |
There was a problem hiding this comment.
Commenting on this one, but it applies to each of the fixed 4 partitions. There's a lot of duplication here. If we make partitions dynamic (and thus more flexible), we remove the duplication and keep things simpler to change as we evolve our codebase. We really need a CI system that's not rigid for us to have room to grow / adapt.
|
@tiagobento Thank you for the feedback. I'll work on the suggestions. One thing:
I experimented with the |
|
@tkobayas 34% is a HUGE gain! That combined with the parallelization this PR brings will put our PR checks into another level of speed. |
cb5a28e to
44d0f54
Compare
The monorepo PR build ran all ~857 modules sequentially in a single job (~3h). This splits the test step into parallel jobs partitioned by project area, cutting wall-clock time to ~1h. Partition files (.github/supporting-files/ci/partitions/) list leaf modules per project area. CiComputeBuildScopes computes transitive closures and assigns each affected module exclusively: a module goes to a partition only if it is in exactly one partition's closure. Shared modules and everything else go to an implicit default partition. - 3 partition files: partition0 (optaplanner), partition1 (kogito-runtimes), partition2 (kogito-apps). Drools + shared core modules form the implicit default (no file needed). - ci-parallel.yaml: compute-partitions job outputs JSON partition array, test job uses dynamic matrix (partition × java × os cross-product). Activated by 'parallel ci' PR label. Per-partition upstream files make each job fully independent. - ci.yaml: sequential build, unchanged. Skips when 'parallel ci' label is attached. - ci-setup: new optional inputs partitions-dir and skip-build-scopes. Partition logic gated on CI_PARTITIONS_DIR env var. - Upstream builds use -T 1C and -Dquarkus.build.skip=true for speed. - Per-partition + default golden files for all 12 test scenarios. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parallel CI Test Run: Partition Files ApproachRun Module distribution
Job elapsed time
Wall-clock time = compute-partitions (2m 24s) + slowest test job (1h 20m 31s) = ~1h 23m Note:
|
Parallel CI Test Run: Parallel Upstream with Quarkus Application RebuildRun Job elapsed time
The partition2 upstream figures include both upstream phases:
The workflow wall-clock time, measured from the start of |
Parallel CI Test Run: Computed Image-Producer RebuildRun Job elapsed time
The partition2 upstream figures include both upstream phases:
The workflow wall-clock time, measured from the start of |
|
@tkobayas Very nice work!! It's curious that JDK21 is slower than JDK17 :o |
Uh oh!
There was an error while loading. Please reload this page.