hero_picture
Cover Image for IaC管理されているDynamoDBストリームの復元を検討する

IaC管理されているDynamoDBストリームの復元を検討する

こんにちは、クラウドソリューション事業部の石垣です。

DynamoDBの復元作業にてちょっとハマった箇所がありましたので記事を投稿します。

はじめに(何にハマったか)

以下のDynamoDBの復元作業でちょっと苦労しました。

  • DynamoDBテーブルがIaC管理(CloudFormation)されている
  • DynamoDB Streamが設定されている
  • DynamoDB StreamをトリガーとしたLambdaも同様にIaC管理(CloudFormation)されている

復元時に、テーブルを一度削除しなければいけないのですが、その際にDynamoDB Streamは元に戻せずスタック更新する必要があったというところでちょっと苦労しました。

環境準備

上記のような環境を作るために、CloudFormationスタックを作成します。

  • 親スタック用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Resources:
      3  DynamoDBStack:
      4    Type: AWS::CloudFormation::Stack
      5    Properties:
      6      TemplateURL: "https://your-bucket.s3.ap-northeast-1.amazonaws.com/dynamodb.yaml"
      7
      8  LambdaStack:
      9    Type: AWS::CloudFormation::Stack
      10    Properties:
      11      TemplateURL: "https://your-bucket.s3.ap-northeast-1.amazonaws.com/lambda.yaml"
      12      Parameters:
      13        DynamoDBStreamArn: !GetAtt DynamoDBStack.Outputs.DynamoDBStreamArn
      14    DependsOn:
      15      - DynamoDBStack
  • DynamoDB用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Resources:
      3  MyDynamoDBTable:
      4    Type: AWS::DynamoDB::Table
      5    Properties:
      6      TableName: MyDynamoDBTable
      7      AttributeDefinitions:
      8        - AttributeName: Id
      9          AttributeType: S
      10      KeySchema:
      11        - AttributeName: Id
      12          KeyType: HASH
      13      BillingMode: PAY_PER_REQUEST
      14      StreamSpecification:
      15        StreamViewType: NEW_AND_OLD_IMAGES
      16
      17Outputs:
      18  DynamoDBStreamArn:
      19    Value: !GetAtt MyDynamoDBTable.StreamArn
      20    Export:
      21      Name: DynamoDBStreamArn
      22
  • Lambda用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Parameters:
      3  DynamoDBStreamArn:
      4    Type: String
      5    Description: The ARN of the DynamoDB Stream
      6
      7Resources:
      8  LambdaExecutionRole:
      9    Type: AWS::IAM::Role
      10    Properties:
      11      AssumeRolePolicyDocument:
      12        Version: '2012-10-17'
      13        Statement:
      14          - Effect: Allow
      15            Principal:
      16              Service: lambda.amazonaws.com
      17            Action: sts:AssumeRole
      18      Policies:
      19        - PolicyName: LambdaPolicy
      20          PolicyDocument:
      21            Version: '2012-10-17'
      22            Statement:
      23              - Effect: Allow
      24                Action:
      25                  - logs:CreateLogGroup
      26                  - logs:CreateLogStream
      27                  - logs:PutLogEvents
      28                Resource: "arn:aws:logs:*:*:*"
      29              - Effect: Allow
      30                Action:
      31                  - dynamodb:DescribeStream
      32                  - dynamodb:GetRecords
      33                  - dynamodb:GetShardIterator
      34                  - dynamodb:ListStreams
      35                Resource: !Ref DynamoDBStreamArn
      36
      37  MyDynamoDBTriggerLambda:
      38    Type: AWS::Lambda::Function
      39    Properties:
      40      FunctionName: MyDynamoDBTriggerLambda
      41      Runtime: nodejs20.x
      42      Role: !GetAtt LambdaExecutionRole.Arn
      43      Handler: index.handler
      44      Code:
      45        ZipFile: |
      46          exports.handler = async (event) => {
      47            console.log("Event: ", JSON.stringify(event, null, 2));
      48            return {
      49              statusCode: 200,
      50              body: "Hello World",
      51            };
      52          };
      53
      54  DynamoDBEventSourceMapping:
      55    Type: AWS::Lambda::EventSourceMapping
      56    Properties:
      57      BatchSize: 5
      58      EventSourceArn: !Ref DynamoDBStreamArn
      59      FunctionName: !GetAtt MyDynamoDBTriggerLambda.Arn
      60      StartingPosition: TRIM_HORIZON
      61

データを入れておく

作成されたDynamoDBテーブルに適当にデータを入れておきます。

参考:

Lambdaのログをみると、DyanamoDB Streamから発火して動作しているのが確認できます。

DynamoDBテーブルをエクスポートしておく

この後テーブルを削除するので、事前にテーブルデータをエクスポートしておきます。

  • ポイントインタイムリカバリ(PITR)を有効にする
  • S3にエクスポートする

これで前準備は完了です。

復元の流れ

以下のようなイメージになります。

テーブル削除

インポート時には新規テーブルを同名で作成する形となるので、一度DynamoDBテーブルを手動で削除します。

復元(データインポート)

エクスポートしたデータをインポートしていきます。

実際にデータが入っているファイルが格納されているフォルダを指定します。

エクスポート対象バケット/AWSDynamoDB/エクスポートID/data/ になるかと思います。

ちなみにファイルは以下のような形で、gzip形式で圧縮されています。

