サーバーレス

Lambda SnapStart を Micronaut に適用して起動速度を比較してみた

yassan

社内のレースゲーム大会の最終レースが目前に迫っており、なんとか準優勝でフィニッシュを決めたいやっさんでございます。 re:Invent 2022 は現地参加は出来ておりませんが、オンラインで基調講演など各種動画を視聴して楽しんでいます。

Lambda SnapStartが発表されました

re:Invent 2022ではLambda SnapStartが発表されました。Lambda SnapStartはLambdaのコードが含まれた仮想マシンのスナップショットを取得することにより、復元用スナップショットを起動時に利用することでコールドスタートにかかる時間が最大10倍短縮されます。

Lambda SnapStartの何が嬉しいの?

JavaにおけるLambdaの起動は基本的にパッケージングされたJarファイルを環境のJVM上で起動することになりますが、このJarファイルを読み込むのに数秒かかる、というのはよくある話でした。特にFat Jarと言われる多くのライブラリを詰め込んだJarファイルの場合にその起動時間の遅さが顕著に現れます。以前投稿したブログではMicronautフレームワークを利用したAWS Lambdaの起動でコールドスタート時間に4秒程かかっています。

Lambda SnapStartによって、このコールドスタートにかかる時間が大幅に短縮されることがとても嬉しいのです!「起動速度が遅いからLambdaでJavaはちょっと ... 」と二の足を踏んでいた方々、Lambda SnapStartで高速な起動を手に入れられる可能性が出てきました。

Lambda SnapStartを試す

早速、Lambda SnapStartを試しました。前提条件は以下の通りです。

項目 条件
ランタイム Java11(Amazon Corretto 11)
メモリ 320MB
フレームワーク Micronaut
Jarファイルのサイズ 約14MB

Lambda SnapStart適用前のコールドスタート時間の検証

まず、検証用に前回のブログ投稿と同じLambdaで同時実行数200、1000回のAPIアクセスを実行してみます。

Heyで以下のようなコマンドを指定して実行します。

hey -n 1000 -c 200 https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/Prod/ping

結果は以下のようになりました。

Response time histogram:
  0.024 [1] |
  0.921 [799]   |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  1.818 [0] |
  2.715 [0] |
  3.612 [0] |
  4.508 [0] |
  5.405 [0] |
  6.302 [1] |
  7.199 [196]   |■■■■■■■■■■
  8.096 [2] |
  8.993 [1] |

Latency distribution:
  10% in 0.0314 secs
  25% in 0.0377 secs
  50% in 0.0434 secs
  75% in 0.0547 secs
  90% in 6.8022 secs
  95% in 6.8935 secs
  99% in 7.0368 secs

E2Eで6秒以上かかっているLambdaが200件あり、これらがコールドスタートに引っかかっています。200件という数値はLambdaの同時実行数と一致しており、メトリクスからも確認できます。

CloudWatch Logs Insightで確認しましたところ、コールドスタートに平均3.7秒かかっていることが確認できました。

Lambda SnapStart適用後のリストア時間の検証

次に、Lambda SnapStartを適用してから適用前と条件でLambdaを同時実行させます。まずは、該当するLambdaにSnapStartを適用します。

AWS SAMのテンプレートを更新し、Lambda SnapStartを適用しました。その後、別のAPIとしてデプロイしました。AWS SAMのテンプレートファイルの差分は以下画像のようになっています。

デプロイに成功したら、Lambdaの情報を確認します。以下画像のようにSnapStartが適用されていることが確認できました。

それでは、早速負荷をかけてみます。SnapStart適用前と同じHeyのコマンドを実行し負荷をかけていきます。はたして結果はどうなったでしょうか ... 。以下のようになりました。

Response time histogram:
  0.025 [1] |
  0.520 [799]   |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  1.016 [0] |
  1.511 [0] |
  2.007 [0] |
  2.502 [0] |
  2.998 [0] |
  3.494 [9] |
  3.989 [6] |
  4.485 [38]    |■■
  4.980 [147]   |■■■■■■■

Latency distribution:
  10% in 0.0333 secs
  25% in 0.0390 secs
  50% in 0.0472 secs
  75% in 0.0662 secs
  90% in 4.6221 secs
  95% in 4.6611 secs
  99% in 4.7177 secs

Lambdaの起動は速くはなっています ... 速くはなっているのですが ... 腑に落ちないですね。そんなに速くはないですね。 CloudWatch Logs Insightでクエリしてみます。SnapStartによる復元にかかった時間は以下の通りでして、復元時間はものすごく早いです。

しかしながら、200リクエスト程、3~5秒程のレイテンシがE2Eで発生しています。ここが腑に落ちません。この200リクエストは、今回の性能検証におけるLambdaの同時実行数と一致します。

比較検証する

まずパーセンタイルのレイテンシを比較してみます

検証対象 10% 25% 50% 75% 90% 95% 99%
SnapStart 適用前 0.0314 0.0377 0.0434 0.0547 6.8022 6.8935 7.0368
SnapStart 適用後 0.0333 0.0390 0.0472 0.0662 4.6221 4.6611 4.7177

90%パーセンタイル以上で2.2秒以上も早い結果が出ています。

次にコールドスタートまたはリストアスタートの際のLambda関数の初回起動のDurationの比較をします。

検証対象 Init Restore Duration 合計(Init + Restore + Duration)
SnapStart 適用前 3788.24 - 2638.19 6426.43
SnapStart 適用後 - 287.48 2150.07 2437.55

この表からは、SnapStartを利用した復元でもJava Lambdaにおける仮想マシンの起動に時間がかかっているのではないかという洞察を得られます。同時実行数の200だけレイテンシが上昇しているところも関連性があると見てよさそうです。同時実行数についてはProvisioned Concurrencyを活用しつつも、予約した同時実行数を超える実行にはLambda SnapStartを使うという使い方もよいのではないかと考察します。

制約について(2022年12月7日時点)

Lambda SnapStartには(2022年12月7日時点で)いくつかの制約があります。Provisioned Concurrency、ARM64アーキテクチャ、Lambda拡張API、Amazon EFS、AWS X-Ray、512MB以上のエフェメラルストレージをサポートしていません。 特にX-Rayは分散システムのトレーシングを実現できるとても便利なAWSサービスですので、今後利用できることを期待したいと思います。また、ランタイムがJava11(Amazon Corretto 11)前提であり、Java11以外のランタイムでは(2022年12月7日時点では)利用できません。

まとめ

Lambda SnapStartによって、確かにLambdaの起動時間は速くなりました。 その差は2秒以上の差があります。しかしながら同時実行における仮想マシンの起動において、起動時間がミリ秒まで縮まることはなく、2秒程かかっています。 Graal VMによるネイティブイメージはビルドに要する時間が長い傾向にありますし、JavaにおけるLambdaのビルドの選択肢に銀の弾丸はないと言ったところでしょうか。今回はMicronautでAPI Gatewayまで含めて検証しましたが、Pure Javaによる実装など、よりスリムなJarを目指したLambdaであれば起動速度が変わる可能性があり、さらなる検証もしていきたいと思います。

以上です。

参考情報

AUTHOR
Yasuyuki Sato
Yasuyuki Sato
記事URLをコピーしました