What happens in Webflux if a single synchronous call is made?
Asked Answered
P

2

7

I'm beginning a foray into the world of reactive Java programming using Webflux in Spring Boot.

I'm running into a scenario where it's really hard to do a certain database call reactively.

If I do a single blocking database call within a Mono, what happens?

The code would look something like this...

public Mono<ReturnThing> isThisAsyncOrNot() {
    //Async, non-blocking API call
    return webClient.doSomeAPIWork()
                     .flatMap(whatevers -> {
                         //Synchronous, blocking database call
                         ReturnThing returnThing= returnThingRepo.getByWhateverId(whatever.ID);
                         }
                         return returnThing;
                     });
}

Now yes, I know that there's an easy way to do this reactively. That's not the question I'm asking (in fact, the real code is quite a bit more complex).

What I really want to know is what that synchronous database call will do to my performance. Will the overall method still be async non-blocking (except during the part where the db call is made, which is blocking)? Or will this somehow ruin the whole reactive paradigm and cause the entire thing, start to finish, to be blocking?

Paradise answered 2/1, 2020 at 16:45 Comment(1)
I am surprised that your lambda method in flatMap() returns ReturnThing and not Mono. I understand that you wanted to simplify your code, but now there is a confusion. Does your getByWhateverId() method return Mono (but in that case it is reactive), or did you mean map() instead of flatMap()?Daggett
L
8

The golden rule to remember is that you can never make a blocking method non-blocking. You can go the other way around trivially, and you can do various things to not wreck the reactive paradigm entirely, but there's no way to turn it inherently asynchronous.

Or will this somehow ruin the whole reactive paradigm and cause the entire thing, start to finish, to be blocking?

Worse than this, unfortunately. Assuming it doesn't crash the application, it will cause one of the few reactive threads (or maybe the single reactive thread) to block while your blocking database call executes. This means that all other reactive operations that need to use that thread (this could quite feasibly be your entire application) will have to wait until that blocking database call finishes before they can be scheduled, which is a critical hit on performance.

The accepted way to deal with these sorts of situations (where you have a blocking call you need to make from a reactive chain) is to use the bounded elastic scheduler, which delegates execution to a backend thread pool so as not to tie up the main event thread(s).

Leavelle answered 2/1, 2020 at 18:4 Comment(1)
R
8

You need to understand the crux of reactive and you'll be able to answer this question on your own. Each operator in reactive processes items one-at-a-time. Each operator also has a queue where items can be queued if the operator is busy. Now consider the following code:

Flux.fromIterable(Arrays.asList(1,2,3,4,5))
    .flatMap(a -> {
         Thread.sleep(2000);
         return Mono.just(a);
    }).subscribe(System.out::println);

You'll see items are prints at intervals of 2 seconds. This is because flatMap's thread is blocked for 2 seconds, and the items rest of the elements are queued up. flatMap's thread has to become free if it wants to consume items from its queue.

In your case, you said that the DB call was blocking. What this would result in is the calling thread getting blocked. So your method will neither be non-blocking nor asynchronous. However, do something like

Mono.just(whatever)
    .subscribeOn(Schedulers.elastic())
    .flatMap(whatever -> returnThingRepo.getByWhateverId(whatever.ID));

inside the flatMap and your method would become non-blocking and asynchronous. This happens because the calling thread becomes free as soon as the .subscribeOn() operator is called.

Hopefully this answered your question.

This does not break the reactive paradigm. Reactive, as I see it, is much more than non-blocking, asynchronous operations. The idiomaticity and easy multi-threading capabilities are a big selling point for me, besides NIO.

Roselba answered 6/1, 2020 at 17:9 Comment(1)
I realise I'm late to the party, but I've got a question. How could verify in a test that the thread becomes free as soon as the .subscribeOn() operator is called? I've added this to my project now, but I'm not sure if I'm using it right. I use several operators after subscribeOn, like doOnNext, collectList, flatMap, which contains blocking code.Posehn

© 2022 - 2024 — McMap. All rights reserved.