zio / zio-http

A next-generation Scala framework for building scalable, correct, and efficient HTTP clients and servers
https://zio.dev/zio-http
Apache License 2.0
800 stars 403 forks source link

Optimize netty integration and default config #3114

Closed kyri-petrou closed 2 months ago

kyri-petrou commented 2 months ago

Change to default Netty configuration

The main improvement in this PR is a change in the sizing of Netty's EvenLoopGroup. By default, Netty will size the EventLoopGroup at nCPU * 2, and will use this pool for 2 purposes:

  1. Accepting new connections (boss)
  2. Running the pipeline and IO (worker)

The sizing of this pool is extremely conservative and assumes that some blocking IO might occur in Netty's worker threads. It also guards against cases where the underlying transport is blocking. However, since we're delegating all user-defined logic to ZIO's executor, and since the transports we're using are all non-blocking (Epoll, KQueue, NIO), a better configuration is to:

  1. Use a separate EventLoopGroup with nThreads=1 for the boss group. This way, we avoid delays in accepting new connections when the worker threads are busy serving existing connections
  2. Size the worker EventLoopGroup with nThreads=nCPU since there's no blocking code running in the worker threads

With this change, we see a ~10% increase in throughput vs the default configuration

Use ctx.channel.write instead of ctx.write in ServerInboundHandler

This part is not really well documented in Netty, but their main differences are

In our case, short-cutting writes to the pipeline by starting at the current handler doesn't bring any benefit because the ServerInboundHandler is the last one in the pipeline. In addition, as this video on Netty's best practises seems to suggest, when we're writing to the channel from a different thread it's better to use ctx.channel.write.

Other changes