こんにちは、クラウドソリューション事業部の石垣です。
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テーブルに適当にデータを入れておきます。
参考:
- https://www.seeds-std.co.jp/blog/creators/2024-08-28-csv-to-dynamodb/
- https://www.seeds-std.co.jp/blog/creators/2024-08-30-1c265349-70c5-4444-bfda-7c9c431c29c6/
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テンプレートと整合性が取れなくなるという話でした。
今回は、エクスポートしたテーブルをそのまま戻すという形で記載しましたが、既存のテーブルからデータをエクスポートして変換した後に別のテーブルとして復元する場合等(本番環境からデータをエクスポートして必要に応じてマスクしたものをステージング環境に展開する等)にも意識しないといけないことかなと思います。