ElixirでAWS Lambdaの.zipファイルとコンテナイメージの速度比較

# はじめに

ElixirでAWS Lambdaのアプリケーションを書く場合、これまではカスタムランタイムを含めたアプリケーションを.zipファイルで固めてアップロードする方式一択でしたが、2020年末に、AWS Lambdaがコンテナイメージをサポートしたことで、カスタムランタイムを含めたアプリケーションをDockerイメージにし、ECRに登録して利用するという方式も選択できるようになりました。

今回、それぞれのアプリケーションをデプロイしてみて、コールドスタートからの実行速度や、通常の実行速度等を比較してみたいと思います。

# 比較してみる

## アプリケーションの作成

今回は、リクエストされた小文字を大文字に変換して返す簡単なLambdaを作成して速度比較を行います。

アプリケーション作成については、AWS LambdaやAzure Functions、IBM Cloud Functionsのアプリケーションを簡単に作成できる謹製ライブラリがあるのでそちらを使います。

ライブラリはこちらで公開
→　https://github.com/imahiro-t/faas_base
→　https://hex.pm/packages/faas_base

① `mix new upcase`でプロジェクトを作成する
② mix.exsにライブラリを追加する

```elixir
  def application do
    [
      mod: {FaasBase.Aws.Application, []}
    ]
  end

  defp deps do
    [
      {:faas_base, "~> 1.2.0"}
    ]
  end
```

③　`upcase.ex`にハンドラを実装する

```elixir
defmodule Upcase do
  use FaasBase, service: :aws
  alias FaasBase.Logger
  alias FaasBase.Aws.Request
  alias FaasBase.Aws.Response
  @impl FaasBase
  def init(context) do
    # call back one time
    {:ok, context}
  end
  @impl FaasBase
  def handle(%Request{body: body} = request, event, context) do
    Logger.info(request)
    Logger.info(event)
    Logger.info(context)
    {:ok, Response.to_response(body |> String.upcase, %{}, 200)}
  end
end
```

コードは以上になります。

## .zipファイルの場合

カスタムランタイムのアプリケーション（.zipファイル）を作成し、試します。

```
$ mkdir -p _build
$ docker run -d -it --rm --name elx erintheblack/elixir-lambda-builder:al2023_1.16.2
$ docker cp mix.exs elx:/tmp
$ docker cp lib elx:/tmp
$ docker exec elx /bin/bash -c "mix deps.get; MIX_ENV=prod mix aws.release"
$ docker cp elx:/tmp/_aws ./_build
$ docker stop elx
```

./_build/_aws/にupcase-0.1.0.zipという.zipファイルができているので、AWSの管理コンソールより、AWS Lambda作成してテストします。

カスタムランタイムはAmazon Linux 2023を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/49062ae9-e4ca-f642-37bd-76a82ca809c0.png)

作成した.zipファイルをアップロードして、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/0d530604-e3a4-2a7d-4661-ffaaf22079f3.png)

ハンドラをモジュール名である「Upcase」に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/dcee9f90-57ad-d9db-22c7-8cecdf5a7325.png)

で、テストします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/38ff26d4-cec7-940c-0598-3bef1470699b.png)

コールドスタートからの初期所要時間が1202.68 ms、所要時間に39.35 msかかっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/b958c603-0b78-c7c4-e263-496924d194bd.png)

ウォームアップされた状態から10回テストを行ってみると、所要時間の平均は45.31 msでした。

次に、メモリを128MBから1024MBに上げて計測してみたところ、コールドスタートからの初期所要時間はあまり変わらずの1182.99 ms、所要時間は5.46でした。

ウォームアップされた状態から10回テストを行ってみると、所要時間の平均は7.14 msでした。

所要時間は７倍近く速くなりましたが、初期所要時間はほぼ変化なしでした。

## コンテナイメージの場合

カスタムランタイムのアプリケーション（コンテナイメージ）を作成し、試します。

```
$ handle_module=Upcase
$ image_name=upcase
$ mkdir -p _build
$ docker run -d -it --rm --name elx erintheblack/elixir-lambda-builder:al2_1.10.4
$ docker cp mix.exs elx:/tmp
$ docker cp lib elx:/tmp
$ docker exec elx /bin/bash -c "mix deps.get; MIX_ENV=prod mix aws.release ${handle_module}"
$ docker cp elx:/tmp/_aws ./_build
$ docker stop elx
$ docker build -t ${image_name}:latest ./_build/_aws/
```

upcaseというDockerイメージができるので、ローカル環境で動作確認してみます。

```
$ docker run -p 9000:8080 upcase:latest
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"foo":"bar"}'
```

動作確認できたら、DockerイメージをECRに登録し、AWSの管理コンソールより、AWS Lambda作成してテストします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/bf761c24-a3d8-964f-df66-7436b99c4db4.png)

で、テストします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/f3625dab-ddc6-de45-95c5-a0b7e2118e13.png)

コールドスタートからの初期所要時間が1335.45 ms、所要時間に94.70 msかかっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94455/c4b720ce-0ab3-b725-99b0-338a6cebe498.png)

ウォームアップされた状態から10回テストを行ってみると、所要時間の平均は50.52 msでした。

次に、メモリを128MBから1024MBに上げて計測してみたところ、コールドスタートからの初期所要時間はあまり変わらずの1355.58 ms ms、所要時間は4.23 msでした。

ウォームアップされた状態から10回テストを行ってみると、所要時間の平均は6.９4 msでした。

所要時間は8倍近く速くなりましたが、初期所要時間はほぼ変化なしでした。

# さいごに

なんとなくコンテナイメージの起動は遅いのでは？と思っていたのですが、やってみると大差ない結果になりました。（メモリの消費は多少小さくなった）

そもそも従来のAWS　Lambdaの内部でもコンテナイメージ上で動いていて、今回作成したコンテナイメージのベースイメージもAWS側が提供しているカスタムランタイム用のベースイメージを使用しているので、差がなくて当然なのかもしれません。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-create.html#runtimes-images-lp

そうなると下記の点でコンテナイメージを選択する方が有利な気がします。

①　必要なものをコンテナにインストールしておけるので、アプリケーションを完全な状態でパッケージングできる
② .zipファイルの上限が50MBに対し、コンテナイメージの上限は10GB
③　Runtime Interface Emulatorがベースイメージにあるとローカルで簡単に試せる

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-test.html#images-test-AWSbase

既に運用している環境ではデプロイ周りとかの調整が必要になりますが、AWS側のランタイム戦略の影響を受けにくいところからも、移行できるなら移行しておきたいって感じました。