tomoima525's blog

Androidとか技術とかその他気になったことを書いているブログ。世界の秘密はカレーの中にある!サンフランシスコから発信中。

Provisioned Concurrency を Amplify 上の Lambda Function で利用する方法

Provisioned Concurrency は、Lambda 関数を常にクラウド上に起動させておく機能です。Amplify はこの機能をサポートしていませんが、CloudFormation ファイルをカスタマイズすることで Provisioned Concurrency の生成を自動化することができます。この記事では、Provisioned Concurrency にまつわる Amplify 特有の設定方法を紹介します。例えば

  • プロダクション環境のみに Provisioned Concurrency を設定するには?
  • Lambda トリガーや AppSync から Povisioned Lambda 関数を実行するには?

などです。それでは早速ごらんください!

なお、この記事は以下の自分の技術記事の日本語訳です。

tomoima525.medium.com

CloudFormation の設定

Provisioned Concurrency では、CloudFormation ファイルに Alias と Version と呼ばれる 2 つの設定の追加が必要です。Provisioned Concurrency は、Version を持つ Lambda 関数にのみ設定できます。Version を持たない場合、$Latest という値がデフォルトについきますが、これには追加できません。Alias は Provisioned Concurrency の名前で、他のリソースから呼ぶ際に使います。後述しますが、複数の Provisioned Concurrency を定義して、ユースケースに応じて切り替えられます。

まずは最も単純な Provisioned Concurrency の例です。

{
  ...
  "Resources": {
    "LambdaFunction": {
      ...,
      "Properties": {
        "ReservedConcurrentExecutions": 10,
      }
       },
    "LambdaVersion": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "LambdaFunction"
        }
      }
    },
    "ProvisionedConcurrencyLambdaAlias": {
      "Type": "AWS::Lambda::Alias",
      "Properties": {
        "FunctionName": {
          "Ref": "LambdaFunction"
        },
        "FunctionVersion": {
          "Fn::GetAtt": [
            "LambdaVersion",
            "Version"
          ]
        },
        "Name": "provisioned",
        "ProvisionedConcurrencyConfig": {
          "ProvisionedConcurrentExecutions": 10
        }
      },
      "DependsOn": "LambdaFunction"
    },

この例では、10 のインスタンスを Provisioned Concurrency 用に設定しました。

Provisioned Concurrencyに合わせてConcurrentに実行できるインスタンスを制約する ReservedConcurrentExecutions も追記しています。ReservedConcurrentExecutionsProperties セクションで設定し、ProvisionedConcurrentExecutions よりも大きい値にする必要があることに注意してください。

以下のコマンドを実行して、このアップデートをクラウドに反映します。

amplify push

プロダクション環境にのみ Concurrency を適用する

場合によっては、Provisioned Concurrency をプロダクション環境だけに適用して、コストを削減したいことがあります。そのためには、Conditionsリソースを追加する必要があります。

"Conditions": {
  ...
  "CreateProdResources": {
    "Fn::Equals": [
      {
        "Ref": "env"
      },
      "production"
    ]
  }

そして ProvisionedConcurrencyConfig に以下を追加します。

"ProvisionedConcurrencyConfig": {
  "Fn::If": [
    "CreateProdResources",
    {
      "ProvisionedConcurrentExecutions": 2
    },
    {
      "Ref": "AWS::NoValue"
    }
  ]
}

amplify pushを実行すると、Production 以外の環境では Provisioned Concurrency や alias が表示されなくなります。

Lambda Trigger の設定

次に、Lambda Trigger に Provisioned Concurrency を適用する方法を見てみます。何も設定しないと、Lambda Trigger(DynamoDB のイベントストリームなど)は$LATESTバージョンの Lambda 関数に接続されるため、Provisioned Concurrency は利用できません。ここで必要となるのが、Event Source Mapping の更新です。Event Source Mapping は、Lambda 関数が接続されるソースを定義します。

以下のコードは、AppSync による DynamoDB 上の Photo Table のイベントソースマッピングを表しています。なお GraphQLAPIIdOutput は AppSync 構築時に生成される API アドレスです。

"LambdaEventSourceMappingPhoto": {
  "Type": "AWS::Lambda::EventSourceMapping",
  "DependsOn": [
    "LambdaTriggerPolicyPhoto",
    "LambdaExecutionRole"
  ],
  "Properties": {
    "BatchSize": 100,
    "Enabled": true,
    "EventSourceArn": {
      "Fn::ImportValue": {
        "Fn::Sub": "${GraphQLAPIIdOutput}:GetAtt:PhotoTable:StreamArn"
      }
    },
    "FunctionName": {
      "Fn::GetAtt": [
        "LambdaFunction",
        "Arn"
      ]
    },
    "StartingPosition": "LATEST"
  }
},

ここで Event Source が Provision された Lambda を見るように、provisionedというエイリアスを追加します。

"FunctionName": {
    {
      "Fn::Join": [
        "",
        [
          {
            "Fn::GetAtt": [
              "LambdaFunction",
              "Arn"
            ]
          },
          ":provisioned"
        ]
      ]
    },
  ]
},

ちょっとした落とし穴があって、まずクラウド上にエイリアスリソース(ここではProvisionedConcurrencyLambdaAlias)を作成してください。Alias リソースが生成されないと、イベントソースマッピングの作成時に Function does not exist というエラーが発生します。

"Invalid request provided: Function does not exist (Service: Lambda, Status Code: 400, Request ID: 4c899598-b74a-4b71-b4df-2821e4c6098a, Extended Request ID: null)"

Provisioned Concurrency を Production にのみ適用するには、Conditionsリソースで定義したCreateProdResourcesを使用します。

"FunctionName": {
  "Fn::If": [
    "CreateProdResources",
    {
      "Fn::Join": [
        "",
        [
          {
            "Fn::GetAtt": [
              "LambdaFunction",
              "Arn"
            ]
          },
          ":provisioned"
        ]
      ]
    },
    {
      "Fn::GetAtt": [
        "LambdaFunction",
        "Arn"
      ]
    }
  ]
},

参考:

AWS::Lambda::EventSourceMapping - AWS CloudFormation

AppSync の設定

Provisioned Concurrency を AppSync に設定したいケースもあります。AppSync GraphQL には @function ディレクティブがあり、Lambda 関数を Query や Mutation に直接接続することができます。Provisioned Concurrency を適用するには、関数名に Alias を渡します。

type Mutation {
  createPhoto(...): Result
    @function(name: "createPhoto-${env}:provisioned")

残念ながら、@functionAWS::NoValueをサポートしていません。そのため、Lambda 関数や Lambda Trigger で Provisioned Concurrency を本番環境に設定したのと同じ方法を取ることはできません。今のところ最小値を設定するしかないようです。

"ProvisionedConcurrencyConfig": {
  "ProvisionedConcurrentExecutions": {
    "Fn::If": [
      "CreateProdResources",
      10,
      1
    ]
  }
}

github.com


Amplify で Provisioned Concurrency を設定するためのあれこれについて紹介しました。Amplify はまだ進化している技術なので、将来的には Concurrency を Amplify から直接定義できるようになることを期待しています。