[セッションレポート]Java on Lambda のコールドスタートを乗り越える、これからのサーバーレスアプリケーション(AWS-26) #AWSSummit

2023.04.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。AWS事業本部の木村です。

今回は 2023 年 4 月 20 - 21 日にわたって開催された AWS Summit Tokyo 2023 のセッションレポートです。

セッション概要

AWS Lambda は 2014 年に誕生して以来、様々なランタイムをサポートをし、多様なワークロードで利用されています。Java もクラウドに限らず様々な環境で活用をされているプログラミング言語です。一方、これまで Lambda と Java はサーバーレスの特性と言語特性にギャップがあり、起動速度が長い等の課題から活用をすることが難しい場合がありました。ですが今回はその課題を克服し、サーバーレス環境でどのように Java を活用するのか、その方法について解説を行います。

スピーカー

アマゾン ウェブ サービス ジャパン合同会社

技術統括本部
西日本ソリューション部
ソリューションアーキテクト
ポール (岡田 信夫)氏

セッション視聴

5/22からAWS Summit Tokyoの登録を行うことでオンデマンドで視聴が可能になりました!(現地参加された方は改めての登録は不要です。)

https://aws.amazon.com/jp/summits/tokyo/

登録済みの場合、以下から直接遷移できます。

https://jpsummit.awsevents.com/public/session/view/545

レポート

アジェンダ

1.サーバレスとJavaのこれまで

2.Java on Lambdaでのアプローチ

3.まとめ

1.サーバレスとJavaのこれまで

  • 以前までのJava実行環境に関して
    • 実行環境としてサーバーやコンテナが採用されることが多かった
    • サーバレスとはあまり相性が良くなく採用されることが少なかった
  • LambdaでJavaを実行する際の課題
    • LambdaとJavaを組み合わせることによって、それぞれの特徴によって弊害があった
    • Lambdaのライフサイクル
      • コールドスタートの際、コンテナ生成、パッケージロード、パッケージ展開、ランタイム起動・初期化といったフローが必要である
    • Java(JVM)の起動時のプロセス
      • JVM起動する際にJVMを起動しクラスを読み込む、インタープリタ実行、実行時にJITコンパイルといったフローが必要である
    • Lambdaはライフサイクが短くイベントに応じた処理を実行毎に必要であったため、長い時間のかかるJava起動時のプロセスと相性が良くなかった
      • 逆にこの課題さえ解消することができれば、Javaを実行する際にサーバレスを採用することができる  

2.Java on Lambdaでのアプローチ

アプローチとはしては2つある

  • 起動時間の短縮 必須

  • 既存の資産の活用 オプション

まずは起動速度を短縮してサーバーレスでもJavaを活用
さらに要件に合わせて組み合わせていくアプローチが必要
実行環境が改善したことでサーバーレスを選択肢の一つとすることができるようになってきた

起動時間の短縮

短縮のアプローチとしては3種類ある

紹介するこれらは組み合わせて利用することもできる

AWS Lambda SnapStartの利用

  • AWS Lambda SnapStart
    • 去年リリースされたサービス
    • Lambda+Javaでのコールドスタートを大幅に短縮
      • JVMの起動、クラスの読み込み、インタープリタ実行の一部を事前に終わらせておくことができる
    • Lambda関数のコールドスタート時のレイテンシを大幅に短縮
    • 対象はJava11のCorrettoランタイム
    • 追加料金はなし
    • SAMやCDKにも対応済み
  • ランタイムフック
    • スナップショット取得前後で実行できる
  • 安全なSnapStartを心がける
    • スナップショット取得での停止/再開を考慮する必要がある
    • 主に考慮するべき点は3つ
      • NWの切断/再開
      • エフェメラルストレージ
      • ユニーク/ランダムな値の生成処理
  • NWの切断/再開時に考慮するべき点
    • スナップショット取得後にコネクションがないためエラーが発生するケースがある
    • ランタイムフックを利用して、コネクションの処理をスナップショット作成後に実行するなどの考慮を行う
  • エフェメラルストレージの考慮するべき点
    • AWS Secret Managerなどから取得したエフェメラルストレージに保存された情報が古くエラーが発生するケースがある
    • ランタイムフックを利用して、更新するようをスナップショット作成後に実行するなどの考慮を行う
  • ユニーク/ランダムな値の生成処理を行う際の考慮すべき点
    • スナップショットを撮る前にユニークなIDを作ってしまうと同じものを使い回してしまう
    • ユニークな値を生成する際の考慮
      • 初期フェーズでユニークIDの生成などを行わない
      • 関数ハンドラー内、もしくはRuntime-hookを使ってスナップショットから起動時に生成する
    • ランダムな値を生成する際の考慮
      • java.security.SecureRandom等を使ってSnapShotの影響を受けない乱数を生成する
  • SnapStartのその他注意点
    • 現在はJava11のマネージドランタイムのみ対応
    • 利用にはバージョンとエイリアスを有効化する必要がある
    • その他非対応な機能
      • Arm64アーキテクチャ
      • X-Ray
      • EFS
      • Lambda Extensions API
      • Provisioned Concurrency
      • 512MBを超えるエフェメラルストレージ
    • SnapStartの運用が難しい場合
      • Provisioned Concurrencyを利用してコールドスタートを回避する
      • ただし利用に際しては別途料金が必要になる

