巨大なレイヤーを紐付けたLambda Functionの挙動を観察してみた

Lambda Functionにサイズの大きなLambda Layersを紐づけて諸々の挙動を観察してみました。
2019.01.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

サーバーレス開発部@大阪の岩田です。

2019年1月現在AWS Lambdaの制限として解凍後のデプロイパッケージサイズはレイヤーを含めて250Mまでという制限があります。 本エントリではサイズの大きなレイヤーを使い、デプロイパッケージのサイズを上限ギリギリまで大きくしていった際にどの様な影響があるかを観察してみます。

やること

サイズが49Mのレイヤーを5つ作成し、LambdaFunctionにレイヤーを1つ紐付けた場合、2つ紐付けた場合、3つ・・・と各パターンで

  • AWS CLIでLambda Functionをデプロイするのにかかった時間の計測
  • X-Rayを使ったLambda Function実行時間の計測

を行います。

事前準備

まず下記のシェルスクリプトを実行し、49Mのレイヤーを5つ作成します。

#!/bin/bash

for i in `seq 1 5`; do
  dd if=/dev/urandom of=bigfile${i}.img count=49 bs=1m
  zip bigfile${i}.zip bigfile${i}.img
  aws s3 cp bigfile${i}.zip s3://<適当なS3バケット>
  aws lambda publish-layer-version --content S3Bucket=<適当なS3バケット>,S3Key=bigfile${i}.zip --layer-name bigfilelayer${i}
done

レイヤーの準備ができたのでLambda Functionを準備します。 Lambda Functionのコードです。コード自体には特に意味はありません。

handler.py

import json

def lambda_handler(event, context):

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

デプロイ用にZIPに圧縮しておきます

zip handler.zip handler.py

計測してみる

ここからLambda Functionをデプロイしながらパターンごとに計測していきます。

レイヤー無しの場合

まずLambda Functionをデプロイします。 先ほど作成したZIPを指定しつつ、X-Rayを有効化しています。

 time aws lambda create-function --runtime python3.6 --handler handler.lambda_handler --role arn:aws:iam::xxxxxxxxxxxx:role/lambda_basic_execution --function-name biglayer_function --zip-file fileb://handler.zip --tracing-config Mode=Active

デプロイの所要時間です。

real	0m1.426s
user	0m0.522s
sys	0m0.197s

サクッと終了しました。 適当なテストイベントを設定して実行、X-Rayのトレースを確認してみます。

initializationの表示が出ており、コールドスタートしていることが分かります。 コールドスタートしているにも関わらずトータルの所要時間は199msなので、かなり高速にサンドボックス環境を作成できていると言えるのではないでしょうか?

レイヤー1つの場合

次にレイヤーを1つ紐付けてデプロイパッケージのサイズを大きくしてみます。

time aws lambda update-function-configuration --function-name biglayer_function  --layers arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:layer:bigfilelayer1:1

デプロイの所要時間です。

real	0m8.659s
user	0m0.467s
sys	0m0.136s

一気に遅くなったことが分かります。

X-Rayのトレース結果です。

Lambda Functionの所要時間は相変わらず数msレベルですが、総所要時間は752msとなっています。 この調子でレイヤーを増やしていき、レイヤー5つを紐付けた場合まで計測していきます。

結果と考察

最終的な結果は下記の通りです。 複数回計測すべきですが、計測結果が明らかに遅くなっていったので1回で切り上げました。

レイヤ数 Lambda Function作成の所要時間 Lambda Function実行の所要時間
0 1.426s 199ms
1 8.659s 752ms
2 22.722s 1.2s
3 30.136s 1.6s
4 39.231s 4.5s
5 47.709s 5.4s

紐付けるレイヤーを増やしていくとLambda Function保存時の所要時間、Lambda Function実行の所要時間共に増えていきました。 レイヤー無しとレイヤー5つの場合を比較するとLambda Function保存時の所要時間は46秒程度、Lambda Function実行の所要時間は5秒程度遅くなっています。

勝手な妄想ですがDockerで例えるならLambda Function保存時はdocker build相当の処理が、コールドスタート時にはdocker pull相当の処理が裏で動いているのかもしれません。

Lambda Function保存時にdocker build相当の処理が動いているのであれば、Lambda Layersの指定にLatestが使えないのも納得です。

まとめ

簡単にですが巨大なレイヤーを紐付けたLambda Functionの挙動を観察してみました。 また時間ができたらレイヤー無しの巨大なLambda Functionや、巨大なレイヤーを1つだけ紐づけたLambda Functionの挙動も確認してみたいと思います。 ※一度249Mのレイヤーを作ろうと試したのですが、タイムアウトしてレイヤー作成に失敗してしまいました。。

今年のre:Inventでこのあたりの内部構造の話が聞けることを期待したいです!