Lambdaのコールドスタートを改めて整理する

## 概要

Lambdaを使うとき、「コールドスタート」の課題があることをよく耳にします。これについて、過去の経緯、直近のアップデート状況なども踏まえつつまとめてみた記事です。
AWS初心者の目線なので不足点などあるかもですが、その場合にはご指摘いただければありがたいです。

## 前提となる知識

### 1. Lambdaは実行時にコンテナを生成している

以下のスライドはAWS公式からの引用ですが、Lambda関数実行時の流れを表したものです。これに記載の通り、Lambdaの裏側では、実行時にコンテナが生成されています。なお、以降は実行時に生成されるものを__関数インスタンス__と呼びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/fb36e4fc-d3df-553e-dc25-72b2aa5bc298.png)
(出典)[全部教えます！サーバレスアプリのアンチパターンとチューニング](https://speakerdeck.com/keisuke69/quan-bu-jiao-emasu-saharesuahurifalseantihatantotiyuninku)

### 2. LambdaはVPC外のサービスである

AWSのサービスは、自分で作ったVPC内に設置されるものと、そうでないサービスの2つに分かれます。

|設置場所|サービス例|
|---|---|
|VPC内|EC2, RDS, ELB 等|
|VPC外|Lambda, S3, Dynamo DB 等|

例えば、EC2やRDSを起動する場合には、どこのVPC上で起動するか選べます。
一方、LambdaやS3を単体で利用する場合は、特にVPCを意識せずに実行できます。これらのサービスは、「AWSが管理するVPC」で起動されるためであり、デフォルトだとユーザのVPCでは起動されないからです。

 - (例1) EC2 + RDSの構成
   - デフォルトVPCを利用するか、新たにVPCを作成して、その中にEC2やRDSを配置する

 - (例2) API Gateway + Lambda + DynamoDB
   - いずれも自分のVPC外のサービスであるため、VPCを用意しなくても実行することができる

ただ、LambdaとVPC内に起動されるサービスを組み合わせて使うことも可能です。
例えば、Lambda + RDSの構成も可能です。この場合、LambdaはVPC内にENI(Elastic Network Interfaces)を作成して、そこからRDSにアクセスします。（Lambda + RDSの構成は相性が悪いと言われますが、この記事ではそこまで触れません。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/ccf31e83-78c6-f0e8-f6ca-00248c1ee2c5.png)
(出典)AWS公式: https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/

この図で言えば、左側がAWSが管理するVPCで、そこから右側のユーザのVPCにENIを作成してアクセスしています。これがVPC Lambdaとか呼ばれたりする仕組みです。
※後述しますが、この図は2019/9のアップデート前の図です。

これら2つの前提知識を踏まえて、コールドスタートとは何かを説明します。

## コールドスタートとは

そもそもコールドスタートとは、Lambda関数実行時に通常より時間がかかることを言いますが、正確には以下の2つに分けられます。

### 1. 関数インスタンスの起動が遅い

関数インスタンスの生成に時間がかかることを言います。起動時に生成されたLambdaの関数インスタンスは、一定時間後に消失します。そのため、Lambdaの初回実行時以外にも、一定時間経過してから実行された場合には、関数インスタンスが既に消失しており生成から再実行されます。結果、コールドスタートとなるわけです。

