micronaut-core: Micronaut Client slow, occasional ReadTimeoutException

Expected Behavior

I expected that using a Micronaut Http Client would return results at a similar speed as compared with using java.net.http.HttpClient.

Actual Behaviour

Comparing side by side with a direct HttpClient GET request and a Micronaut Client request the micronaut client is consistently 3-4 times slower.

  • Test Application
    • java.net.http.HttpClient - 30-40 ms
    • io.micronaut.http.client.annotation.Client - 90-100 ms
  • Production Application
    • java.net.http.HttpClient - 80-100 ms
    • io.micronaut.http.client.annotation.Client - 400-500 ms

I also found that the micronaut client occasionally throws a read timeout exception after hanging for 10-20 seconds. On my aws fargate instance this happens quite often. On my local box it only happens once per 30-40 requests.

io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26) at io.micronaut.http.client.netty.DefaultHttpClient.lambda$exchangeImpl$44(DefaultHttpClient.java:1378) at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) at io.micronaut.reactive.reactor.instrument.ReactorSubscriber.onError(ReactorSubscriber.java:64) at reactor.core.publisher.SerializedSubscriber.onError(SerializedSubscriber.java:124) at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:295) at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:280) at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:419) at io.micronaut.reactive.reactor.instrument.ReactorSubscriber.onNext(ReactorSubscriber.java:57) at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) at io.micronaut.reactive.reactor.instrument.ReactorSubscriber.onNext(ReactorSubscriber.java:57) at reactor.core.publisher.MonoDelay$MonoDelayRunnable.propagateDelay(MonoDelay.java:271) at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:286) at io.micronaut.reactive.reactor.instrument.ReactorInstrumentation.lambda$init$0(ReactorInstrumentation.java:62) at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833)

Steps To Reproduce

  1. Checkout https://github.com/kevind-wgu/mn-client-test
  2. mvn clean install
  3. java -jar target/mn-0.1.jar
  4. curl “localhost:8080/book/direct” (run multiple times)
  5. curl “localhost:8080/book/micronaut” (run multiple times)

I have found the direct HttpClient takes about 30-40 ms to download, convert to Book object and return it. I have found the Micronaut Client takes about 100ms to download and return a string.

  • I failed to get the micronaut client to convert the string to an object. I kept getting a io.micronaut.http.codec.CodecException: Cannot decode bytes with value and couldn’t figure out why so I gave up assuming that returning a String instead of a Book object should be faster anyway.

Environment Information

MacOS - Monterey, 12.5.1 Java - openjdk version “17.0.3” 2022-04-19 LTS Maven - Apache Maven 3.6.3

Example Application

https://github.com/kevind-wgu/mn-client-test

Version

3.6.0

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 7
  • Comments: 15 (5 by maintainers)

Most upvoted comments

Can also confirm this is happening. I’m using the httpClient in a @Scheduled controller method. Have tried with a different event loop group and also using @ExecuteOn, neither prevented the read timeout and I have the client read timeout set to “30s” which is plenty for what I’m calling.

I made the changes recommended in the docs and it gets rid of the random ReadTimeout exceptions. It continues to be a little over twice as slow (80-100ms instead of 20-40). Maybe that is expected?

Here are the changes I made to application.yml

micronaut:
  netty:
    event-loops:
      other:
        num-threads: 10
        prefer-native-transport: true
  http:
    client:
      event-loop-group: other

Can the Micronaut HttpClient be used in non-reactive style coding without blocking the event loop?

You’re blocking the event loop so that is the likely cause of the read timeouts