階層型コンパイルの指定

  • Java8以降ではプロファイリングを行い適切なコンパイルレベルでJITコンパイルが行われる
    • 但しプロファイリングや最適化にはオーバーヘッドがある
  • サーバーなどで実行されていてライフサイクルが長い場合、実行されている時間が長くなるとパフォーマンスが特定のラインまで向上し続ける
  • ただしLambdaではライフサイクルが短いため、パフォーマンスの向上途上でライフサイクルが終了してしまう
    • 無駄なオーバーヘッドだけ発生している
  • JITの階層型コンパイルのレベルを1に指定する
    • 環境変数にJVMのオプションを指定する
      JAVA_TOOL_OPTIONS="-XX:+TieredCompilation -XX:TiredStopAtLevel=1"
    • 初期所要時間が11.378から6.725になり、40%削減することができる

GraalVM Native Compile/AOT Compile

  • あらかじめネイティブコードにコンパイルしておくことでJVM起動時の時間を解決する
  • JVMとNative Imageの比較
    • JVM
      • JITコンパイル
      • マネージドランタイムでのデプロイ
      • 動的な言語機能
      • 起動速度が遅い
    • Native Image
      • AOTコンパイル
      • カスタムランタイムでのデプロイ
      • クローズドワールドモデル、ダイナミック機能なし
      • 起動速度が遅い

既存資産の活用

  • 既存のナレッジやアプリケーションの資産を活かして効率的に開発を進める
  • WebフレームワークをLambdaで動かす
    • アプリケーションの初期化処理を伴うフレームワークは起動がさらに遅くなってしまうため今まで採用が難しかった
    • 初期化後にスナップショットを撮ることができるライフサイクルを活用すれば既存のアプリケーションのサーバーレス化も一つの選択肢になるようなるかもしれない
    • これまでの開発体験のままサーバーレスを活用することも可能になるかもしれない
    • 但しあくまで動かせるという選択肢であった推奨ではないという点は気に留めておいていただきたい
  • AWS Lambda Web Adapter
    • LambdaでWebアプリを実行するためのOSSツール
  • AWS Lambda Web Adapterのメリット
    • 開発者がAdapterを意識する必要がない
      • サイドカーとして存在するためライブラリのインストールが不要
      • 開発者が開発するためにAdapterの理解が不要
      • 異言語やランタイムに依存しないので、Java以外でも活用できる
    • SnapStartと組み合わせた場合
      • 通常は10秒のinit timeの上限が長くなるのでアプリケーションの初期化処理が大きなアプリケーションもLambdaで動かすことが可能になる
  • WebフレームワークとLambdaの親和性
    • Lambdaは、リクエストごとに独立した実行環境が与えられ強固なIsolationを持っている
    • Webフレームワークは、マルチプロセスやマルチスレッド、あるいはイベントループによってリクエストを受け付ける仕組みを持っている
    • WebフレームワークはLambdaの実行モデルを考慮したデザインになっているわけではないことに注意する必要があり、相性があうわけではないことも多い
  • FWを利用する場合の注意事項に関しては以下参照

3.まとめ

  • Java+サーバーレスを活用するプラットフォームが整った
  • SnapStart等で起動時間の短縮やサーバーレス環境向けの最適化を行う
  • SnapStartではスナップショット取得/再開時の振る舞いを意識して安全な実装を考慮する点が重要
  • Lambda Web Adapterを活用してWebフレームワーク等の既存資産の活用も選択肢の一つ

感想

初めてのオフラインセッションに参加したということもあり、非常に興奮気味でお話しを伺っておりました。
課題とそれに対する解決方法が分かりやすく、アプリの開発経験が少ない私でもスッとお話しが頭に入ってきました。

1年ちょっと前に初めて業務でAWSを利用したのが、LambdaでのJava実装であり初めてぶつかった大きな壁とういうこともあり非常に感慨深かったです。Provisioned Concurrencyも検討したのですが、料金がネックとなり規模も小さかったことからPythonでの実装に変更となってしまいました。 もし今だったら・・・と考えるとAWSの環境が日々改善されているというのが非常に身にしみたセッションでした。

この記事がどなたかの参考になれば幸いです。

以上、AWS事業本部の木村でした。