Based on this comparison of different programming language's size and performance on Google Cloud Run, I decided to do the same test with some more languages. So in this post it's Rust.
Demo Rust Service
For this test of microservice deployment size and speed, I created a pretty identical service to Richard's example.
Here's my employee.rs file with a DTO struct for the employee data. Source code is here.
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Employee {
pub Id: String,
pub FullName: String,
pub Location: String,
pub JobTitle: String,
}And here are two functions in main.rs that serve the employee data, and run the web service.
async fn employees(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
let v: Vec<employee::Employee> = vec![
employee::Employee {
Id: String::from("100"),
FullName: String::from("Jack Donaghy"),
JobTitle: String::from("Writer"),
Location: String::from("NYC"),
},
employee::Employee {
Id: String::from("101"),
FullName: String::from("Liz Lemon"),
JobTitle: String::from("Executive"),
Location: String::from("NYC"),
},
];
Ok(Response::new(serde_json::to_string(&v).unwrap().into()))
}Here's our main function with the listener configuration.
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(employees)) });
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}And that's it, after running cargo run locally this service spins up and answers requests for employee data on port 8080.
Build and deploy to Cloud Run
So now let's build a docker image and deploy to Cloud Run, see the file deploy_gcp.sh for details.
# Build and publish image to our cloud registry
gcloud builds submit --tag gcr.io/$1/rustservice
# Deploy image to Cloud Run and allow unauthenticated traffic to service
gcloud run deploy rustservice --image gcr.io/$1/rustservice /
--platform managed --project $1 --region us-central1 /
--allow-unauthenticatedNow we have our image published to our registry, at a pretty compact 10.9 MB using the Google's Distroless CC base image (extra compact just for running applications, the build is done using the full 1.6 GB Rust image).
We also already have our service deployed to cloud run, and can test the service with curl.
curl https://rustservice2-qtw3rvj3ya-uc.a.run.app | jqAnd which returns our test data.
[
{
"Id": "100",
"FullName": "Jack Donaghy",
"Location": "NYC",
"JobTitle": "Writer"
},
{
"Id": "101",
"FullName": "Liz Lemon",
"Location": "NYC",
"JobTitle": "Executive"
}
]Cold run
Now let's do the same performance test using hey that was done with the other frameworks.
hey -n 200 -c 10 https://rustservice2-qtw3rvj3ya-uc.a.run.appHere are the cold start results (calling from a VM running in the same us-central1 (Iowa) region).
Summary: Total: 0.8240 secs Slowest: 0.6409 secs Fastest: 0.0051 secs Average: 0.0271 secs Requests/sec: 242.7116
Total data: 30600 bytes Size/request: 153 bytes
Response time histogram: 0.005 [1] | 0.069 [189] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.132 [0] | 0.196 [0] | 0.259 [0] | 0.323 [2] | 0.387 [6] |■ 0.450 [0] | 0.514 [0] | 0.577 [0] | 0.641 [2] |
Latency distribution: 10% in 0.0060 secs 25% in 0.0064 secs 50% in 0.0072 secs 75% in 0.0087 secs 90% in 0.0127 secs 95% in 0.3021 secs 99% in 0.6339 secs
Details (average, fastest, slowest): DNS+dialup: 0.0012 secs, 0.0051 secs, 0.6409 secs DNS-lookup: 0.0004 secs, 0.0000 secs, 0.0078 secs req write: 0.0000 secs, 0.0000 secs, 0.0012 secs resp wait: 0.0258 secs, 0.0050 secs, 0.6152 secs resp read: 0.0001 secs, 0.0000 secs, 0.0062 secs
Status code distribution: [200] 200 responses
Hot run
Now running again with the hot instances resulted in these numbers.
Summary: Total: 0.1737 secs Slowest: 0.0300 secs Fastest: 0.0059 secs Average: 0.0084 secs Requests/sec: 1151.3049
Total data: 30600 bytes Size/request: 153 bytes
Response time histogram: 0.006 [1] | 0.008 [177] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.011 [11] |■■ 0.013 [1] | 0.016 [0] | 0.018 [0] | 0.020 [0] | 0.023 [0] | 0.025 [0] | 0.028 [0] | 0.030 [10] |■■
Latency distribution: 10% in 0.0064 secs 25% in 0.0068 secs 50% in 0.0073 secs 75% in 0.0078 secs 90% in 0.0083 secs 95% in 0.0282 secs 99% in 0.0291 secs
Details (average, fastest, slowest): DNS+dialup: 0.0010 secs, 0.0059 secs, 0.0300 secs DNS-lookup: 0.0002 secs, 0.0000 secs, 0.0045 secs req write: 0.0000 secs, 0.0000 secs, 0.0003 secs resp wait: 0.0072 secs, 0.0058 secs, 0.0107 secs resp read: 0.0000 secs, 0.0000 secs, 0.0006 secs
Status code distribution: [200] 200 responses
Results summary
Here's a table of the 95% responses based on runtime & client location.
Call type Service running locally Service running on Cloud Run us-central1
Cold start 0.0009 secs 0.3021 secs
Hot start 0.0008 secs 0.0282 secs
And the full table with Dotnet, Go, Node, Java and Rust, averaging both cold and hot start 95% percentile results.
Language 95th percentile
dotnet 1.01s
go 0.33s
nodejs 1.32s
java 5.97s
rust 0.17s
And of course at the end, it all depends on which platform you are comfortable with and can use most effectively. But some basic performance expectations are of course important to consider. In a next step I would like to add Bun to this test, which uses a new Zig-based runtime for javascript.