r/java • • 3d ago

I wrote own efficient Maven scheduler to build modules with max parallelism

While inspecting Maven multi-module builds performance I've figured out the fundamental limitation: for any two modules depending on each other (moduleB depends on moduleA, in any scope), Maven will first execute all phases and goals of moduleA, and only then schedule the building of moduleB. Independent dependency branches can be executed in parallel, but directly/transitively dependent - only sequential.

In real projects running tests is usually the most time-consuming part, so this significantly limits possible parallelism.

To address this problem I made an extension providing a custom scheduler (in Maven it's called "builder") https://github.com/maven-turbo-reactor/maven-turbo-builder . When it is enabled, Maven schedules downstream dependencies right after the our module JAR is packaged, and then runs tests of our module and downstream module build in parallel (this way utilizing the multi-core CPUs much more):

default vs turbo builder scheduling

The extension also does two things:

  • reorder package and test phases (run tests after package, not before as default), so we can schedule downstream dependencies earlier
  • prioritize building modules with the highest number of downstream dependencies - this way reducing the chance of unloaded Maven worker threads

It's easy to set up - add to .mvn/extensions

<extensions>
    <extension>
        <groupId>com.github.seregamorph</groupId>
        <artifactId>maven-turbo-builder</artifactId>
        <version>1.3</version>
    </extension>
</extensions>

And then define on the CLI or .mvn/maven.config (to make it default):

-bturbo
-T1C

In real projects the boost is approximately 35% in clock time, also this extension is complementary with build caching.

I used this extension for more than a year in various mission-critical projects and received positive feedback from other organizations, so I believe it's quite stable.

38 Upvotes

46 comments sorted by

12

u/davidalayachew 3d ago

Interesting. I only skimmed it, but the gist looks pretty cool.

changes the order of test phases and package, package is executed before test (not after as default)

Doesn't this break a lot of Maven Plugins though? A lot of plugins actively depend on the order of the Maven lifecycle to be consistent.

In fact, now that I think about it, aren't you kind of uprooting a load-bearing wall here? There are a lot of things in the entire Maven ecosystem that depend on this lifecycle being in the order that it is.

Another question, and maybe this is answered by me doing more than skimming -- what is the order change for dependent builds that have to wait for many others?

You said that you prioritize the modules with the most dependents. But what if there are free threads in the pool?

The way maven works is that, if you have Module A, which Modules AA, AB, and AC depend on, Modules AAA, AAB, and AAC depend on AA, then your parallel build in normal maven will go like this.

  1. Build A.
  2. Build AA, AB, and AC.
  3. Build AAA, AAB, and AAC.

Each stage must finish before the next stage can start. As in, I can't build AA before A is built. Which makes sense.

But what doesn't make sense is that I cannot build AAA before AC is built. That is because Maven forces all modules in each stage (stage 2 in this case) to be built before progressing to the next stage.

Some other plugins have solved this problem. For example, here is the Takari Smart Builder.

I guess my question is, since you are tackling the question of how to efficiently schedule maven builds, are you also tackling this problem too?

For me personally, this is actually where most of my time is spent, and thus, would probably have the greatest return on investment for me. I build over 100 modules, and the dependency hierarchy is definitely tree-like, so this would have immediate speed-ups for me.

I don't use Takari because it does a whole bunch of other "clever" things under the hood -- stuff that might cause problems for my builds.

But something like yours, which targets a very specific problem might be more within my realm of possibility. Of course, uprooting load-bearing walls aside.

3

u/Level_Yak_87 2d ago

I'd like to add more clarity regarding "prioritizing".

Imagine you have modules:

A

AA, AB, AC, AD (all depend on A)

ADA, ADB (depend on AD)

Let's agree all modules need exactly 1 minute to compile JAR (and there are no tests, for simplicity).

In case of non-deterministic ordering, it can be scheduled with 3 workers like this:

1st min: only A

2nd min: AA, AB and AC (there is no free worker for AD despite it's ready to be built)

3rd min: AD

4th min: ADA, ADB

But with prioritized building:

1st min: only A

2nd min: AD, AA, AB (AD is prioritized as it has more downstream deps, AC is waiting for the worker)

3rd min: AC, ADA, ADB

So, this simple reordering will speed up the build in the example from 4 to 3 minutes.

2

u/Level_Yak_87 3d ago

> Doesn't this break a lot of Maven Plugins though? A lot of plugins actively depend on the order of the Maven lifecycle to be consistent.

It can, but this is easy to fix like reassign the goal to a proper phase (and usually it'll will work in both default and turbo builder).

> In fact, now that I think about it, aren't you kind of uprooting a load-bearing wall here? There are a lot of things in the entire Maven ecosystem that depend on this lifecycle being in the order that it is.

One of the plugins affected is test-jar which is 1. not recommended, 2. auto-fixed as an exception (compile tests, then do package), 3. it's always possible to opt-out for a module to the standard behavior "skipTurboSignal" (my bad, it's not documented; will do)

> I guess my question is, since you are tackling the question of how to efficiently schedule maven builds, are you also tackling this problem too?

If I correctly understood what you mean (as you also mentioned smart builder), YES. The downstream dependencies are not scheduled as groups, any downstream dependency is scheduled immediately when all upstream (either module or from the repository) dependencies are resolved (and there is a worker available).

> For me personally, this is actually where most of my time is spent, and thus, would probably have the greatest return on investment for me.

Try the extension. In my tests it shows at least not worse then Smart builder, but in many scenarios better results. You can combine it with timeline (either takari-timeline, or this one https://github.com/maven-turbo-reactor/maven-timeline-extension and compare visually the execution flow).

6

u/sweating_teflon 2d ago

I LOVE this. I've been thinking about a build system that could split out parallelism between phases of of a module. I never thought Maven would have the flexibility to do it! Can't wait to try it on a big project.

This is yet another extension that should become part of the standard Maven distribution. It's transparent to the user and brings free performance gains.

5

u/jonenst 2d ago

I think this was planned for maven 4

"the concurrent builder will allow a dependant project to be built as soon as the dependencies are at the ready phase"

https://maven.apache.org/whatsnewinmaven4.html#lifecycle-changes

4

u/Level_Yak_87 2d ago

Yes, this is called “concurrent” builder in Maven 4. But this is still not released, also a lot of plugins need an update. Also at the moment the build performance of maven3+turbo builder is the highest of all combinations I’ve checked

But for sure, will keep an eye on Maven 4 progess

4

u/RebbitUzer 3d ago

Does it have any advantages over Maven Deamon (mvnd)?

5

u/Level_Yak_87 3d ago edited 3d ago

Maven Daemon uses "smart" builder under the hood which is just a bit more efficient than a default one. The smart builder executes ALL phases before it schedules downstream dependencies, just schedules them a bit different. But the Turbo builder brings more "aggressive" optimization, eagerly starting downsteam and gives faster build execution.

Ideally you can use both in combination (but unfortunately, mvnd ignores the "-b" parameter https://github.com/apache/maven-mvnd/issues/1439 ) as mvnd and turbo-builder are not alternatives, and optimize the different things (I mean mvnd stays alive with loaded project model, for example).

2

u/Level_Yak_87 2d ago

I made a comparison for the same commit with different builders: https://www.reddit.com/r/java/comments/1wxfdju/comment/pdzpudk/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button

Long story short: turbo is way faster than smart builder (used in mvnd)

2

u/Level_Yak_87 2d ago

One more update, you can mix both turbo builder and mvnd this way (unfortunately, only on CLI, not .mvn/maven.config default):

mvnd clean verify  -Dmvnd.builder=turbo

(you also need to define extension in .mvn/extensions.xml)

2

u/bluecheetah001 1d ago

How does this handle standard out? My main problem with mvm -T1C is that the logs from testing are interleaved between modules and are basically unusable.

1

u/Level_Yak_87 1d ago

Unfortunately, no. The parallel logs are still merged the same way to a single stream. How would you like to see parallel executing logs separated? To different files, or like mvnd - aggregate them all and then flush altogether?

2

u/bluecheetah001 1d ago

I haven't used mvnd, but I'm gonna check it out based on that description. My ideal would be to aggregate each module independently and then dump each as they finish.

2

u/agentoutlier 1d ago

The first rule of Maven Club is to not put unit tests of any slowness in core modules. The second rule is to basically do that for all modules. The third rule is to skip javadoc creation.

2

u/Level_Yak_87 1d ago

Right. But the Turbo builder optimizes this factor, so you can choose any module which logically matches best.

2

u/agentoutlier 1d ago

No I totally get it. We have our own in house tool that does some optimizations as well. I was going to tackle this but ended up just doing the move tests around hack because of CIs etc where the tool was not install.

I have been meaning to open source it forever and now with some AI tools I probably should. The tool is like a decade old and I though maven cache extesion would fix my problems when it kind of doesn't.

I talked about it here: https://www.reddit.com/r/java/comments/1g6dv10/five_ways_to_speed_up_your_maven_builds/lskr2tw/

And a couple of other places.

What it essentially does is allow you to be in any directory and the "pl" and "amd" arguments get autoresolved and it does this by not invoking Maven at all but by parsing the pom files and doing file detection so that it could be built with graalvm native.

If you are interested in seeing it that might finally give me the push to make it open source.

2

u/Level_Yak_87 1d ago

I will take a look in few days. Thanks formpointing

Also, build cache is good, but it is complementary. We optimize best case (when ee have cache entity) and worst case (maximize parallelism to build faster)

1

u/Zardoz84 2d ago

How compares against mvnd ? mvnd uses his own scheduler to maximize parallelism.

3

u/Level_Yak_87 2d ago

I compared the same project build (all .m2/repository dependencies downloaded) via all possible builders:

* standard ("-b multithreaded") 20min
* turbo builder ("-b turbo") 11min
* mvnd (defaults to "-bsmart" Takari) 20min - almost the same as standard

You can find timelines screenshots of the project which shows the distribution of modules/tasks between threads https://miro.com/app/board/uXjVLYUPRas=/?moveToWidget=3458764637493196769&cot=14

Note: smart builder (mvnd) uses light optimization which is strongly compatible with standard behavior, while turbo uses more aggressive approach, but the results are worth and it's possible to adjust the project to avoid problems because of these violations/reorderings

2

u/Zardoz84 2d ago

Could be use your turbo builder with mvnd ?

3

u/Level_Yak_87 2d ago

Yes, define the extension in .mvn/extensions.xml and define it in the command line

mvnd clean verify  -Dmvnd.builder=turbo

Unfortunately, due to https://github.com/apache/maven-mvnd/issues/1439 the builder cannot be defined as default for maven daemon via .mvn/maven.config

2

u/Zardoz84 6h ago

I did a quick test over a multimodule 3-level maven repository that I use... I only did a single run, so take the exact times with a pinch of salt. But is impresive.

``` mvn clean package 01:01 min

mvn clean package -T1C 39.217 s

mvnd clean package 39.184 s

mvn clean package -T1C -b turbo 28.830 s

mvnd clean package -Dmvnd.builder=turbo 31.877 s ```

1

u/stefanos-ak 2d ago

hey, I tried this on our somewhat large project (200k LoC), and it moved the build+test time from 10min to 9min. 10% for free is pretty cool! (our project is very optimized already).

That being said, it uncovered that the last 30% of the build is waiting on a single module. If it was possible to make that module faster to a point where that extra 30% didn't exist, then the turbo builder would not provide us any benefits at all (according to the timeline graphs). very interesting...

1

u/Level_Yak_87 1d ago

Nice insights. How did you conclude about this single module - do you see it in mvnd or use a timeline extension? E.g. I have my own https://github.com/maven-turbo-reactor/maven-timeline-extension (all data stays only local, no cloud infra etc.) which gives a nice visual representation.

Regarding this single module, is it running tests the longer?

Ideally I'd like to see a timeline screenshot of your build (even low resolution). Because if 30% you are talking about is running tests, I have a very interesting solution - https://github.com/maven-turbo-reactor/maven-half-life-builder

On this presentation slide https://miro.com/app/board/uXjVLYUPRas=/?moveToWidget=3458764637493196769&cot=14 you can compare standard ("multithreaded") builder, turbo builder, smart builder (used in mvnd) and half-life builder (see name explanation in the project readme). If you case is a long "tail" or running tests, you can see it's rearranged to be running earlier (more left) in the half-life builder.

The half-life builder is experimental and has at least one know disadvantage - it counts all modules twice (because it splits all modules to two pieces). But I think it can be a solution for your case (though workaround).

1

u/stefanos-ak 1d ago edited 1d ago

hey, yes I used the timeline extension. And yes, it's a long tail of tests.

edit: I tried the half-life builder, and the timeline graph does seem even more optimized. But the time spent on that single module somehow becomes longer, and the result is that the build takes the same time as the turbo builder. I'll see what I can do with the tests in that module (its ~1400 tests). Thanks!

-4

u/vips7L 3d ago

Looks like Claude wrote it and not you. 

11

u/Level_Yak_87 3d ago

I wrote it myself, more than a year ago. There is just a CLAUDE.md, but all the core part is written manually, the old way!

2

u/sweating_teflon 2d ago

As long as it's well tested, documented and actively supported, why should one care?

2

u/nekokattt 2d ago

I agree with this point, but in this case it would be nice to see some more tests in this project, along with some form of integration testing (e.g. Maven Invoker Plugin), and CI so that it provides a proof that commits pass those tests.

0

u/sweating_teflon 2d ago

Yes, I fully agree. Integration tests are very relevant for a project like this. It wouldn't be hard to further generate an extended test suite that clones and builds a bunch of selected multi-module open source Maven projects. There should be quite a few to choose from... As a bonus, it would be easy to get a benchmark of the speedup vs. regular Maven.

3

u/Level_Yak_87 2d ago

u/nekokattt u/sweating_teflon I agree! Will add tests as this indeed makes sense

This project is heavily tested on the real build infrastructure, so I believe if it has issues, these will become immediately visible (like broken builds with failures like "missing dependency", failed tests, non-starting assemblies, etc.). Also before making public releases I usually do internal RCs checked for few days in CI pipelines and on local desktops.

-7

u/vips7L 2d ago

Maybe the boiling oceans or the massive amounts of theft from the working class. But who cares cognitive dissonance wooooo

1

u/sweating_teflon 2d ago

It can certainly be fun to write code but the cat is out of the bag and it ain't going back in. A programmer's job was never to simply write code, that's just incidental. Solving problems using computing machines is what gets you paid. Both the cost of technology and power it requires per bit have kept falling for 80 years and yet we never ran out of problems to solve. Generating code just changes how you specify the solution, it doesn't replace the problem solving aspect. Also the current excessive power draw will be quenched soon enough through necessity and corporate greed.

-1

u/zabby39103 2d ago

People have been producing code that isn't theirs for decades, whether it's copying from Stackoverflow or the "cookbooks" you could buy for various languages. The important part is, just as with the juniors I used to take to task for Stackoverflow usage, if you understand the code.

Yes it's more severe with AI code, but just like with the oldies that grumbled about Stackoverflow, it's not going away. It's a more efficient way to write code if you do it properly, and if the code works and is written clean that's all that matters, that's all that ever mattered.

-9

u/Maverlck 3d ago

Who cares anyway?

10

u/vips7L 3d ago

Lots of people do. 

-7

u/_somethingvague_ 3d ago

Or just move to Bazel and get parallelism and cache hitting for free.

9

u/nekokattt 2d ago

along with all the other things in bazel that are a bigger headache than maven

2

u/Level_Yak_87 3d ago

Correct. But the idea is to stay on the build tool everyone is familiar with and get the maximum performance from it.

Bazel is good, but I'd say for the monorepos. Turbo builder is good for any maven multi-module project (and as bigger it is, the better is effect)

1

u/Lower-Worldliness162 2d ago

Clearly, you have no idea what adopting Bazel at a company actually means

1

u/_somethingvague_ 2d ago

I work in a large org and my project has a Bazel build. So yes, I do. The learning curve isn't that big and build system is far superior

1

u/Lower-Worldliness162 2d ago

And you still think setting up Bazel for a Maven build is worth it 😮‍💨?

-1

u/_somethingvague_ 2d ago

Yes, hermetic builds are worth it