Spring Reactor Webflux Scheduler Parallelism
Asked Answered
F

2

6

For a fully non-blocking end to end reactive calls, is it recommended to explicitly call publishOn or subscribeOn to switch schedulers? For either cpu consuming or non consuming tasks, is it favorable to always use parallel flux to optimize performance?

Ferriferous answered 25/5, 2020 at 16:53 Comment(1)
webflux already uses a multi-threaded event loop group by default, so in normal circumstances I wouldn't expect any advantage from thatTriplett
M
4

For a fully non-blocking end to end reactive calls, is it recommended to explicitly call publishOn or subscribeOn to switch schedulers?

publishOn is used when you publish data to downstream while subscribeOn is used when you consume data from upstream. So it really depends on what kind of job you want to perform.

For either cpu consuming or non consuming tasks, is it favorable to always use parallel flux to optimize performance?

Absolutely not, Consider this example:

Flux.range(1, 10)
        .parallel(4)
        .runOn(Schedulers.parallel())
        .sequential()
        .elapsed()
        .subscribe(i -> System.out.printf(" %s ", i));

The above code is a total waste because i will be processed almost instantly. Following code will perform better than above:

Flux.range(1, 10)
        .elapsed()
        .subscribe(i -> System.out.printf(" %s ", i));

Now consider this:

public static <T> T someMethodThatBlocks(T i, int ms) {
    try { Thread.sleep( ms ); }
    catch (InterruptedException e) {}
    return i;
}

// some method here
Flux.range(1, 10)
        .parallel(4)
        .runOn(Schedulers.parallel())
        .map(i -> someMethodThatBlocks(i, 200))
        .sequential()
        .elapsed()
        .subscribe(i -> System.out.printf(" %s ", i));

The output is similar to:

 [210,3]  [5,1]  [0,2]  [0,4]  [196,6]  [0,8]  [0,5]  [4,7]  [196,10]  [0,9] 

As you can see that first response came in after 210 ms, followed by 3 responses with near 0 elapsed time in between. The cycle is repeated again and again. This is where you should be using parallel flux. Note that creating more number of threads doesn't guarantee performance because when there are more number of threads then context switching adds alot of overhead and hence the code should be tested well before deployment. If there are alot of blocking calls, having more than 1 number of thread per cpu may give you a performance boost but if the calls made are cpu intensive then having more than one thread per cpu will slow down the performance due to context switching.

So all in all, it always depends on what you want to achieve.

Moreland answered 25/5, 2020 at 17:56 Comment(0)
V
4

Worth stating I'm assuming the context here is Webflux rather than reactor in general (since the question is tagged as such.) The recommendations will can of course vary wildly if we're talking about a generalised reactor use case without considering Webflux.

For a fully non-blocking end to end reactive calls, is it recommended to explicitly call publishOn or subscribeOn to switch schedulers?

The general recommendation is not to explicitly call those methods unless you have a reason to do so. (There's nothing wrong with using them in the correct context, but doing so "just because" will likely bring no benefit.)

For either cpu consuming or non consuming tasks, is it favorable to always use parallel flux to optimize performance?

It depends what you're aiming to achieve, and what you mean by "CPU consuming" (or CPU intensive) tasks. Note that here I'm talking about genuinely CPU intensive tasks rather than blocking code - and in this case, I'd ideally farm the CPU intensive part out to another microservice, enabling you to scale that as required, separately from your Webflux service.

Using a parallel flux (and running it on the parallel scheduler) should use all available cores to process data - which may well result in it being processed faster. But bear in mind you also have, by default, an event loop running for each core, so you've essentially "stolen" some available capacity from the event loop in order to achieve that. Whether that is ideal depends on your use-case, but usually it won't bring much benefit.

Instead, there's two approaches I'd recommend:

  • If you can break your CPU intensive task down into small, low intensity chunks, do so - and then you can keep it on the event loop. This allows the event loop to keep running in a timely fashion while these CPU intensive tasks are scheduled as they're able to be.
  • If you can't break it down, spin up a separate Scheduler (optionally with a low priority so it's less likely to steal resources from the event loop), and then farm all your CPU intensive tasks out to that. This has the disadvantage of creating a bunch more threads, but again keeps the event loop free. By default you'll have as many threads as there are cores for the event loop - you may wish to reduce that in order to give your "CPU intensive" scheduler more cores to play with.
Variscite answered 25/5, 2020 at 21:56 Comment(3)
other than calling using schedulers for blocking calls, are there any other reason to publish/subscribe on schedulers?Ferriferous
@Ferriferous Absolutely - you may want to consider that for CPU intensive tasks as per my answer above, particularly if you can't break that task up into small chunks (and run each of those chunks individually.)Variscite
is it possible to have a low priority scheduler in reactor land?Whittle

© 2022 - 2024 — McMap. All rights reserved.