Cloud Functions, Cloud Runのスピンアップタイムの比較と考察
概要
Cloud Runを使ってるアプリケーションで初回アクセス時の高速化がどこまで出来そうかを知るために、Cloud RunとCloud Functionsのスピアップタイムを比較しました。
基本的にJavaで開発するのでメインの想定はGraal VMをネイティブイメージをCloud Runで実行する場合とCloud Functions with Javaですが、比較のためにGoなど他言語でのFunctionsも計測しました。
スクラップの使い方がまだ良く分かってないけど、考察で違う見解や別言語あるいは別構成でのテストの結果とかよければ追加してみてください。
比較結果
実行結果のサマリは以下の通り。
平均スピンアップ(sec) | 1回目 | 2回目 | |
---|---|---|---|
Cloud Run: Java (JVM) | 7.321 | 7.702 | 6.94 |
Cloud Run: Java (native-image) | 1.161 | 1.209 | 1.113 |
Functions: Java (JVM) | 2.0535 | 2.122 | 1.985 |
Functions: Go | 0.299 | 0.299 | 0.23 |
Functions: JavaScript | 0.295 | 0.308 | 0.282 |
Functions: .NET Core | 1.435 | 2.217 | 0.653 |
比較
構成
単純なHello Worldアプリケーション。Cloud Run版はQuarkusを使用。
計測方法
スピンアップの計測のため十分な時間をおいて2回計測。
スピンアップタイムの比較
Cloud Run: Java (JVM)
❯ time curl https://run-java-jvm-xxxx.run.app
Hello, Cloud Run!curl https://run-java-jvm-xxxx.run.app 0.02s user 0.00s system 0% cpu 7.702 total
❯ time curl https://run-java-jvm-xxxx.run.app
Hello, Cloud Run!curl https://run-java-jvm-xxxx.run.app 0.00s user 0.01s system 0% cpu 6.940 total
Cloud Run: Java (native-image)
❯ time curl https://run-java-native-xxxx.run.app
Hello, Cloud Run!curl https://run-java-native-xxxx.run.app 0.01s user 0.00s system 0% cpu 1.209 total
❯ time curl https://run-java-native-xxxx.run.app
Hello, Cloud Run!curl https://run-java-native-xxxx.run.app 0.01s user 0.01s system 1% cpu 1.113 total
Functions: Java (JVM)
❯ time curl https://xxxx.cloudfunctions.net/example-fn-java
Hello, Cloud Functions!curl 0.01s user 0.00s system 0% cpu 2.122 total
❯ time curl https://xxxx.cloudfunctions.net/example-fn-java
Hello, Cloud Functions!curl 0.01s user 0.00s system 0% cpu 1.985 total
Functions: Go
❯ time curl https://xxxx.cloudfunctions.net/fn-go
Hello, Go!curl https://xxxx.cloudfunctions.net/fn-go 0.00s user 0.01s system 3% cpu 0.368 total
❯ time curl https://xxxx.cloudfunctions.net/example-fn-go
Hello, Go!curl https://xxxx.cloudfunctions.net/fn-go 0.01s user 0.00s system 4% cpu 0.230 total
Functions: JavaScript
❯ time curl https://xxxx.cloudfunctions.net/fn-js
Hello JS!curl https://xxxx.cloudfunctions.net/fn-js 0.01s user 0.00s system 3% cpu 0.308 total
❯ time curl https://xxxx.cloudfunctions.net/example-fn-js
Hello JS!curl https://xxxx.cloudfunctions.net/fn-js 0.01s user 0.00s system 4% cpu 0.282 total
Functions: .NET Core
❯ time curl https://xxxx.cloudfunctions.net/fn-net
Hello .NET Core!curl https://xxxx.cloudfunctions.net/fn-net 0.01s user 0.00s system 0% cpu 2.217 total
❯ time curl https://xxxx.cloudfunctions.net/fn-net
Hello .NET Core!curl https://xxxx.cloudfunctions.net/fn-net 0.01s user 0.00s system 4% cpu 0.261 total
実行時間の比較
参考までにだけどスピンアップ後の実行時間の比較。どれも0.13ms - 0.14ms
と同じくらい。Hello Worldなので速度は言語レベルの差異はでずネットワークとか各種オーバーヘッドで固定でかかるレイテンシがこの結果だと思われる。
❯ time curl https://run-java-jvm-xxxx.run.app
Hello, Cloud Run!curl https://run-java-jvm-xxxx.run.app 0.01s user 0.00s system 7% cpu 0.141 total
❯ time curl https://run-java-native-xxxx.run.app
Hello, Cloud Run!curl https://run-java-native-xxxx.run.app 0.01s user 0.00s system 8% cpu 0.133 total
❯ time curl https://xxxx.cloudfunctions.net/example-fn-java
Hello, Cloud Functions!curl 0.00s user 0.01s system 7% cpu 0.152 total
❯ time curl https://xxxx.cloudfunctions.net/fn-go
Hello, Go!curl https://xxxx.cloudfunctions.net/fn-go 0.01s user 0.00s system 6% cpu 0.169 total
❯ time curl https://xxxx.cloudfunctions.net/fn-js
Hello JS!curl https://xxxx.cloudfunctions.net/fn-js 0.01s user 0.00s system 8% cpu 0.146 total
❯ time curl https://xxxx.cloudfunctions.net/fn-net
Hello .NET !curl https://xxxx.cloudfunctions.net/fn-ne 0.01s user 0.00s system 8% cpu 0.136 total
考察
分かり切ってた事だけどCloud Run: Java (JVM)が5sec
から7 sec
とダントツで遅い。これはJVMの起動が遅い事に加えてCloud Run自体のスピンアップタイムが影響していると思われる。しかしながらnative-imageであっても1 sec
前後ということでローカルでの直接実行と比較すると非常に遅い。
これはおそらくDocker系コンテナのオーバーヘッドが大きそう。実際ローカルで動かしても直接jarコマンドないしはネイティブ実行した場合は、Jarは一瞬ひっかかるのでおそらく0.5sec
から0.8sec
程度、ネイティブの場合は瞬時に起動するので0.3sec以下
くらいなのだが、dokcerで実行すると1 sec
から2 sec
程度かかる。これと同等以上のオーバーヘッドがCloud Runには存在している。ただし、jarとネイティブの起動速度の差がローカルで実行するよりDocker上で実行する方が圧倒的に大きいので工夫の余地はあるかもしれない。
また、BorgベースのためかFunctionsの場合はJVMであっても2 sec
程度とそれなりに速い。ただしNode.jsやGo言語などに至っては0.3 sec
前後と爆速なのでこの差は大きい。今回は試してないがGo言語をCloud Runで実行した場合はGraalVMのネイティブイメージ同様に1秒程度はかかるだろう。
このあたりのCloud RunとFunctionsのオーバーヘッドの差異は将来的なGCP側のチューニングで改善する可能性もある。
特に直接Dockerfileを使わずにBuildpacksで作られたイメージの場合は特別な実行のされ方でスピンアップを短縮してくるという将来的な可能性も無くはない。
が、現状としてこのオーバーヘッドを取り除きたければCloud RunをやめてFunctionsを使うか、無料枠をあきらめてmin-instances を使うしかない。
GAEは今回検証してないがおそらくFunctionsに準じた結果になるはず。
まとめ
とりあず自分が細々とやってる個人プロダクトの速度を改善出来ないかとというモチベーションで調べてみたけど、やはりDockerのオーバーヘッドは無視出来なさそう。マイクロサービス化はしてるので良く使うというか最初に使うAPIをFunctions + Goで実装してその裏側で次に使いそうなAPIをスピンアップすることで見かけ上の速度を上げるとかは出来そうかなー。まあ、そこまでやる? って話はあるけれど。Functionsにしなくてもnative-imageで1秒くらいになるなら言語やFWを変える手間を考えたら妥当な対応かも。
業務で使うようなものなら素直にmin-instancesの料金を払うのが一番良さそうかな。ざっくり月に1000円ちょっとだし。
Java, Ruby, Goのスピンアップタイムの比較
GraalVM
❯ time curl https://app-graal-xxx.run.app/healthcheck
{"message":"Hello World, Java","date":"2021-05-23T21:18:50"}curl https://app-graal-xxx.run.app/healthcheck 0.00s user 0.01s system 1% cpu 1.189 total
❯ time curl https://app-graal-xxx.run.app/healthcheck
{"message":"Hello World, Java","date":"2021-05-24T00:58:10"}curl https://app-graal-xxx.run.app/healthcheck 0.01s user 0.01s system 0% cpu 1.389 total
❯ time curl https://app-graal-xxx.run.app/healthcheck
{"message":"Hello World, Java","date":"2021-05-24T01:34:54"}curl https://app-graal-xxx.run.app/healthcheck 0.00s user 0.01s system 1% cpu 1.058 total
JVM
❯ time curl https://app-jvm-xxx.run.app/healthcheck
{"message":"Hello World, Java","date":"2021-05-23T21:19:56"}curl https://app-jvm-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 5.851 total
❯ time curl https://app-jvm-xxx.run.app/healthcheck
{"date":"2021-05-24T00:57:50","message":"Hello World, Java"}curl https://app-jvm-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 5.912 total
❯ time curl https://app-jvm-xxx.run.app/healthcheck
{"date":"2021-05-24T01:33:01","message":"Hello World, Java"}curl https://app-jvm-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 5.547 total
Ruby
❯ time curl https://app-ruby-xxx.run.app/healthcheck
{"message":"Hello World, Ruby","date":"2021-05-24T00:57:24"}curl https://app-ruby-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 1.612 total
❯ time curl https://app-ruby-xxx.run.app/healthcheck
{"message":"Hello World, Ruby","date":"2021-05-24T01:32:24"}curl https://app-ruby-xxx.run.app/healthcheck 0.01s user 0.01s system 0% cpu 1.516 total
❯ time curl https://app-ruby-xxx.run.app/healthcheck
{"message":"Hello World, Ruby","date":"2021-05-24T02:00:39"}curl https://app-ruby-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 1.297 total
❯ time curl https://app-ruby-xxx.run.app/healthcheck
{"message":"Hello World, Ruby","date":"2021-05-24T03:23:06"}curl https://app-ruby-xxx.run.app/healthcheck 0.01s user 0.00s system 0% cpu 1.497 total
Go
❯ time curl https://app-go-xxx.run.app/healthcheck
{"message":"Hello World, Go","date":"2021-05-24T00:00:00"}curl https://app-go-xxx.run.app/healthcheck 0.01s user 0.00s system 2% cpu 0.457 total
❯ time curl https://app-go-xxx.run.app/healthcheck
{"message":"Hello World, Go","date":"2021-05-24T00:00:00"}curl https://app-go-xxx.run.app/healthcheck 0.01s user 0.00s system 1% cpu 0.622 total
❯ time curl https://app-go-xxx.run.app/healthcheck
{"message":"Hello World, Go","date":"2021-05-24T00:00:00"}curl https://app-go-xxx.run.app/healthcheck 0.01s user 0.00s system 1% cpu 0.631 total