🧷

AWS Lambda 関数の予約済同時実行数を検証してみる

2023/04/11に公開

背景

EventにAmazon S3(以下、S3)を設定しているAWS Lambdaの関数を作成しています。この関数は、S3にアップロードされたファイルを処理して、外部APIにリクエストを送信するものです。
S3へのアップロードリクエストが短期間に大量に発生した場合、後続の外部APIにも大量のリクエストが発生してします。
その結果、外部APIの負荷が高くなってしまうことがあるため(あったため)、Lambda 関数自身の同時実行数を制限する機能を検証しました。

AWS Lambdaの予約済同時実行数とは

AWS Lambdaでは、一度に実行できる関数インスタンスの数に制限があります。この制限は、AWSアカウントごとのリージョン単位で設定されており、その数を「予約実行可能数」と呼びます。予約実行可能数は、AWS Lambdaの同時実行数と関連しており、同時実行数が予約実行可能数を超過しないように、適切なスロットリングが行われます。

参考: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-concurrency.html

結論:予約実行可能数を超過するアップロード数が発生した場合の動き

Amazon S3からトリガーされるLambda関数の場合、予約実行可能数を超過する数のファイルがS3にアップロードされた場合、以下のような動作となります。

  • 予約実行可能数以内のLambda関数が正常に実行される
  • 予約実行可能数を超過したイベントはスロットリング(イベントの処理停止)を行い、キューに入る
  • キューに入ったイベントは、Lambdaの同時実行数が予約実行可能数以下になると順次処理される
  • キューに入ったイベントの順番保証はされない

検証

S3のバケットにアップロードしたファイルをトリガーに実行するLambda 関数を作成します。
予約済み同時実行数を1に設定し、コード内で数秒待機することで、意図的にスロットリングを発生させます。
S3にアップロードされたファイルを読み取り、Lambda関数内でログ出力します。

main.go

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
)

// Event: S3
func handleS3Event(content context.Context, s3Event events.S3Event ) error {
	log.Println(content, s3Event)
	for _, record := range s3Event.Records {
		s3Entity := record.S3
		bucket := s3Entity.Bucket.Name
		key := s3Entity.Object.Key

		fileContent, err := readFileFromS3(bucket, key)
		if err != nil {
			return err
		}

		fmt.Printf("File content: %s\n", fileContent)

		log.Println("Sleeping for 5 seconds...")
		time.Sleep(10 * time.Second)
		log.Println("Done")
	}

	return nil
}

func readFileFromS3(bucket, key string) (string, error) {
  ...(本筋ではないため割愛)...
}

func main() {
	lambda.Start(handleS3Event)
}

serverless.yml

service: sls-s3-event-lambda
frameworkVersion: '3'
provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1
  architecture: x86_64
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - s3:*
          Resource: "*"
custom:
  defaultStage: dev
package:
  patterns:
    - "!./**"
    - "./bin/**"
functions:
  hello:
    handler: bin/main
    timeout: 30
    events: 
      - s3:
          bucket: ${self:custom.defaultStage}-sls-s3-event-lambda
          event: s3:ObjectCreated:*
          rules:
            - prefix: hello/
            - suffix: .txt
    reservedConcurrency: 1  # 予約済同時実行数を1に設定

ループ回数をファイル内に書き出して、AWS CLIを使用してS3にアップロードするだけのシェルスクリプトです。

upload.sh

#!/bin/bash
BUCKET="dev-sls-s3-event-lambda"

# 対話によってAWS_PROFILEを変更する
echo "Input AWS_PROFILE"
read AWS_PROFILE

for i in {1..10}; do
  echo "Uploading file for loop $i"
  echo "Loop: $i" > test.txt
  aws s3 cp test.txt s3://${BUCKET}/hello/test_loop_${i}.txt --profile ${AWS_PROFILE}
  sleep 1
done

シェルスクリプトを実行して10個のファイルを連続でアップロードします。

$ chmod +x upload.sh
$ ./upload.sh

検証結果

Lambda関数の実行ログ

Lambda関数のメトリクス(1分間隔)

  • 検証中の同時実行数は1
  • 1分あたりのスロットリングは最大39回発生

  • 全10ファイル分のイベントが実行されている
  • 処理される順番とアップロードした順番は一致していない

以上から、予約済み同時実行数を超過したイベントも処理されていることがわかります。
ただし、処理の順番保証はされていないことがわかります。
スロットリング回数が50回であることから、イベント実行とキューイングが繰り返されていることがわかります。

まとめ

  • 予約実行可能数以内のLambda関数が正常に実行される
  • 予約実行可能数を超過したイベントはスロットリング(イベントの処理停止)を行い、キューに入る
  • キューに入ったイベントは、Lambdaの同時実行数が予約実行可能数以下になると順次処理される
  • キューに入ったイベントの順番保証まではされない

キューに入ったイベントの順番保証を行うには、SQS等のキューイングサービスを使用する必要がありそうですね。

以上です。

GitHubで編集を提案
NCDCエンジニアブログ

Discussion