Search code examples
spring-webfluxreactor

WebFlux Controllers Returning Flux and Backpressure


In Spring WebFlux I have a controller similar to this:

@RestController
@RequestMapping("/data")
public class DataController {

  @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
  public Flux<Data> getData() {
    return <data from database using reactive driver>
  } 
}
  1. What exactly is subscribing to the publisher?
  2. What (if anything) is providing backpressure?

For context I'm trying to evaluate if there are advantages to using Spring WebFlux in this specific situation over Spring MVC.


Solution

  • Note: I am not a developer of spring framework, so any comments are welcome.

    What exactly is subscribing to the publisher?

    It is a long living subscription to the port (the server initialisation itself). Therefore, the ReactorHttpServer.class has the method:

    @Override
    protected void startInternal() {
        DisposableServer server = this.reactorServer.handle(this.reactorHandler).bind().block();
        setPort(((InetSocketAddress) server.address()).getPort());
        this.serverRef.set(server);
    }
    

    The Subscriber is the bind method, which (as far as I can see) does request(Long.MAX_VALUE), so no back pressure management here.

    The important part for request handling is the method handle(this.reactorHandler). The reactorHandler is an instance of ReactorHttpHandlerAdapter. Further up the stack (within the apply method of ReactorHttpHandlerAdapter) is the DispatcherHandler.class. The java doc of this class starts with " Central dispatcher for HTTP request handlers/controllers. Dispatches to registered handlers for processing a request, providing convenient mapping facilities.". It has the central method:

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (this.handlerMappings == null) {
            return createNotFoundError();
        }
        return Flux.fromIterable(this.handlerMappings)
                .concatMap(mapping -> mapping.getHandler(exchange))
                .next()
                .switchIfEmpty(createNotFoundError())
                .flatMap(handler -> invokeHandler(exchange, handler))
                .flatMap(result -> handleResult(exchange, result));
    }
    

    Here, the actual request processing happens. The response is written within handleResult. It now depends on the actual server implementation, how the result is written.

    For the default server, i.e. Reactor Netty it will be a ReactorServerHttpResponse.class. Here you can see the method writeWithInternal. This one takes the publisher result of the handler method and writes it to the underlying HTTP connection:

    @Override
    protected Mono<Void> writeWithInternal(Publisher<? extends DataBuffer> publisher) {
        return this.response.send(toByteBufs(publisher)).then();
    }   
    

    One implementation of NettyOutbound.send( ... ) is reactor.netty.channel.ChannelOperations. For your specific case of a Flux return, this implementation manages the NIO within MonoSendMany.class. This class does subscribe( ... ) with a SendManyInner.class, which does back pressure management by implementing Subscriber which onSubscribe does request(128). I guess Netty internally uses TCP ACK to signal successful transmission.

    So,

    What (if anything) is providing backpressure?

    ... yes, backpressure is provided, e.g. by SendManyInner.class, however also other implementations exist.

    For context I'm trying to evaluate if there are advantages to using Spring WebFlux in this specific situation over Spring MVC.

    I think, it is definitely worth evaluating. For performance I however guess, the result will depend on the amount of concurrent requests and maybe also on the type of your Data class. Generally speaking, Webflux is usually the preferred choice for high throughput, low latency situations, and we generally see better hardware utilization in our environments. Assuming you take your data from a database you probably will have the best results with a database driver that too supports reactive. Besides performance, the back pressure management is always a good reason to have a look at Webflux. Since we adopted to Webflux, our data platform never had problems with stability anymore (not to claim, there are no other ways to have a stable system, but here many issues are solved out of the box).

    As a side note: I recommend, having a closer look at Schedulers we just recently gained 30% cpu time by choosing the right one for slow DB accesses.

    EDIT: In https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn-handler-functions the reference documentation explicitly says:

    ServerRequest and ServerResponse are immutable interfaces that offer JDK 8-friendly access to the HTTP request and response. Both request and response provide Reactive Streams back pressure against the body streams.