SpringBootでLambda SnapStartを試してみた

TL;DR

  • SnapStartを使うとLambdaがコールドスタートの場合の起動時間を短縮できる
  • LambdaでSpringBootを抱えた場合でもコールドスタートで1000ms以内に返せる(かも)

経緯

SpringBootをサーバーレスで実行するには、ECS+Fargateのようなコンテナ構成をよく利用している。 (ECSでサービス+ELBを立てて、warm状態でスタンバイしておき、requestを待ち受ける構成)

この場合、fargateが時間課金であるためにリクエストがない場合でも待機コストがかかる。

これをLambdaにすることでpay as you goスタイルにできるのだが、 Lambdaの場合はリクエストがないと一定時間経過後にtimeoutし、次のリクエストまではコールドスタートとなり レイテンシーが上がってしまう。

特にJavaは起動時のレイテンシーが高く、これがネックで採用を見送っていた。 (Lambdaのprovisionedを使えばレイテンシーは解決できるが、時間課金になってしまう)

これが re:Invent 2022で発表されたLambda SnapStartにより短縮できるとのことなのでやってみた

手順

サンプルリポジトリ を作った。

ベースは aws公式のリポジトリ

VSCodeのdevcontainerで開き、samを使ってデプロイできるようにしてある

環境

  • Java21
  • SpringBoot3
  • Gradle8.5
  • AWS sam 1.113.0

SnapStart制限

  • 2024/3時点でSnapStartはjava11以降、かつjavaのmanaged runtimeのみサポート
  • arm64は未サポート
  • EFS未サポート
  • storageは512MBまで

SnapStartを有効にするには

以下を満たすことでSnapStartが自動的に配備される ソースコード上の依存ライブラリ追加は不要

  • Lambdaの設定で SnapStartの項目に PublishedVersions を設定
  • Lambdaの新しいversionを発行する

samを使う場合、 template.yml に以下の記述を追加することで対応できる

 1Resources:
 2  SnapStartDemoFunction:
 3    Type: AWS::Serverless::Function
 4    Properties:
 5      Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest
 6      Runtime: java21
 7      CodeUri: .
 8      MemorySize: 128
 9      Policies: AWSLambdaBasicExecutionRole
10      Timeout: 60
11      # ここと
12      SnapStart:
13        ApplyOn: PublishedVersions
14      # ここ
15      AutoPublishAlias: live
16      Events:
17        HttpApiEvent:
18          Type: HttpApi
19          Properties:
20            Path: /{proxy+}
21            Method: any
22            TimeoutInMillis: 20000
23            PayloadFormatVersion: '1.0'

ソースコードでの対応

公式サンプル にあるようにAPI Gateway配下でSpringBootを動かすのに SpringBootLambdaContainerHandler が便利なので利用する。

これを使うことでrequestとresponseをLambdaのプロキシ統合に自動的に合わせてくれる。 (spring側は通常どおりrequest/responseを操作できる)

また、公式docのPerformance tuning にある通り、SnapStartのより効かせるために、対象APIのダミーリクエストを初期化コードとして埋め込んだ。

これによりSnapShotの中でAPIで必要なclassも読み込まれている状態でrestoreされるので、より起動が早くなるらしい。

実行結果

  • SnapStartなし、coldスタート:973ms1028ms
  • SnapStartあり、coldスタート、preloadの加工あり:515ms633ms

これだけだと言うほどあまり早くなっていないが、preloadされるclassが多くなればなるほどSnapStartの効果が効いてくるものと思われる。

その他

SnapStartでresoreされた状態はDBコネクション等は当然接続しなおしが必要となる。 その他、hostname、ポート、DNSキャッシュあたりの振る舞いについては確認が必要

また、SnapStartとは直接関係ないがLambdaでSpringを抱えるうえでパフォーマンス改善につながる要素は対応を検討