Search code examples
spring-boothl7-fhirspring-messaginghapi-fhirrsocket

RSocket vs HTTP performance for transferring FHIR resources


I have recently started a series of performance investigations on using service-to-service communication of FHIR resources, to identify the processing time spent in the:

  1. Payloads communication/exchange
  2. Serializing and deserializing the payloads

During the investigations I have encountered two results which I don't understand, hence I want the help of the RSocket development team. I will detail the results and questions onward.

In order to identify the fastest communication method, I have analysed three transport methods using two transport protocols - HTTP and RSocket. More exactly - I have analysed and benchmarked:

  1. Exchanging FHIR resources using the HAPI REST server and a HAPI FHIR client
  2. Exchanging strings (serialized FHIR resources) using REST communication, by accessing Spring @RestController endpoints using aRestTemplate web client
  3. Exchanging FHIR resources using RSocket messages

The analysis of the first two communication methods yielded huge differences between the exchange of FHIR resources using the HAPI REST server and the exchange of (raw) string payloads, including the deserializing of those payloads into FHIR resources. More exactly - for big FHIR resources, the overhead added by the HAPI REST server is around 3-4 times bigger than the one entailed by the (raw) strings communication and deserializing.

Regarding the RSocket communication between two services - I have tried to use two modes to exchange the FHIR resources:

  1. As raw strings, serialized from the FHIR resources
  2. As raw FHIR resources, leaving RSocket to handle the serializing and deserializing

The first method (using raw strings) yielded payload exchange overheads almost similar to the ones entailed by the HTTP (using REST) communication. More exactly - the communication overhead is a few percentages (5-10%) higher than the HTTP communication. This is surprising for me, as I thought the RSocket communication overhead will be much lower than the HTTP communication - I have seen at least one Spring & Netifi presentation in which RSocket is advertised to be '10x faster than HTTP'.

My first thought was that I have done something wrong in the RSocket configuration, hence I have tried various configuration changes for the RSocketRequester Spring bean, starting with the setting of the zero copy frame decoder. However, none of them made any noticeable improvements in the overall performance.

My next attempt was to exchange raw FHIR resources between the services, by implementing the RSocket encoder and decoder classes to exchange the FHIR resource. After some struggle with the implementation of the RSocket Encoder and Decoder interfaces, I have managed to exchange FHIR resources between the two services. The problem - the performance of the FHIR resources communication is very low, way lower than the performance of the Strings exchange.


With the corresponding apologies for the very long introduction / context setting, my question / help request is: what am I doing wrong and/or missing in my analysis, so that I haven't obtained the performance benefits promised by RSocket?

I have included the two sample projects (an RSocket requester & a responder) in this Github repository. The most relevant classes are the RSocket configuration and the FHIR Bundle encoder and decoder.

Disclaimer: I am aware this question would have been better addressed on the Netifi community pages. In the last couple of days the page is no longer accessible, hence this long post here.

Thank you, in advance.


Solution

  • RSocket maintainer here.

    Briefly looking into the configuration, I may say that the benchmark is inappropriately formed and obviously will give an incorrect result.

    What I can say directly is that at this point you are calling readBundle method which subscribe to the requesterMono every time you execute a remote call, which basically open a new TCP connection on every subscription.

    It means that the behavior you achieve is absolutely identical to standard Http 1.0 behavior (that is why you are seeing the same results).

    As the first step in achieving the correct setup, I would recommend you to cache your Mono<RSocketRequester> in order to reuse the same connection for all the calls.

     @Bean
    public Mono<RSocketRequester> requester(BundleDecoder bundleDecoder, IntegerEncoder integerEncoder) {
            final RSocketStrategies.Builder builder = RSocketStrategies.builder()
                                                                       .decoder(bundleDecoder)
                                                                       .encoder(integerEncoder);
    
            return RSocketRequester.builder()
                                   .rsocketFactory(factory -> factory.dataMimeType(MediaType.APPLICATION_CBOR_VALUE)
                                                                     .frameDecoder(PayloadDecoder.ZERO_COPY))
                                   .rsocketStrategies(builder.build())
                                   .connectTcp(responderHost, responderPort)
                                   .retry()
                                   .cache();
    }
    

    Apart from that, I may recommend you to look at the usage of LoadBalancedRSocket which let you efficiently reuse a couple of connections for tons of calls -> https://github.com/OlegDokuka/rsocket-issue-717