Netty vs Rust on Socks5 Proxy Performance

This article was originally posted on medium, just finished setup WordPress on oracle cloud, hence moved to here.

A weekend half-day project to explore the Netty, Rust, and Docker.

Inspired by this video, Rust for Java Developers — Mitch Blevins: OKC JUG.

The comparison between Netty and Rust is not a good one; since Netty is a high-performance app framework written in Java, while rust is a programming language. However, the comparison could be interesting and help some people to decide which route to go, Java or Rust.

The comparison is going to utilize Docker containers since we can use docker stats to see CPU and memory usage.
We will set up 3 projects, netty-socks5, rust-socks5(merino), and Nginx static web app with a 4k image.

You can reference the comparison repository here

We won’t walk through how to set up a Gradle java project for Netty or rust project, there are tutorials for them. What I learned from this comparison is how to create docker images for the Rust app and do the performance test.

1. Dockerfile for Netty app

This will build a jar with Gradle and copy it into an OpenJDK runtime image.

FROM gradle:jdk11 AS build
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle build --no-daemonFROM openjdk:11-jre
EXPOSE 1080RUN mkdir /app
COPY --from=build /home/gradle/src/build/libs/*.jar /app/netty-socks5.jar
ENTRYPOINT ["java", "-jar","/app/netty-socks5.jar"]

2. Dockerfile for Rust app

This is an interesting step, I was trying to create executable binary locally on my Linux x64 machine, then copy the binary to scratch docker image, it will throw the following error.

standard_init_linux.go:211: exec user process caused "no such file or directory"

Also, it is not easy to use musl toolchain to compile binary locally, it would have an OpenSSL library issue. so I have to use clux/muslrust to build the binary, it is good learning.

As you may notice the comment in the Dockerfile, we have to use ENTRYPOINT instead of CMD, otherwise, it will throw cannot find file error from Linux. If anyone knows the reason please leave a comment.

FROM clux/muslrust AS buildRUN mkdir /source
WORKDIR /source
COPY ./Cargo.toml .
COPY ./Cargo.lock .
COPY ./src ./srcRUN cargo build --release
RUN strip ./target/x86_64-unknown-linux-musl/release/merinoFROM scratch
EXPOSE 1080
COPY --from=build /source/target/x86_64-unknown-linux-musl/release/merino /# cannot use CMD ["./merino --no-auth"], it won't find the cmd
ENTRYPOINT [ "./merino", "--no-auth"]

3. Dockerfile for webapp

We don’t want to be blocked by google.com or some websites because of the performance test, there are GB of data to be transferred and would cost a lot of time with the U.S. low internet speed (50 Mpbs)

There is no ENTRYPOINT or CMD here since the Nginx image auto starts up the Nginx service.

FROM nginx:alpine
COPY ./4k.jpg /usr/share/nginx/html/

4. Compose up

A docker-compose.yml file will help us to set up containers with a network interface, each container would know each other through the DNS 127.0.0.11. I was wondering how can I let curl, netty-socks5, or rust-socks5 resolve http://webapp/4k.jpg. As it later turns that curl could send socks5 to connect request with Domain as the destination.

curl -s -x socks5h://localhost:1081 http://webapp/4k.jpg >/dev/nulluse socks5h instead of --socks5 will force curl to do DNS resolution in Socks5 Server
version: '2'services: 
netty-socks5:
build: ./netty-socks5
ports:
- "1081:1080"
rust-socks5:
build: ./rust-socks5
ports:
- "1082:1080"
webapp:
build: ./webapp

5. Start the test!

It is getting excited this moment, time to reveal the trust. I am impressed by how a small rust app could be and its C nature, my guess was rust would outperform the Netty (or Java).

I did 2 kinds of tests, sequential and parallel. Test script is here. Sequential maybe not be interesting because, in the real world, all browsing requests happen in parallel.

Each test consists of 1000 requests to 4k.jpg file, and 10 threads will be started to do the requests.

Parallel Tests Result

Here is the result with freshly started containers.

1. fresh started containers
netty(java) 0m12.278s
merino(rust) 0m10.665s2. test after test 1
netty(java) 0m10.499s
merino(rust) 0m10.696s3. fresh started containers again
netty(java) 0m12.423s
merino(rust) 0m10.963s4. test after test 3
netty(java) 0m10.783s
merino(rust) 0m10.978s
Netty-Socks5 idle time has CPU usage from 0.05% to 0.15%, while rust is always 0%

Observations

  1. Netty (Java) is slower on the warm up test, this may due to Java’s overhead.
  2. Merino (Rust) is slower on the second test after warm up. I didn’t dig into why is that, maybe Merino(Rust) implementation can be improved?
  3. Netty(Java) memory usage is higher than Merino (Rust), 400MB vs 2.5MB, as we know first one requires JVM.
  4. Netty(Java) docker image size is higher than Merino (Rust), 289MB vs 2.12 MB. If we ignore the JVM or Linux file system, Netty app size is about 3MB, while Merino app is 2.1MB

Thanks for reading! Please leave comments if anything is wrong.