|Hibernate goes Reactive - What Does That Mean?|
|Written by Nikos Vaggalis|
|Monday, 22 November 2021|
Hibernate Reactive Version 1.00 fosters non-blocking I/O access to the database plus reactive extensions. Let's get under the covers and find our why this helps.
First a definition of the problem. JPA/JDBC was always blocking, meaning that any operation that touches the database through JDBC operations will block on socket IO, and when that's happening the thread it is running on will be blocked.
So that means that when I make a call to the database from my main thread, the thread will block until the driver returns the results of the query. Then what about wrapping that call in a CompletableFuture to make the call asynchronous and wait on the Future instead, calling my callback when the database operation completes? That way don't I need a non-blocking driver after all?Perhaps, but since the stack is not non-blocking all the way down, there are certain disadvantages.
But why should the stack be non-blocking all the way down? Let's hear what the experts have to say on that matter, and who better to ask than Rob Hedgpeth, author of R2DBC Revealed with whom with I had a conversation on Twitter:
NV: Why does it have to be non-blocking all the way down to the driver level, like R2DBC?
RH: So, the main difference is that the JDBC API (and implemented drivers) will block the I/O. Meaning it will block at the point the data in the relational database is being accessed, managed, etc. Wrapping in a Completable Future, in this case, will still be waiting for the JDBC thread to complete before delivering via future.
On the other hand, the R2DBC spec provides a rough outline to communicate with a relational DB in a way that doesn’t block disk I/O. The spec itself is really just a guideline, and the actual implementation is left up to you but it can also be used in combination with CompletableFutures.
The gist being the DB wire protocol implementation can be used in a more efficient way, eliminating threads, thereby decreasing memory usage and possibly Increasing throughout. Of course, it all really depends on the use case.
The magic sentence is:
"Wrapping in a Completable future, in this case, will still be waiting for the JDBC thread to complete before delivering via a future".
In that case we just had wrapped a synchronous call in an asynchronous wrapper, just faking asynchrony. The thread that makes the actual jdbc call will block until the query returns and won't be able to go back to the thread pool and serve another request.
That is the essence of non-blocking I/O, but reactivity means much more than that. Adding it to the mix we get the caller/subscriber controlling the volume of the data returned by the publisher/database, aka backpressure, streaming results, and of course the advantage of Reactive programming versus the imperative model.
For instance, in a typical Spring WebFlux reactive application, the stream the subscriber subscribes to gets a Mono or Flux data type which represent a pipeline of operators, without blocking the calling thread. This programming paradigm holds the potential for improved scalability, and more controlled degradation under peak load.
Talking about the reactive stack all the way down, this also means that the underlying engine and web server should also be like that.
As such Hibernate Reactive uses the reactive engine Vert.x and its non-blocking SQL client libraries (not R2DBC). Subsequently Vert.x is based on non-blocking server Netty, which is based on the event loop model instead of Tomcat's model of spanning a new thread on every request. Hence all the HTTP requests that your application receives are handled by event loops and IO threads.
On that matter, Spring Webflux by default has Netty as an embedded server and, while Webflux is also supported on Tomcat, it's only with versions that implement Servlet 3. 1 or later APIs.
Hibernate Reactive is now fully integrated in Quarkus, and, especially with the advent of RESTEasy Reactive as the preferred web layer for Quarkus, it’s now easy and convenient to mix and match reactive and blocking database access code within the same program, making it easy to switch to reactive in those parts of the system where its benefits are obtained.
That means that under Quarkus you can choose:
This happens because:
thanks to hints in your code (such as the @Blocking and @NonBlocking annotations), Quarkus extensions can decide when the application logic is blocking or non-blocking. The HTTP request is always received on an I/O thread.
Then, the extension dispatching that request to your code decides whether to call it on the I/O thread, avoiding thread switches, or on a worker thread. This decision depends on the extension.
For example, the RESTEasy Reactive extension uses the @Blocking annotation to determine if the method needs to be invoked using a worker thread, or if it can be invoked using the I/O thread.
So if you want to go Reactive and since all the stack has to be all the way down reactive, then the ORM part of JPA should be too. And that's what Hibernate Reactive 1.00 brings to the table.
In any case, when is it worth using Hibernate Reactive?
Hibernate Reactive version 1.0.0 currently supports the Vert.x clients for PostgreSQL, MySQL, and DB2, though the architecture is not limited to these drivers.
More details on the project's GitHub repo.
or email your comment to: firstname.lastname@example.org
|Last Updated ( Monday, 22 November 2021 )|