コールドスタートを解決するLambda SnapStartにPythonが対応するも、色々と注意が必要な件。よく読んで、よく使おう！

# はじめに
AWS LambdaのPythonコーダーの皆さんに、朗報です！
2024年11月にコールドスタート対策の有効な手段である**Lambda SnapStartがPythonにも対応しました！**（.NETにも）

https://aws.amazon.com/jp/blogs/news/aws-lambda-snapstart-for-python-and-net-functions-is-now-generally-available/

今回は**SnapStartの基本的な機能の復習**と、**SnapStart利用時の注意点**についてまとめます。

:::note warn
本記事ではPython版のみ扱います。.NET版については割愛いたします。
:::


# SnapStart復習
それでは、SnapStartの概要とそもそもSnapStartが登場した背景をご紹介します。

## SnapStart以前(～2019年) ～コールドスタートとの戦い～
2014年にリリースされたサーバーレスコンピューティングサービスである**AWS Lambda**ですが、当初からコールドスタートという問題がありました。

コールドスタートとは、Lambdaサービスが**実行環境の準備**、**コードのセットアップ**のセットアップに時間がかかってしまう問題を指します。
上記のコールドスタート期間に対して課金はされませんが、読み込みモジュールに依っては数秒オーダーで発生する場合もあるため、より短いレイテンシーを求める処理では課題となります。

また、コールドスタートはLambda実行に必ず発生するわけではなく、実行環境が保持されている期間内の実行であれば発生しません（これをウォームスタートと呼ぶ）。
そのため、比較的呼び出し頻度が少ないLambda関数の実行時、かつ低レイテンシーの要求がある場合に、問題となります。

:::note warn
上記の説明はざっくりと理解してもらうための説明であり、一部正確でない可能性もあります。Lambdaコールドスタートの正確な理解は、以下のAWSブログをご覧ください。
:::
https://aws.amazon.com/jp/blogs/news/operating-lambda-performance-optimization-part-1/

## SnapStart以前のコールドスタートの解決手段 ～2019年Provisioned Concurrencyの登場～
このコールドスタートを解決するための手段として、re:Invent2019で**Provisioned Concurrency**が発表されました。

Provisioned Concurrencyを設定することで、関数は実行前にウォームアップさせておくことができ、2桁ミリ秒で応答できるようになります。

https://aws.amazon.com/jp/blogs/compute/new-for-aws-lambda-predictable-start-up-times-with-provisioned-concurrency/

ただし、Provisioned Concurrencyは同時実行数を事前に設定する必要があり、また設定した同時実行数分だけ利用コストがかかることになります。
そのため、全てのLambdaでProvisioned Concurrencyを使うわけではなく、低レイテンシーが求められるLambda関数などに限定し、かつ実行数を予測したうえで値を設定する必要がありました。


## 新たなる手段の登場 ～2022年SnapStartの登場～
SanpStartはre:Invent2022で発表された機能であり、Provisioned Concurrencyを設定することなく、Lambda関数の起動パフォーマンスを大幅に高速化する内容でした。

<font color="red">**しかも無料で！**</font>

ちなみにSnapStartでは、最初に初期化されたメモリの状態のスナップショットを作成しキャッシュ化しておくことで、低レイテンシーを実現しています。

またProvisioned Concurrencyのように同時実行数を設定する必要がなく、**対象のLambda関数でSnapStartを有効にするだけ**という非常に簡単な設定でできるのも大きな未了でした。

https://aws.amazon.com/jp/blogs/news/reducing-java-cold-starts-on-aws-lambda-functions-with-snapstart/

ただし対象言語はJava11と非常に限定された言語であり、その恩恵を受けるユーザーはまだ限定されていました。


## ついに出た！SnapStartの対応言語拡大！ ～2024年Python対応～
そしてついに待望の他言語の対応が、re:Invent2024直前に発表されました！
今回はPythonと.NETということで、Pythonユーザーには非常に嬉しいニュースとなりました。


# Python版SnapStartは注意点が多い！？
ここまで読んで<b>「じゃあ早速すべてのPythonベースのLambda関数でSnapStartを有効化しよう！</b>と思ったそこのあなた・・・ちょっと待ちましょう。

Python版SnapStartは以下注意点が必要です。

* ①Python版は追加料金が発生する
* ②Pythonランタイムのバージョンが3.12以降
* ③Lambdaをバージョニングする
* ④モジュールや関数コードが非常に軽い場合、恩恵はほぼない


## ①Python版は追加料金が発生する
上でJavaでは無料と書きましたが、Python版では料金が発生します。
料金は以下を参照していただきたいですが、ざっくりとは以下のケースで発生します。

