4

Since R2DBC is reactive and non blocking I would like to understand the benefit of using R2DBC in a simple RESTful CRUD service

Assume a spring boot application is exposing a RESTful service using a repository below

public interface CustomerRepository extends ReactiveCrudRepository<Customer, Long> {

    @Query("SELECT * FROM customer WHERE last_name = :lastname")
    Flux<Customer> findByLastName(String lastName);

}

This repository is called from a service and the results needs to be transformed in the service before returning to controller.

Flux<Customer> customers = repository.findAll();

In order to access the complete list of customers , I need to invoke blockLast() on the Flux which makes it blocking and defeats the purpose of using reactive components

Does that mean that there is no benefit of using R2DBC in this simple example ? Am I missing something ?

Can flux be used only for asynchronous subscription where the processing of Flux collections happens in a different thread ?

lives
  • 1,243
  • 5
  • 25
  • 61

2 Answers2

4

In order to access the complete list of customers , I need to invoke blockLast() on the Flux which makes it blocking and defeats the purpose of using reactive components

You only need to call blockLast() if you want to actually obtain a reference to a List<Customer> - but as you say, if you do that, you lose all the reactive advantages. (The only time you should really consider blocking IMHO is if you're migrating to a reactive system, and putting reactive libraries in place, but not yet ready to make the whole system reactive.)

If you just want to access the complete list of customers at once, you might sensibly call collectList() to get a Mono<List<Customer>. That way you stay in a reactive context, but in any reactive operator you have the entire list available.

The only thing to watch out for here is the memory footprint - if you just process a Flux<Customer> as-is, then you never need to store a group of them in memory so it really doesn't matter how many there are (it can even be infinite.) If you collect it to a list first, then all of those customers then have to be stored in memory simultaneously.

Whether this is a problem depends on your use-case. If you're talking about 10 or so customers, no problem at all. If you're talking about billions, then chances are that's not going to be a sensible solution.

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
  • Thanks Michael for the detailed clarification. Is there any benefit of using Mono> in the above example , apart from the fact that the code can stay in a reactive context ? – lives Jul 09 '21 at 17:06
  • Agree. If there are million records , then using Flux is right option as the records can be processed as and when they are available ,instead of waiting for the huge list to be build. This will help in efficient usage of memory. But I tested that example and found that the consumer in that case is processing in the same main thread. It’s as good as the blocking scenario. I have posted the sample code here . https://stackoverflow.com/questions/68320273/reg-different-threads-for-flux-consumer Let me know if my understanding is incorrect – lives Jul 09 '21 at 17:07
  • @lives I'm not sure I understand your point - it's certainly not equivalent to a blocking solution just because it's using one thread. The thread things are processing in is of little relevance - the point of a reactive solution is that you can have multiple non-blocking operations executing concurrently on the same thread, therefore avoiding the overhead of context switching between different threads. – Michael Berry Jul 09 '21 at 18:00
  • Thanks Mike. Can you please share any link which explains " Multiple non blocking operations executing concurrently on same thread" ? – lives Jul 09 '21 at 22:15
  • @lives The top answer here would be a good start - essentially this is the single threaded event loop concept: https://stackoverflow.com/questions/34855352/how-in-general-does-node-js-handle-10-000-concurrent-requests – Michael Berry Jul 09 '21 at 22:36
2
  1. Since the method retuns a Flux, it returns a promise that can complete sometime in future.
  2. The thread calling this method should not block - that's the whole point of reactive. If you use .blockLast(), you will make the calling thread block. As Michael said in this answer, this should only be done when you are integrating reactive and non-reactive code together.
  3. Here is a diagram that explains how async. computations are work (reactive, CompletableFuture, etc.). Hopefully this clears your doubts;

enter image description here

As you can see in the diagram, if you do .blockLast(), you lose out on true non-blocking. Ideally, the calling thread should get free immediately so that it can do other work.

  1. A properly written reactive application will be a single chain with the subscription happening at the upper-most layer. The entire application should be an async pipeline. You should use applications like BlockHound to check if your threads are blocking anywhere.

"Can flux be used only for asynchronous subscription where the processing of Flux collections happens in a different thread ?" - Flux is nothing but a promise that says it can finish some time in future. As the diagram shows, callbacks will execute on the thread that the async. computation finished on. You can switch this thread using .publishOn().

Prashant Pandey
  • 4,332
  • 3
  • 26
  • 44