テーブルの詳細指定では、作成した通りに設定を入れていきます。

設定が入力できたらインポートボタン押下でちょっと待ちます(数分〜十数分)。

無事インポートが完了しました。

スタック更新

ここからが問題の箇所です。

インポート後、DynamoDBストリームはオフとなっています。

これを手動でオンにると、DynamoDBストリームのARNが以下となります。

arn:aws:dynamodb:リージョン:アカウントID:table/テーブル名/stream/2024-12-27T06:08:12.424(設定日時)

この設定日時のところで、Lambda側のイベントソースマッピングと整合性が取れなくなります(IaCのテンプレートファイルとも整合性が取れてません)。

ということで、Lambdaのトリガー含めて再作成する必要があります。

以下の形で一度スタック上DynamoDBストリームとそれをトリガーとするLambdaのイベントソースマッピングを削除します。

  • 親スタック用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Resources:
      3  DynamoDBStack:
      4    Type: AWS::CloudFormation::Stack
      5    Properties:
      6      TemplateURL: "https://your-bucket.s3.ap-northeast-1.amazonaws.com/dynamodb.yaml"
      7
      8  LambdaStack:
      9    Type: AWS::CloudFormation::Stack
      10    Properties:
      11      TemplateURL: "https://your-bucket.s3.ap-northeast-1.amazonaws.com/lambda.yaml"
      12#      Parameters:
      13#        DynamoDBStreamArn: !GetAtt DynamoDBStack.Outputs.DynamoDBStreamArn
      14    DependsOn:
      15      - DynamoDBStack
  • DynamoDB用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Resources:
      3  MyDynamoDBTable:
      4    Type: AWS::DynamoDB::Table
      5    Properties:
      6      TableName: MyDynamoDBTable
      7      AttributeDefinitions:
      8        - AttributeName: Id
      9          AttributeType: S
      10      KeySchema:
      11        - AttributeName: Id
      12          KeyType: HASH
      13      BillingMode: PAY_PER_REQUEST
      14      # StreamSpecification:
      15      #   StreamViewType: NEW_AND_OLD_IMAGES
      16
      17# Outputs:
      18#   DynamoDBStreamArn:
      19#     Value: !GetAtt MyDynamoDBTable.StreamArn
      20#     Export:
      21#       Name: DynamoDBStreamArn
      22
  • Lambda用テンプレート
      1AWSTemplateFormatVersion: '2010-09-09'
      2Parameters:
      3  DynamoDBStreamArn:
      4    Type: String
      5    Description: The ARN of the DynamoDB Stream
      6
      7Resources:
      8  LambdaExecutionRole:
      9    Type: AWS::IAM::Role
      10    Properties:
      11      AssumeRolePolicyDocument:
      12        Version: '2012-10-17'
      13        Statement:
      14          - Effect: Allow
      15            Principal:
      16              Service: lambda.amazonaws.com
      17            Action: sts:AssumeRole
      18      Policies:
      19        - PolicyName: LambdaPolicy
      20          PolicyDocument:
      21            Version: '2012-10-17'
      22            Statement:
      23              - Effect: Allow
      24                Action:
      25                  - logs:CreateLogGroup
      26                  - logs:CreateLogStream
      27                  - logs:PutLogEvents
      28                Resource: "arn:aws:logs:*:*:*"
      29              # - Effect: Allow
      30              #   Action:
      31              #     - dynamodb:DescribeStream
      32              #     - dynamodb:GetRecords
      33              #     - dynamodb:GetShardIterator
      34              #     - dynamodb:ListStreams
      35              #   Resource: !Ref DynamoDBStreamArn
      36
      37  MyDynamoDBTriggerLambda:
      38    Type: AWS::Lambda::Function
      39    Properties:
      40      FunctionName: MyDynamoDBTriggerLambda
      41      Runtime: nodejs20.x
      42      Role: !GetAtt LambdaExecutionRole.Arn
      43      Handler: index.handler
      44      Code:
      45        ZipFile: |
      46          exports.handler = async (event) => {
      47            console.log("Event: ", JSON.stringify(event, null, 2));
      48            return {
      49              statusCode: 200,
      50              body: "Hello World",
      51            };
      52          };
      53
      54  # DynamoDBEventSourceMapping:
      55  #   Type: AWS::Lambda::EventSourceMapping
      56  #   Properties:
      57  #     BatchSize: 5
      58  #     EventSourceArn: !Ref DynamoDBStreamArn
      59  #     FunctionName: !GetAtt MyDynamoDBTriggerLambda.Arn
      60  #     StartingPosition: TRIM_HORIZON
      61

更新完了してトリガー周り(DynamoDBストリームとLambdaのイベントソースマッピング)がなくなったことを確認します。

その後で再度元のテンプレートファイルで更新をかけて戻します。

無事戻りました。

終わりに

DynamoDBストリームのARNがストリームの設定日時となるため、復元後に手動で再設定するとIaCテンプレートと整合性が取れなくなるという話でした。

今回は、エクスポートしたテーブルをそのまま戻すという形で記載しましたが、既存のテーブルからデータをエクスポートして変換した後に別のテーブルとして復元する場合等(本番環境からデータをエクスポートして必要に応じてマスクしたものをステージング環境に展開する等)にも意識しないといけないことかなと思います。


👉️ AWS Graviton のことなら acCloud にお任せください!
👉️ AWS Graviton のことなら acCloud にお任せください!