* キャッシュ
  * 最初に作るスナップショットのキャッシュ化にかかるコスト
  * SnapStartを有効化したLambdaのバージョン毎に課金が発生
  * <b><font color="red">最低3時間の課金が発生し、以後は1秒毎に課金</font></b>（ここがかなり怖い）
  * 東京リージョン：```$0.0000015046 per GB-second```
* リストア/復元
    * スナップショットからLambda実行環境を復元されるたびに課金
    * 復元回数とメモリ割り当てにのみコストに影響
    * 東京リージョン：```$0.0001397998 for every GB restored```

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/snapstart.html#snapstart-pricing

> SnapStart で設定された関数の場合、Lambda は実行環境を定期的にリサイクルし、初期化コードを再実行します。耐障害性のため、Lambda はスナップショットを複数のリージョンに作成します。Lambda が別のリージョンで初期化コードを再実行すると、そのたびに料金が発生します。

特にキャッシュの料金に関しては注意が必要で、<font color="red">最低3時間の課金が発生し、以後は1秒毎に課金</font>とのことで、全Lambdaやメモリ割り当てが大きいLambdaには使いづらいな―という印象です。

:::note warn
特に未使用となった関数バージョンでもSnapStartの設定がそのままだと、キャッシュへの課金が発生するため注意が必要です。この辺りのLambdaバージョン管理もしっかりする必要がありそうです。
:::


## ②Pythonランタイムのバージョンが3.12以降
こちらはまだPythonランタイムのバージョンを上げていない方は、注意が必要です。
2024年12月現在ではPython 3.9まではサポートされていますが、早めに3.12以降に上げておきましょう。
https://devguide.python.org/versions/


## ③Lambdaをバージョニングする
SnapStartを利用するためには、Lambdaのバージョン設定が必要です。
```$LATEST```で運用されている方は、SnapStartが適用できませんので、ご注意ください。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-versions.html


## ④モジュールや関数コードが非常に軽い場合、恩恵はほぼない
繰り返しになりますが、SnapStartでは最初に初期化状態をスナップショットとして作成してキャッシュ化しておきます。
そのため、初期化状態のメモリ容量が小さければその恩恵はありません。

以下は非常に軽いLambdaコードをSnapStart有無で実行した場合のLambdaログの比較です。

:::note info
SnapStartしない通常では、コールドスタート時の初期化処理は**Init Duration**と表示されます。
SnapStartのウォームスタート時は**Init Duration**のログはなく、代わりに**Restore Dutration**が表示されます。
:::
https://docs.aws.amazon.com/lambda/latest/dg/snapstart-monitoring.html

SnapStartなし↓
```
REPORT RequestId: XXXXX    Duration: 36.88 ms    Billed Duration: 37 ms    Memory Size: xxxx MB    Max Memory Used: 94 MB    Init Duration: 898.06 ms
```

SnapStartあり↓
```
REPORT RequestId: XXXXX    Duration: 16.29 ms    Billed Duration: 64 ms    Memory Size: xxxx MB    Max Memory Used: 83 MB    Restore Duration: 558.86 ms    Billed Restore Duration: 47 ms
```

Duration自体はほとんど差がなかったとしても、**Init Duration**と**Restore Duration**は比較する必要があります。
<b><font color="red">Init Duration > Restore Duration</font></b>となるならSnapStartを選択する価値が出てきます。

なお、Provisioned Concurrencyが2桁ミリ秒で応答するのに対し、**復元/リストアの方が1秒未満と時間がかかる**ようなので、これ以上の効果があればSnapStartの価値が出て来そうです。


## （個人的な）⑤デプロイ資材が少し複雑になる？
これはバージョン管理をしていない場合だと、バージョン管理やエイリアス設定もIaC資材に追記する必要があります。

またSnapStartのIaC対応はサービスによりまちまちな感じです。

Serverless Frameworkの場合、```snapStart: true```だけで、バージョン管理/エイリアス設定、エイリアス呼び出し化は自動化してくれそうです。
https://developer.mamezou-tech.com/blogs/2022/12/21/lambda-snapstart-serverless-framework/

CDKでも```v2.53.0```からはL2コンストラクトで使えるようで、それ以前であっても```addPropertyOverride```を使えば行けそうです。
https://github.com/aws/aws-cdk/issues/23153

:::note info
この辺りの情報はまだ調べ切れていないので、コメントいただけると嬉しいです！
:::


# 最後に
LambdaのSnapStartにPythonがついに対応ということで歓喜してしまいましたが、コスト含めて注意点は多いです。
ですが、今後のさらなるアップデートでより使いやすくなることにも期待したいです。