🤖

AWS Lambda SnapStart for Java functionsを試してみる(2022/12/18 Update Ver.)

2022/12/01に公開

AWS LambdaとServerless Advent Calendar 2022 12/18分の記事です。

re:InventのEXPOにあるAWSのServerless&Containerのコーナーで、全然会話できなくて、I love Lambdaと言って帰ってきたダメな人です。


さて、2022/11/28(現地時間)のMonday Night Liveで登場したLambda SnapStartを早速試してみました。
実は昔はJavaな人だったので(今回コード全く書いてないけど)

Lambda SnapStartとは

Lambda SnapStartについてはこちらをご覧ください。(Now English Only)

https://aws.amazon.com/jp/blogs/aws/new-accelerate-your-lambda-functions-with-lambda-snapstart/

Javaのコールドスタートが遅い(ウォームスタートは全ランタイム中最速レベルのはず)問題は、Javaランタイム登場当初からずっとあった話で、その解決となる1つの手段なのではないかと思います。
レイテンシー重視の場合は、Provisioned Concurrencyを利用せよと書かれています。
※Provisioned ConcurrencyではLambda SnapStartは使えないとのこと。

やってみた

とりあえず、アナウンスのJeff Barr氏のブログと同じく、Githubにある「Serverless Spring Boot 2 example」を使ってJavaのアプリをデプロイします。
Java11(Corretto)、SAM CLI、Gradle等が入っていなかったので、この時点で新規インストールします。(現地時間11/29 6時半ぐらい)

設定

とりあえず、設定なしで実行し、問題ないことを確認したら、
ブログ記事にを参考に、SnapStartの設定を行います。

そして、ビルド&デプロイを実行・・・。こけました。

SnapStartってパラメータないよ!?

ここで、SAM CLIのバージョンおよびReleaseを見てみると、11/29のSnapStart対応のSAM CLIが出ており(v1.66.0)、インストールしてあったのは、v1.65.0だったので、絶妙なタイミングで旧バージョンをダウンロードしていたようでした。
ということでSAMのアップデートを実施したところ、このエラーは回避しました。

気を取り直して、最新バージョンのSAM CLIで実行し、SnapStartの設定を行います。
設定後のコンソールはこんな感じになりました。

ちなみに、こちらは設定なしの場合

そして、APIあるので、叩いてLambdaをInvokeしてみます。
1回目。めっちゃ時間かかる・・・。6秒かかってます。
初回は仕方ない(と思っている)ので、ちょっと直して、デプロイして、
再度LambdaをInvokeしてみますが、何度やっても6秒・・・。

これは何か失敗しているなと、
ドキュメントを眺めたり、
セッションがあったので、受講してみたところ、バージョンの作成が必要であることがわかりました。
I rebuilt and redeployed, published a fresh version of the function to set up SnapStart, and ran another test: って書いてありました。OMG!!!

バージョンを作成し、いざInvoke

本来はSAMで頑張るべきところでしょうが、とりあえずコンソール上で、バージョンを指定します。
バージョン作成画面にもSnapStartに関する説明が書いてありました。

そして、作成。
バージョン指定の設定画面では以下のような表示になります。

すると、大量のLogStreamが作成(事実上のコンテナ起動)されました。Provisioned Concurrencyみたいな挙動を感じます。 

中身を見てみると、init処理が実行されていました。

そして、テストで設定ありのバージョンのLambdaをInvokeしてみると、実行速い!
ログを見ますと、init処理がなく、RESTORE 〜 という2行が出力されており、217msで起動していることがわかります。6s -> 217msは確かに速い。

という感じで、ハマりましたが、動作の確認までできました。うーん。これはすごい

まとめ

  • JavaかつCorretto限定だけど、これは熱いアップデートなのでは?
    • これ他のランタイムにもこないかな?
  • バージョン指定忘れずに。
  • 現状、Graviton2(arm64)では使えない注意。
    • 間違えてarm64で実行した人です。
  • とはいえ、レイテンシー重視の場合は、Provisioned Concurrencyを利用せよと書かれています。※Provisioned ConcurrencyではLambda SnapStartは使えないとのこと。(再掲)
  • 次はAWS CDKでやってみようかな(最近はCDKを多用している人です)
  • もう1つ、Monday Night LiveでLambdaのCasheの話が出てきたと思うんだけど、そういえば全然話に出てこない・・・。確かにCacheと書いてあるので、これなのかなぁ・・・。
  • このSnapshot Cacheは14日間は残り続けるらしいので、その間はinitなしで実行される
    • 15日後にどうなるか試してみたいところ。忘れなかったら追記します。

2022/12/18 Update

15日過ぎたので、どうなっているか、確認してみました。
定期的に、ログが出ているので、コンテナの再作成しているみたいな挙動しています。

15日過ぎ去った2022/12/18、再度テスト実行しよう(その間全く触らずにおりました)と思って、最新バージョンのテストページへ・・・。

え、テスト実行できない???
SnapStart用のCache削除=バージョンの失効?的な挙動なんでしょうか?
ちょっと詳細調べてみたいと思います。
バージョン指定しないで実行すれば、普通に起動に6sかかります。

気を取り直して、ちょっと修正して、再度BuildおよびDeployを実施します。
が、バージョン作成時(厳密にはSnapStart用のCache(コンテナ)起動時)にエラーが出る。

Cloudwatch Logsを見ると、こんな感じのエラーが。

Error loading class com.amazonaws.serverless.sample.springboot2.StreamLambdaHandler: com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0: java.lang.UnsupportedClassVersionError
java.lang.UnsupportedClassVersionError: com/amazonaws/serverless/sample/springboot2/StreamLambdaHandler has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(Unknown Source)
	at java.base/java.security.SecureClassLoader.defineClass(Unknown Source)
	at java.base/java.net.URLClassLoader.defineClass(Unknown Source)
	at java.base/java.net.URLClassLoader$1.run(Unknown Source)
	at java.base/java.net.URLClassLoader$1.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.net.URLClassLoader.findClass(Unknown Source)

・・・OSのバージョンアップしたから?
とりあえず、調べたところ、以下のような解決策が載っていたので、
https://stackoverflow.com/questions/61810193/how-to-specify-jvm-to-gradle-when-using-aws-lambda

build.gradleに以下を追加し、build&デプロイ後、バージョンの作成を行ったところ、正常に新規バージョンの作成が完了しました。

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(11))
    }
}

そして、バージョンのテスト実行画面にいくと、実行できる!

そして、実行すれば、restore処理234.28 msで起動してくれるのでした。

その後、テスト実行できなかったバージョンの画面にいくと、こんな感じの通知が。

なるほど、復元しないとダメなのね?ということで、押してみます。
なんかエラー出た。
Capacity Overらしい。これが一時的なものかがわからないので、別日に実行してみます。年末だから、足りてないかもなぁ・・・。

アップデート内容のまとめ

  • 実行後、15日放置するとアイドル状態なり、テスト実行すらできなくなる。
  • (おそらく)復元で復活できる(別?のエラーが出たので、これは継続調査)
  • 新しいバージョンを発行すれば、無論そのバージョンではSnapStartでCacheできる(当たり前といえば当たり前)

なんか中途半端なアップデートの記載になりましたが、以上です。

Discussion