CloudFormationのDependsOn属性を使ってAPI Throttlingエラーを防止する

この記事ではCloudFormationでAPI Throttlingエラーを防止するためにDependsOn属性を使う方法について説明いたします。
2021.08.17

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

こんにちは、ジョン・ヒョンジェです。

CloudFormationでは一つのスタックに同じリソースをたくさん作成しようとすると、過度なAPIコールが発生してリソースの作成に失敗する可能性があります。

例として、一つのスタックで数十個のDynamoDBテーブルを作成している場合、DynamoDB APIへの過度なコールによるAPI Throttlingエラーが発生し、テーブルの作成に失敗してしまいます。そのため、DynamoDBでは同時に作成できるテーブルの数を50個に制限しています。DynamoDBだけではなくEC2インスタンスなど他のリソースも同じ理由で、あまりにも多くの数を作成しようとするとエラーが出る恐れがあります。

このようなAPI Throttlingエラーを防止するために、CloudFormationのDependsOn属性を使うことができます。今回はその話をしていきたいと思います。

DependsOn属性について

CloudFormationでリソースを作成するときに、DependsOn属性を使ってリソースの作成順序を設定することができます。

以下のコードを用いて説明いたします。(AWSドキュメントから抜粋)

AWSTemplateFormatVersion: '2010-09-09'
Mappings:
  RegionMap:
    us-east-1:
      AMI: ami-0ff8a91507f77f867
    us-west-1:
      AMI: ami-0bdb828fd58c52235
    eu-west-1:
      AMI: ami-047bb4163c506cd98
    ap-northeast-1:
      AMI: ami-06cd52961ce9f0d85
    ap-southeast-1:
      AMI: ami-08569b978cc4dfa10
Resources:
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId:
        Fn::FindInMap:
        - RegionMap
        - Ref: AWS::Region
        - AMI
    DependsOn: myDB
  myDB:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: '5'
      DBInstanceClass: db.t2.small
      Engine: MySQL
      EngineVersion: '5.5'
      MasterUsername: MyName
      MasterUserPassword: MyPassword

DependsOn属性を利用すると、特定のリソースが他のリソースの次に作成されるように指定できます。このコードでは「Ec2Instance」というEC2インスタンスにDependsOn: myDBと指定していて、「Ec2Instance」は「myDB」というRDSインスタンスを作成した後に作成し始めます。

特定の順序で作成や削除する必要があるリソースがある場合に、DependsOn属性を使ってリソースの依存関係を明示的に指定することができます。

この特性を利用して、先ほどお話ししたAPI Throttlingエラーを防ぐこともできます。では、DependsOn属性を使ってみます。

やってみよう

CloudFormationテンプレートを以下のように作成します。(テンプレートの作成はYAMLでします)

AWSTemplateFormatVersion: "2010-09-09"
Resources: 
  myTable1: 
    Type: AWS::DynamoDB::Table
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable1"
  myTable2: 
    Type: AWS::DynamoDB::Table
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable2"

              .
              .
              .

  myTable51: 
    Type: AWS::DynamoDB::Table
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable51"

「myTable1」から「myTable51」まで、51個のテーブルを作成しています。

このテンプレートを使ってスタックを作成してみると、以下のようなエラーが出てスタック作成に失敗します。

Subscriber limit exceeded: Only 50 tables can be created, updated, or deleted simultaneously

DynamoDBではAPI Throttlingを防止するために、同時に作成できるテーブルの数を50個に制限しています。では、各リソースにDependsOn属性を追加し、作成するテーブル数を100個にしてみます。

AWSTemplateFormatVersion: "2010-09-09"
Resources: 
  myTable1: 
    Type: AWS::DynamoDB::Table
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable1"
  myTable2: 
    Type: AWS::DynamoDB::Table
    DependsOn: myTable1
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable2"

              .
              .
              .

  myTable100: 
    Type: AWS::DynamoDB::Table
    DependsOn: myTable99
    Properties: 
      AttributeDefinitions: 
        - 
          AttributeName: "UserId"
          AttributeType: "S"
        - 
          AttributeName: "UserName"
          AttributeType: "S"
      KeySchema: 
        - 
          AttributeName: "UserId"
          KeyType: "HASH"
        - 
          AttributeName: "UserName"
          KeyType: "RANGE"
      ProvisionedThroughput: 
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TableName: "myTable100"

各リソースにDependsOn属性を使って順序を指定しています。このテンプレートを使ってスタックを作成してみます。

同時ではなく順序通り次々とAPIを呼び出しますので時間が結構かかりますが、何のエラーもなく正常に100個のテーブルが作成されたのが確認できます。

最後に

DependsOn属性はリソース間の依存関係を指定するために使いますが、このようにAPI Throttlingエラーを防ぐための使い方もあります。実際にCloudFormationで同じリソースをこんなにたくさん作成する場合はほとんどないと思いますが、もし必要な場合は使ってみてください!

では、ブログは以上です。ありがとうございます。