Cloud Run のコールドスタート時間を言語別に計測してみた(Python/Node/Go/C++/Rust)
March 23, 2024
GCP の Cloud Run のようなインスタンス毎の課金制 PaaS サービスをはじめ、Cloud Functions や AWS の Lambda といった FaaS の永遠の課題はコールドスタート問題ですね!
今回は、私が日頃からお世話になっている Cloud Run について、言語別にコールドスタート時間を計測してみました。
言語は私が日頃使っている or 今後使う可能性の高いものをチョイスしました。
- Python
- Node.js(JavaScript)
- Go
- C++
- Rust
TL;DR #
2024 年 3 月 13 日から 20 日までの1週間で計測したものです。各インスタンスに 30 分毎にアクセスし、そのたびのコールドスタート時間を示したグラフです。
なお Cloud Run インスタンスは以下の設定です。
- CPU:1
- メモリ:512MiB
- CPU ブースト機能:無効化
Python
最小 1 秒から最大 3 秒のレンジで推移。平均値で見ると、1.5 秒から 2 秒くらいかな。
Node.js
最小 2 秒くらいから最大 7 秒近くのレンジで推移。平均値で見ると、3 秒くらいかな。
Go
最小 0.05 秒(50ms)くらいから最大 0.25 秒(250ms)近くのレンジで推移。平均値で見ると、0.1 秒から 0.15 秒くらいかな。
C++
最小 0.05 秒(50ms)くらいから最大 0.3 秒(300ms)をわずかに超過くらいのレンジで推移。平均値で見ると、0.1 秒を少し下回るくらいかな。
Rust
最小 0.05 秒(50ms)くらいから最大 0.2 秒(200ms)に満たないくらいのレンジで推移。平均値で見ると、0.1 秒を少し下回るくらいかな。
結果まとめ #
目分量で計測しただいたいの平均値ではありますが、コールドスタートが速い順に並べると以下になりました。
- C++ と Rust:コールドスタート 0.1 秒以下
- Go:コールドスタート 0.1〜0.15 秒
- Python:コールドスタート 1.5〜2 秒
- Node.js:コールドスタート 3 秒
計測手順 #
準備したコードとデプロイ方法について #
Cloud Run のドキュメントページに各言語ごとのクイックスタート手順が示されています。
- Quickstarts | Cloud Run Documentation | Google Cloud
- https://cloud.google.com/run/docs/quickstarts
Python、Node.js、Go、C++ #
Python、Node.js、Go、C++についてはドキュメントに従って以下のコードをコピペで用意のうえ、gcloud run deploy
にて、ソースコードから直接 Cloud Run にデプロイしました。
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
"""Example Hello World route."""
name = os.environ.get("NAME", "World")
return f"Hello {name}!"
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
import express from 'express';
const app = express();
app.get('/', (req, res) => {
const name = process.env.NAME || 'World';
res.send(`Hello ${name}!`);
});
const port = parseInt(process.env.PORT) || 8080;
app.listen(port, () => {
console.log(`helloworld: listening on port ${port}`);
});
// Sample run-helloworld is a minimal Cloud Run service.
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
log.Print("starting server...")
http.HandleFunc("/", handler)
// Determine port for HTTP service.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
log.Printf("defaulting to port %s", port)
}
// Start HTTP server.
log.Printf("listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
name := os.Getenv("NAME")
if name == "" {
name = "World"
}
fmt.Fprintf(w, "Hello %s!\n", name)
}
#include <google/cloud/functions/framework.h>
#include <cstdlib>
namespace gcf = ::google::cloud::functions;
auto hello_world_http() {
return gcf::MakeFunction([](gcf::HttpRequest const& /*request*/) {
std::string greeting = "Hello ";
auto const* target = std::getenv("TARGET");
greeting += target == nullptr ? "World" : target;
greeting += "\n";
return gcf::HttpResponse{}
.set_header("Content-Type", "text/plain")
.set_payload(greeting);
});
}
int main(int argc, char* argv[]) {
return gcf::Run(argc, argv, hello_world_http());
}
Rust #
Cloud Run のドキュメントに Rust のサンプルは存在しなかったため、以下の記事の手順をそのまま利用させていただきました。
- Cloud Run で Rust の API Server を動かす | Zenn
- https://zenn.dev/kowaremonoid/articles/7e077f9eb4439b
use axum::{routing::get, Router};
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// initialize tracing
tracing_subscriber::fmt::init();
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/", get(root));
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
tracing::debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// basic handler that responds with a static string
async fn root() -> &'static str {
"Hello, World!"
}
Dockerfile
# Use the official Rust image.
# https://hub.docker.com/_/rust
FROM rust
# Copy local code to the container image.
WORKDIR /usr/src/app
COPY . .
# Install production dependencies and build a release artifact.
RUN cargo build --release
# Service must listen to $PORT environment variable.
# This default value facilitates local development.
ENV PORT 8080
# Run the web service on container startup.
ENTRYPOINT ["target/release/cloud-run-rust-test"]
アクセス方法について #
GCP の Cloud Scheduler を用いて、各言語のインスタンスに 30 分毎(*/30 * * * *
)に HTTP リクエストを飛ばすようにしました。
計測結果 #
記事冒頭の TL;DR に示した通りでした!C++と Rust は速い、そして Go も速い!