Search code examples
spring-webfluxreactive

Should we use a reactive-stack web framework like spring webflux when we have blocking calls?


I'm trying to understand when we would use a reactive-stack framework like webflux. I've read articles that seems to suggest that we would benefit from reactive approach when we have many blocking calls. For example, if we had a webhook service that needs to call client servers to update them with information.

But I also read here https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html

A simple way to evaluate an application is to check its dependencies. If you have blocking persistence APIs (JPA, JDBC) or networking APIs to use, Spring MVC is the best choice for common architectures at least. It is technically feasible with both Reactor and RxJava to perform blocking calls on a separate thread but you would not be making the most of a non-blocking web stack.

And this seems to suggest the exact opposite. I read that reactive is more useful if you can stream the information. Example if you have a frontend asking for a list and a reactive source that can give you the information as it becomes available rather than only all of it when it is done.

So the question is when should we use reactive for backend? Should we use it when we have many blocking calls? E.g. HTTP calls to a client where we need to wait for the responses. Or is that exactly the wrong use case for it?

I'm aware there are other considerations like the complexity of the code. I know the reactive approach is harder to implement but I'm only asking about performance and scalability here.


Solution

  • It's very hard to give you any concrete answer here since we don't know your exact architecture.

    But if you understand the problem reactive is trying to solve, and how it does it might give you a better insight into making a better decision.

    The traditional servlet approach that most web servers in Java uses assigns one thread to each request. So as a request comes in, one thread is assigned to it, and this thread then processes the request. If your server then does blocking calls to other servers, the assigned thread needs to wait for the response to come back.

    This tends to result in that web servers have several 100s of threads that spend a lot of their time just waiting. And when i mean waiting, i mean waiting a lot. As much of 90% of the time of a thread could be spent just waiting for a blocking call. For instance, the processing in a web server might take 3ms, then it does a blocking database call, and the thread needs to wait 200ms (don't quote me on the numbers).

    That is a lot of resources spent just waiting.

    So old way:

    • one thread per request
    • if 300 requests then we have 300 threads
    • High memory usage (each thread needs memory)
    • CPU spends a lot of time waiting

    Reactive solves this by having something called an event loop, and then a small thread pool that schedules work on the event loop.

    In practice you can see it like this, one event loop, and then maybe 10 threads that all schedule work, the event loop just works, all the time, and the schedulers just schedule a long line of work for the event loop to push through. So all the threads are constantly 100% busy.

    In a webflux application, usually the number of event loops are dependent on the number of cores in the hardware.

    But this means that we need to be 100% non-blocking. Imagine we have a blocking call in this scenario, then the entire eventloop would stop, all the scheduled jobs would stop, and the entire machine would freeze until we are "un-blocked".

    So reactive:

    • event-loop does all the work
    • small thread pool that schedules work
    • blocking is VERY bad, can freeze entire application
    • since less threads, smaller memory footprint
    • higher CPU usage
    • Possibly higher throughput

    So we are basically trading memory for CPU power.

    So what is a blocking call? well most calls are blocking as in you call another service and you need to wait. But this is where reactive shines, because it has one other feature.

    Since it does not have one specific thread per request, any thread can make the request, but here's the thing, any thread can handle the response, it doesn't have to be the same thread.

    We are what is called thread-agnostic.

    Our non-blocking service can do a lot of blocking calls to other services and still stay completely non-blocking. Cause when say non-blocking i'm meaning that we are non-blocking internally inside our own application.

    So what is a bad blocking call then? well its when you are calling something that is thread dependent. Meaning that you are calling something that is dependent on the same thread that made the call to handle the response, then we need to block that thread and wait for the response.

    If we need to make a call, then block to wait for the response, then handle the response because we need the same thread then we can't use reactive, because then we might block the event loop and this will stop the entire application.

    So for instance everything that uses ThreadLocal is bad in a reactive world, and this is where one of the major problems comes in. JDBC (the database driver specification) was written inherently blocking. As it is dependent on thread local to keep track of transactions to be able to rollback. Which means that all database calls using JDBC are not usuable in a non/blocking reactive application, which means you have to use database drivers that uses the R2DBC spec.

    But a REST call isn't blocking, as it is not thread dependent unless you use thread-dependent functionality, like ThreadLocal which Spring Webclient doesn't.

    So what is your quote talking about? Well Spring Reactor has a mechanism so that you can mix the "old way" (one thread per request) with the new way (thread agnostic). This means that if you would have a hard blocking call (calling an old database using a JDBC driver), you can explicitly tell the framework that "all calls to this database should be placed on its own thread pool" so you are sort of telling the framework that use the old way (by assigning one exclusive thread for that request) for that specific call. But remember you lose all the benefits of reactive by doing this.

    So if your service is only calling a lot of hard blocking services (like old databases), you would have to constantly opt out of the reactive framework, so you would basically just build an old traditional servlet web service using a reactive framework, which is sort of an anti-pattern. So i do not recommend that.

    All I have written here is general computer knowledge, how threads work, how REST calls work, how database drivers work. I can't explain how computers work in a single stack overflow post.

    This and a lot more is stated in the Reactor reference and I suggest you do more research.

    If you have a road with many turns and no straights, is there any purpose to buying a F1 car if you constantly have to slow down and do a lot of turns all the time?

    I leave that decision to you.