こんにちは、ソフトウェアエンジニアの宮後(miya10kei)です。
最近、年明けのフルマラソンに向けてランニングの日々を送っています🏃💨
2026年10月14日に開催された「Server-Side Kotlin Meetup vol.20」で登壇させていただきました。 今回も前回に引き続きセッション内容をスライドを抜粋しながら簡単に紹介します。
server-side-kotlin-meetup.connpass.com
ちなみに私以外の登壇者の方の発表も非常に面白かったです!
よければ #serverside_kotlin_meetupをご覧いただき、他の登壇者様の資料も見てみてください。
運営に携わっていただいたみなさんおよび、素敵な会場をご提供いただいたコドモンさまにこの場をお借りして感謝申し上げます。
セッション資料
セッション内容
「Kotlin + Quarkus を Lambda SnapStart で動かす - 仕組みと事前ウォームアップ」というタイトルで、Lambda SnapStart の仕組みと、Cold Start をさらに短くするための事前ウォームアップ(Priming)の実装について紹介しました。
背景
ニーリーでは外部SaaSからのWebhookを受けるAPIを「Kotlin + Quarkus on Lambda」で動かしています。 このAPIはリクエスト数が時間帯で大きく変動し、急激なスパイクも想定されます。また、通常のWeb APIとは負荷特性や可用性要件が異なるため、分離して低コストで運用したいという理由からLambdaを採用しました。
なお、Kotlin + Quarkusを選択した経緯については、前回のvol.19で弊社の野呂が紹介しているので、ご興味を持たれた方はこちらもご覧ください。 (テックブログもあります)
ついに決定!ニーリーの次期バックエンド開発言語はKotlin! - Nealle Developer's Blog
LambdaでJVMを動かす場合、課題になるのがCold Startです。 LambdaでKotlinを動かす時の選択肢としては On-Demand / Provisioned Concurrency / SnapStart / GraalVM Native がありますが、Cold Startによる遅延を抑えつつ低コストで運用できるSnapStartを選択しました。将来的にはGraalVM Nativeも視野に入れています。
SnapStart の仕組み
CRaC
SnapStartを理解する上で、まずはCRaC(Coordinated Restore at Checkpoint)について説明します。
CRaCはJVMの起動とウォームアップを短縮するOpenJDKのプロジェクトで、初期化済みのJVMプロセスのイメージを事前に作成し、そこから復元することで起動を高速化します。
アプリケーションは beforeCheckpoint()(保存直前)と afterRestore()(復元直後)のフックで処理を挟むことができます。
SnapStart
SnapStartはLambdaのCold Startを短縮するAWSの機能です。 バージョン発行時に初期化済みの実行環境(microVM)のスナップショットを作成し、Cold Startのたびにそこから復元することで起動を高速化します。
CRaC と SnapStart の関係
SnapStartのフックはCRaCのAPIで書くため、CRaCそのものを使っていると思われがちですが、SnapStartが採用しているのはCRaCのフックのAPI(org.crac)だけです。 保存と復元の仕組みはLambda独自で、CRaCがCRIUでJVMプロセスを保存するのに対し、SnapStartはFirecrackerでmicroVM全体を保存しています。 アプリケーションからはCRaCと同じAPIでフックを書けるのがポイントです。
事前ウォームアップ
Quarkus に任せられるところと任せられないところ
QuarkusのAWS Lambda拡張(quarkus-amazon-lambda)を入れると、SnapStart向けの最適化がデフォルトで有効になり、スナップショット作成時にクラスのプリロードがおこなわれます。
ただし、Quarkusが自動でプリロードするのは主にアプリケーション内のクラスで、リクエスト処理で利用するライブラリのクラスは自前でプリロードする必要があります。
Class Priming と Invoke Priming
自前でプリロードする手段としては次の2つがあります。
- Class Priming:
META-INF/quarkus-preload-classes.txtに対象クラスを列挙してプリロード対象にする - Invoke Priming:
beforeCheckpoint()で実際の処理を呼び出し、実行経路全体をプリロードする
Class Primingはクラス一覧の管理が必要で、ライブラリの更新にも追従しなければなりません。弊社ではライブラリ更新への追従を考慮してInvoke Primingを採用しました。
Invoke Priming の実装
実装はとてもシンプルで、CRaCの Resource を実装したBeanを作り、@PostConstruct でフックを登録するだけです。
@ApplicationScoped のBeanは利用されるまでインスタンスが作られないため、@Startup を付けて起動時にインスタンスを作るのがポイントです。
弊社では署名検証、設定データの読み込み、レスポンスの生成、DBの読み書き、外部HTTP送信といったリクエストが通る処理を、副作用が出ないようダミーの値でInvoke Primingの対象にしています。
Invoke Priming の副作用への対処
Invoke Primingには、実行時に開いたDBや外部サービスとの接続がスナップショットに含まれてしまうという副作用があります。 復元後の接続は状態が保証されないため、スナップショットに含めない工夫が必要です。
弊社では次のように対処しています。
- DB接続: コネクションプールの初期接続数・最小接続数を0にした上で、
beforeCheckpoint()で接続をフラッシュし、afterRestore()で接続を張り直す - HTTP接続: Priming用のクライアントのKeep Aliveを無効にする
まとめ
以上がセッション内容のサマリとなります。 Quarkusに限らず、JVMをLambda SnapStartで動かす場合には同じ考え方が使えると思うので、同じような構成を検討されている方の参考になれば幸いです。
また、ニーリーではServerside Kotlinだけではなくさまざま技術を使って開発を進めています! 一緒に働く仲間を絶賛募集中ですので、よろしければこちらもご覧ください!



























