less is more

心のフルスタックエンジニア👨‍💻バイブスでコードを書いています🤘

SQSのイベントをトリガーに起動するLambdaの仕組みを完全に理解する🧘‍♀️

先日S3のイベント駆動でLambdaをトリガーする記事を書きましたが、今回はSQSのイベントをトリガーにします。

bluepixel.hatenablog.com

LambdaのイベントソースにSQSが指定できるようになったのは意外にだいぶ遅めで、2018年4月です。FIFOキューはさらにその1年半後になります。

aws.amazon.com

スタックの作成

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Name:
    Description: identifier
    Type: String
    Default: sqs-lambda-event
Resources:
  Queue:
    Type: AWS::SQS::Queue
    Properties: 
      QueueName: !Ref Name
 
  EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties: 
      Enabled: true
      EventSourceArn: !GetAtt Queue.Arn
      FunctionName: !GetAtt Lambda.Arn
      BatchSize: 1

  Lambda:
      Type: AWS::Lambda::Function
      Properties:
        FunctionName: !Ref Name
        Handler: index.lambda_handler
        Role: !GetAtt LambdaRole.Arn
        Runtime: nodejs12.x
        Code:
          ZipFile: |
            exports.lambda_handler = async (event, context) => {
              const util = require('util');
              console.log(util.inspect(event,false,null));
              return 200;
            }

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
      Policies:
        - PolicyName: sqs-access
          PolicyDocument:
            Statement:
              - Sid: 1
                Effect: Allow
                Action:
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                  - sqs:ReceiveMessage
                Resource:
                  - !GetAtt Queue.Arn

Lambdaの実行ロールにはSQSを扱うための以下の権限が必要です。

  • sqs:DeleteMessage
  • sqs:GetQueueAttributes
  • sqs:ReceiveMessage

イベントの設定はLambda::EventSourceMappingで行います。 バッチサイズにはメッセージの取得件数を指定します。

動作確認

バッチで10件のメッセージをキューに送信します。

aws sqs send-message-batch --queue-url $QUEUE_URL --entries file://entries.json
[
  {
    "Id": "1",
    "MessageBody": "message: 1"
  },
  {
    "Id": "2",
    "MessageBody": "message: 2"
  },
...

BatchSizeが1なので、10回Lambdaが起動します。
ちなみに関数は同期呼び出しです。

eventの中身はこんな感じです。

{
  "Records": [
    {
      "messageId": "aa7e56f2-198b-46d2-9bdb-fd71ce34c02a",
      "receiptHandle": "AQEBJf14oNlhTcHzramlg4Zv800hpZSJu17DzO1L5B66TKlBN8Nq062rNWUljYtfB4nzKq8qxMwIZ1Bt5Zb99Ou5nHQixmo8WIf6oJTviKkqK4mKgmr+oC2YDR+FnsTHZclRXC04t6aeA/rHFizcNmF+zI/nqRj32URWkfQ8XoY+xipzAYFKQFdukfb1lOOA4f+hLdPqFm3Wq5LB0cM0E3xL3GhxEUU4jppW5thRMFQG2IVcU4Xt/UwIh5DuLvlNErTjCa2ROplPkNdjbtopHf075UH/ecg+CYyG1xgEOiwCaHAJXeco1zlLGP+dNHHDBWuPZktV8YR0jHRbD8LAfq5ku3TbSvJE8SnVz2tFZo5j9YVe5LdICWASXZl2qi1+sXjCjfmLeNyVzW/mJ70u4dXwNQ==",
      "body": "message: 1",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1588712405385",
        "SenderId": "AIDAXTNEXWMNORT3ASWF5",
        "ApproximateFirstReceiveTimestamp": "1588712405388"
      },
      "messageAttributes": {},
      "md5OfBody": "6405aa584acecc53be5693fc1ecc83cb",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:ap-northeast-1:xxxxxxxx:sqs-lambda-event",
      "awsRegion": "ap-northeast-1"
    }
  ]
}

バッチ処理に関して

例えばバッチサイズを10にした場合。
10個のインフライトメッセージが存在する場合でも、イベントが1つになるとは限りません。

動作確認ではメッセージが2つ、6つ、2つに分割されて3回起動されました。

f:id:bluepixel:20200506062445p:plain

これはイベントマッピングの問題ではなく、単純にSQSのアーキテクチャに依る仕様です。

bluepixel.hatenablog.com

裏側が分散システムになっているので、バッチサイズの最大数を取得できる保証はありません。

また、ドキュメントにはMaximumBatchingWindowInSecondsというプロパティが存在しますが、SQSをイベントソースとする場合これは指定することができません。以下のエラーが発生します。

MaximumBatchingWindowInSeconds isn't supported for this event source type.

これはストリーム型のKinesisまたはDynamoDBのための設定値です。

実はロングポーリング

イベントとは実は名ばかりで、裏側ではロングポーリングが使用されています。上述のMaximumBatchingWindowInSecondsが使えないのもそのためで、SQSのポーリング待機時間が優先されるためです。

具体的には基本5つのプロセスが並列で20秒のロングポーリングを行なっていて、インフライトメッセージの数に合わせてスケールするようになっています。最大で1000バッチ処理できるそうです。

気になるのが料金で、このポーリングの部分はしっかり課金されます。
全くメッセージがない場合でもLambdaのイベント設定が有効になっているだけで、およそ5×(60/20)×60×24×30=648,000/monthほどのリクエストが発生することになるので注意してください。

f:id:bluepixel:20200506063702p:plain

イベント駆動なので節約できると思いきや全然そんなことないので、ワーカーを自分で構築しなくて済むくらいの感じです。

メッセージの削除

関数が正常に終了すればメッセージは自動的に削除されます。

タイムアウト

Lambda関数のタイムアウトはメッセージの保持期間より短くする必要があります。処理中にタイムアウトするのを防ぐためです。

また、キューの可視性タイムアウトは関数のタイムアウト値の6倍以上に設定することが推奨されています。

FIFOキュー

S3のイベントでは通常キューしか使えませんでしたが、こちらはFIFOキューもサポートされています。

公式ドキュメント

docs.aws.amazon.com

使用したコード

github.com