AWS Lambdaのコールドスタートとウォームスタートの挙動

::: note
この記事はMIERUNEの朝LT会で発表した内容を公開しているものです
:::


# はじめに

https://aws.amazon.com/jp/builders-flash/202402/lambda-container-runtime/?awsf.filter-name=*all

- AWS Lambdaでコンテナを動かす記事が公開されました。いつもLambdaを深堀しています

# Lambda＝サーバーレス

- Lambdaはサーバーレス実行環境です。基本的に、処理の実行時にサーバーリソースが充当されます
    - ＝関数を実行しない限りは、サーバーリソースを必要としない
    - このサーバーリソースの充当を行うケースを「コールドスタート」と呼びます。ある程度の時間がかかります（数秒はザラにかかる）
- ただし、一度コールドスタートしていると、二回目の関数実行時にはコールドスタートは必要ありません（＝ウォームスタート）
    - ウォームスタートの有効時間は明言されていない

# ウォームスタート時の挙動

- ウォームスタート時は：
    - 事前の関数実行時に定義済みのグローバル変数を参照できる
    - 書き込み済みのエフェメラルストレージを参照できる
- もちろん一定時間が経つとこの「インスタンス」は終了して、一時的な状態は失われる
- これはつまり、変なことをすると「他のリクエストの情報を利用する」関数を書けてしまうということ
    - 別の実行時に保存したグローバル変数を参照してしまうとか…

# 挙動を確認してみる

- クエリパラメータをグローバル変数に保存する関数をデプロイしてみた
    - https://<関数URL>.lambda-url.ap-northeast-1.on.aws/?message=hoge
    - ウォームスタート時は、直前のリクエストのクエリパラメータを参照・レスポンスする関数

```jsx
let someGlobalValue = null;

export const handler = async (event) => {
  
  const response = someGlobalValue === null ? {
    statusCode: 200,
    body: 'coldstart',
  } : {
    statusCode: 200,
    body: 'warmstart, message=' + someGlobalValue,
  };
  
  someGlobalValue = event.queryStringParameters.message;
  
  return response;
};
```

```sh
curl https://<関数URL>.lambda-url.ap-northeast-1.on.aws/?message=hoge
# coldstart
curl https://<関数URL>.lambda-url.ap-northeast-1.on.aws/?message=fuga
# warmstart, message=hoge
```

- ウォームスタート時には、**直前のリクエストで保存したグローバル変数を参照出来ていることがわかる**
    - 極端な例（秘匿したい情報を渡しているケースが生じうる）：https://<関数URL>.lambda-url.ap-northeast-1.on.aws/?message=credential
- 一方、Lambdaはひとつのリクエスト・レスポンスが、単一のインスタンスを占有する（＝isolation）
    - [https://qiita.com/_kensh/items/dc99e9a0238a1b4238a5#lambdaサービスのfunction-modelの良さ](https://qiita.com/_kensh/items/dc99e9a0238a1b4238a5#lambda%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AEfunction-model%E3%81%AE%E8%89%AF%E3%81%95)
    - しかし、直前までのリクエストのコンテキストは持っているということ。この点で普通のサーバーと同じである
- エフェメラルストレージ（`/tmp`）でも同様とのこと（コードで試してはいない）

# まとめ

- Lambdaが直前までのリクエストで生じた変数やエフェメラルストレージのファイルを参照出来るとは知らなかった
    - ＝強制的に「ノーコンテキストな」コードになると思っていた。そうではなかった
    - なので、リクエストの情報が他のリクエストに漏れないように気を付ける必要は、当然ある
- これは便利な仕様だと思う。DBコネクションのリクエスト間の共有や、とても簡易なキャッシュにも使えるはず
    - ただし、ウォームスタートを「アテ」にした実装はNG（生存期間わからないし）、あったらラッキーくらいにしておくべきと思う
        - [https://zenn.dev/yoshii0110/articles/0d50e872b64f59#実行環境のライフサイクル](https://zenn.dev/yoshii0110/articles/0d50e872b64f59#%E5%AE%9F%E8%A1%8C%E7%92%B0%E5%A2%83%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB)
- Provisioned Concurrency（＝常にウォームスタートなインスタンスを保持しておくサービス）だとどういう挙動になるか、未検証

# 余談：LambdaとRDS Proxy

- かつてLambdaとRDSの組み合わせはアンチパターンであった：
    - Lambda関数の実行の都度、DBコネクションを張ってしまうため
    - RDS Proxyでコネクションプーリングすれば回避出来るようになった（けど高い）
- ただし①Aurora Serverlessや②Lambda Web Adapterが利用出来る現状、RDS Proxyを使わずともそれほどリスクは大きくないと見ている（理由：①多数のコネクション数に耐える②1つの関数で全てのエンドポイントが実装されている）
    - https://dev.classmethod.jp/articles/do-you-really-need-rds-proxy/
        - 要約：
            - 従来のサーバーレスアーキテクチャの場合、エンドポイントごとに関数が独立していて、それぞれでDBコネクションを必要とする。簡単にDBコネクションが急増してしまうため、アンチパターンだった
            - ひとつの関数で複数のエンドポイントを賄う場合は（例：Mangumとか、Honoとか、Lambda Web Adapterを利用してコンテナを動かすとか）、従来と比べ必要となるコネクション数が少ない（ウォームスタート時はコネクションを再利用できるため）。なので必ずしもアンチパターンではない
    - お金がかかるのもあるので、「必要になったら追加」と考えている
        - 業務システム（＝非toC）で必要になることはないと思う
        - 単にDBのエンドポイントが変わるだけなので、実装側は透過的に扱えると思われる