一方、関数インスタンスが消失する前にLambdaが再実行された場合、以下のスライドの通りコンテナが再利用され、起動時間を省略しての実行（ウォームスタート）が可能となります。頻繁にLambda関数が実行される場合には、コンテナが再利用される可能性が高くなりコールドスタートの発生確率は下がります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/221683e6-d827-db20-13bd-d06a4f0ba3cf.png)
(出典)[全部教えます！サーバレスアプリのアンチパターンとチューニング](https://speakerdeck.com/keisuke69/quan-bu-jiao-emasu-saharesuahurifalseantihatantotiyuninku)

また、コンパイルが伴う言語では、関数インスタンスの生成にコンパイルの時間もかかってきます。特にJavaは実行環境そのものが重いため、コールドスタートで非常に時間がかかると言われています。(但し、ウォームスタート時には、非コンパイル言語よりも処理が速いと言われています。)

### 2. VPCでの起動が遅い

VPC Lambdaの実行に、別途時間がかかっていたことを言います。これは、前提知識に記載したENIの作成に時間がかかるためです。ENIは作成に10秒以上かかることもあります。特に、2019/9のアップデート以前は関数インスタンスごとにENIが作成されていました。
また、ENIにはIPアドレスが割り当てられるため、関数インスタンスが増えるほどIPアドレスが消化されることや、ENIを作成するAPIの上限に引っかかるという別の課題も引き起こしていました。

ただ、__後述するアップデートにより、これが原因のコールドスタートは大きく改善されました。__

## コールドスタートへの対処

2種類のコールドスタートそれぞれについて対応策を紹介します。

### 1. 「関数インスタンスの起動が遅い」への対応

#### Lambdaの定期実行

特にリクエストがないタイミングでも、Lambda関数を定期的に実行する方法です。これにより、関数インスタンスが高い確率でウォームスタンバイとなり、コールドスタートの発生確率を下げることができます。後述する「Provisioned Concurrency」が登場するまでは、唯一の有効策とされていました。

但し、これには以下のデメリットがあります。

 - __定期実行するための設定が必要になる__
   - 設定の手間がかかるほか、Lambda関数によっては「必要時以外に実行されたら、データ更新が不整合になったりしないか」などの考慮が必要になります。
   - また、定期実行する分コストがかかります。
   
 - __必ずしもウォームスタートになるとは限らない__
   - 関数インスタンスがコールド状態になるまでの時間は決まっているわけではありません。そのため、定期実行はあくまでコールドスタートの確率を下げるための対応方法であり、ウォームスタートを担保できません。

#### Provisioned Concurrency

2019年のre:Inventでアップデートが発表されました。日本語では「プロビジョニングされた同時実行」と表示されます。
Lambdaの関数インスタンスを、あらかじめプロビジョニングされた（＝ウォームスタート）状態にしておくことができるオプションです。これにより、確実にコールドスタートせずに実行することができます。

デメリットとしては、通常のLambdaよりもコストがかかることです。通常のLambdaは「関数が実行された時間」に対して課金されますが、Provisioned Concurrencyを用いると「プロビジョニングした時間」にも課金されます。つまり、常にプロビジョニングしていた場合、その時間にも課金されるわけです。ただ、Provisioned ConcurrencyのON/OFFはAWS CLIからも操作できるので、「決まった時間帯だけプロビジョニングしておく」といった設定によりコストを抑えることも可能です。

以下の記事で、課金について非常にわかりやすく比較されていましたので参考になると思います。

 - [LambdaのProvisioned Concurrencyの価格を調べてみた](https://qiita.com/zizi4n5/items/37eb01467320fd97d114)

また、実行時間に対する検証は以下がとても参考になります。

 - [AWS Lambda Provisioned Concurrencyをゆるく検証](https://tech.unifa-e.com/entry/2019/12/20/003000)

#### どちらで対応すべきか
AWS Innovateなどのセミナーを見ると、Provisioned Concurrencyの利用を推奨しているようです。
自分のアプリケーションに対して、コールドスタートが起きるとどれくらい影響があるのかを考慮しつつ、Provisioned Concurrencyの利用を検討するのが良さそうです。

### 2. 「VPCでの起動が遅いこと」への対応

2019/9に発表されたアップデートにより、VPC Lambdaの仕組みが大きく改善されました。関数インスタンス作成の都度、ENIを作成していた形式から、事前に用意したENIを利用してVPCへ接続する形式へと変更されました。これにより、ENIの作成時間にかかっていた時間が削減されたことになります。また、NATにより関数インスタンスが共通のENIからアクセスするため、IPアドレスを消費する問題やAPI制限の問題も解消されました。

> ネットワークインターフェースの作成は、Lambda 関数が作成されるか、VPC 設定が更新されるときに発生します。 関数が呼び出されると、実行環境は事前に作成されたネットワークインターフェイスを使用するだけで、そこへのネットワークトンネルをすばやく確立します。 これにより、コールドスタートでのネットワークインターフェイスの作成と接続にこれまで発生していた遅延が劇的に削減されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451175/7da209bc-4766-10c9-7b9a-2ef19fddedc3.png)

(引用と画像いずれも出典はAWS公式) https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/

## まとめ
Lambdaのコールドスタートには、関数インスタンスの起動が遅いことと、VPCでの起動が遅いことの2つがありました。ただ、アップデートにより2020/4時点で意識すべき「コールドスタート」の課題は、関数インスタンスの起動が遅いことのみとなっており、また、これについてもProvisioned Concurrencyというアップデートが登場しており、解消が期待されています。

## 終わりに
簡単ではありますが、自分なりの理解をまとめてみました。コールドスタートを初めて聞いた、というような人のお役に立てば幸いです。より詳細な説明は、記事内のリンク先で記載されていますので、そちらを見ていただいた方が良いかと思います。

また、Lambdaでもう一つよく聞く「RDSと相性が悪い」という事象については、以下記事が非常にわかりやすいので、合わせて知りたい方はこちらをご参照ください。

 - [なぜAWS LambdaとRDBMSの相性が悪いかを簡単に説明する](https://www.keisuke69.net/entry/2017/06/21/121501)
 
なお、先述の通りAWSもLambdaに日々アップデートを行っており、既に過去の課題となったものもあります。これからのアップデートにも期待しましょう。