Hi to every Rust and Go fanboy. I was so curious and restless about which one is faster Rust or Go for the backend services.
Then, I just get up from the bed and do a load testing for them. Okay, let’s do the testing by defining all conditions first.
Note that: If I was wrong or missing something, Please tell me. For the reasons of my first time doing the load testing.
Conditions
I need to write an API services application that contains just one endpoint for each lang. Thus I just need to create an API to respond “Hello, 世界!” as a stupid text response. (世界 means world in Japanese.)
Next, I need to pack all applications into the Docker container and deploy them up to the GCP (Google Cloud Platform) using the compute engine.
The spec of the compute engine in summary are
- CPU -> E2-micro 2 vCPU 1 core
- RAM -> 1 GB
- Disk/Storage -> Balanced persistent disk
Dockerfile is going to be a multistage building and based on Bullseye Debian.
The requests are going to be done by Grafana K6 + Docker. For each round of load testing must be 250, 500, and 1000 virtual users and the duration of the request is 30 seconds constant.
The number of virtual users is related to the number of the request. Thus max requests are 250, 500, and 1000 requests for each round of load testing. respectively.
Golang Coding and Dockerfile
I use Echo to do an API services application by following of this code section below.
API code
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet},
}))
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, 世界!")
})
e.Logger.Fatal(e.Start(":3000"))
}
Dockerfile
FROM golang:1.21-bullseye AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 go build -o /bin/app
FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y ca-certificates
COPY --from=build /bin/app /bin
CMD ["/bin/app"]
Rust Coding and Dockerfile
In Rust’s API services, I use Axum to build by the following code.
use axum::{
routing::get,
Router, http::Method,
};
use tower_http::cors::{CorsLayer, Any};
#[tokio::main]
async fn main() {
let cors = CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any);
// build our application with a single route
let app = Router::new()
.layer(cors)
.route("/", get(|| async { "Hello, 世界!" }));
// run it with hyper on localhost:3000
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Dockerfile
FROM rust:1.71-bullseye as builder
WORKDIR /usr/src/myapp
COPY . .
RUN cargo install --path .
FROM debian:bullseye-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/cargo/bin/rust-helloworld /usr/local/bin/rust-helloworld
CMD ["rust-helloworld"]
Then, deploy them up to the compute engine and configure the Firewall to allow TCP access within the port you want.
Set up the test script.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 250 // Don't forget to change this value for each round of testing,
duration: '30s',
};
export default function () {
const res = http.get('http://0.0.0.0:3000/');
check(res, { 'status was 200': (r) => r.status == 200 });
sleep(1);
}
Let’s run the test.
docker run --rm -i grafana/k6 run - <script.js
And see the result.
Result
Rust 250, 500, 1000 vus
#250 vus
data_received..................: 923 kB 30 kB/s
data_sent......................: 585 kB 19 kB/s
http_req_blocked...............: avg=9.88ms min=1µs med=3.7µs max=499.89ms p(90)=11µs p(95)=22.17µs
http_req_duration..............: avg=71.55ms min=33.49ms med=44.51ms max=499.03ms p(90)=116.33ms p(95)=206.49ms
vus............................: min=64 max=250
#500 vus
data_received..................: 1.8 MB 59 kB/s
data_sent......................: 1.2 MB 38 kB/s
http_req_blocked...............: avg=8.56ms min=1.1µs med=3.6µs max=449.49ms p(90)=6.1µs p(95)=15.09µs
http_req_duration..............: avg=74.73ms min=32.57ms med=41.99ms max=509.74ms p(90)=186.39ms p(95)=281.23ms
vus............................: min=66 max=500
#1000 vus
data_received..................: 2.0 MB 65 kB/s
data_sent......................: 1.3 MB 41 kB/s
http_req_blocked...............: avg=89.68ms min=1.2µs med=3.7µs max=15.35s p(90)=7µs p(95)=476.18ms
http_req_duration..............: avg=883.84ms min=34.22ms med=1.07s max=1.81s p(90)=1.39s p(95)=1.48s
vus............................: min=95 max=1000
Go 250, 500, 1000 vus
#250 vus
data_received..................: 977 kB 32 kB/s
data_sent......................: 566 kB 18 kB/s
http_req_blocked...............: avg=5.49ms min=1.4µs med=4.2µs max=259.29ms p(90)=7µs p(95)=22.3µs
http_req_duration..............: avg=121.09ms min=33.04ms med=50.11ms max=689.9ms p(90)=329.37ms p(95)=458.56ms
vus............................: min=250 max=250
#500 vus
data_received..................: 1.7 MB 55 kB/s
data_sent......................: 1000 kB 32 kB/s
http_req_blocked...............: avg=20.6ms min=1µs med=3.8µs max=964.68ms p(90)=6.4µs p(95)=25.63µs
http_req_duration..............: avg=275.63ms min=33.71ms med=170.2ms max=1.05s p(90)=678.4ms p(95)=830.93ms
vus............................: min=307 max=500
#1000 vus
data_received..................: 2.3 MB 72 kB/s
data_sent......................: 1.3 MB 42 kB/s
http_req_blocked...............: avg=59.23ms min=1.1µs med=3.6µs max=1.8s p(90)=6.6µs p(95)=326.87ms
http_req_duration..............: avg=935.49ms min=33.05ms med=1.04s max=1.8s p(90)=1.22s p(95)=1.39s
vus............................: min=348 max=1000
Summary
If we take http duration of each test to plot into the graph.
Rust faster than Go by the calculation below
- Rust Http Duration Average = 343.37 ms
- Go Http Duration Average = 444.07 ms
Rust is faster than go by 33 % for avg of 250, 500 and 100 vus