DynamoDB Streamの表示タイプをCloud Formationで変更する際に大ハマリした話
こんにちは、CX事業本部の若槻です。
今回は、システムで稼働中のDynamoDB Streamの表示タイプをCloud Formationで「NEW_IMAGE」から「NEW_AND_OLD_IMAGES」に変更した際に大ハマリしてしまったので、その際に発生したこと、原因、行った対処について整理してみました。
前提(機能追加前の構成)
あるAWSシステムで「DynamoDBのテーブルにデータが作成されたらDynamoDB StreamによりLambdaが起動して作成されたデータをもとに処理を行う」機能が実装されていました。
前提となる開発方針として、AWSリソースをCloud Formationでデプロイする際のスタックはリソース種類ごとに分けているため、Cloud Formationのテンプレートファイルも以下のようにDynamoDBテーブルとLambda Functionで分けて作成しています。
Resources: DemoTable: Type: AWS::DynamoDB::Table Properties: TableName: demo_table AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 StreamSpecification: StreamViewType: NEW_IMAGE Outputs: DemoDDBStream: Value: !GetAtt DemoTable.StreamArn Export: Name: demoTableStream
Resources: DemoFunction: Type: AWS::Lambda::Function Properties: FunctionName: demo_func Code: ZipFile: | def lambda_handler(event, context): ## 行いたい処理 return Handler: index.lambda_handler MemorySize: 128 Timeout: 30 Role: !Sub arn:aws:iam::${AWS::AccountId}:role/Lambda_Basic_Role Runtime: python3.7 EventMapping: Type: AWS::Lambda::EventSourceMapping Properties: EventSourceArn: !ImportValue demoTableStream FunctionName: !Ref DemoFunction StartingPosition: LATEST
発生したこと
当初はテーブルでのデータの作成時にLambdaで新しいデータを処理するのみだったので、DynamoDB Streamの表示タイプは「NEW_IMAGE」で十分でした。
しかし、その後開発が進み、システムでの機能追加によりテーブルでのデータの変更時にLambda側で新旧のデータを比較する処理が必要となりました。
そこで、下記のようにdynamodb.ymlでDynamoDBテーブルリソースのStreamViewTypeの値を「NEW_IMAGE」から「NEW_AND_OLD_IMAGES」に変更し、既存のスタックに対してアップデートを行おうとしました。
Resources: DemoTable: Type: AWS::DynamoDB::Table Properties: TableName: demo_table AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES Outputs: DemoDDBStream: Value: !GetAtt DemoTable.StreamArn Export: Name: demoTableStream
- DynamoDBのアップデートコマンド
$ aws cloudformation update-stack \ --stack-name demo-dynamodb \ --template-body file://dynamodb.yml
すると、下記のようにスタックのアップデートが失敗し、ROLLBACKが行われてしまいました。
DynamoDBテーブルのアップデート自体はできていますが、その後にExport demoTableStream cannot be updated as it is in use by demo-lambda
という理由でDynamoDB StreamのArnのExportが失敗しているようです。
原因と切り戻し対処
原因は、DynamoDB Streamの表示タイプを更新するとストリームのArnが更新されるため、Lambdaのトリガーにすでに設定されているストリームのArnとの不整合が起きたことでした。
そして、表示タイプの更新が一度でも行われると、以下画像の通りストリームのArnは更新前の値には戻らず、Cloud FormationのROLLBACKが行われた後も不整合状態のままとなるため、Lambda側でDynamoDB Streamのトリガーを再作成することにより切り戻しを行う必要があります。
- LambdaのコンソールよりLambdaのトリガーを見ると、ストリームのArnは末尾
2020-02-12T03:26:15.334
である。(値はトリガー作成時から更新されていない) -
DynamoDBのコンソールよりDynamoDB Streamの詳細を見ると、ストリームの表示タイプは「新しいイメージ」のままだが、Arnは末尾
2020-02-12T13:52:37.889
の値に更新されている。
この状態のままではLambdaが動作しないため、以下の切り戻し対処を行いました。
- lambda.ymlでEventSourceMappingリソースの記述を削除し、スタックをアップデートする。
- dynamodb.ymlでDynamoDBリソースのStreamViewTypeの値を「NEW_IMAGE」に戻し、DynamoDB StreamのArnのOutputの記述を削除して、スタックをアップデートする。
- dynamodb.ymlでDynamoDB StreamのArnのOutputの記述を追加して、スタックをアップデートする。
- lambda.ymlでEventSourceMappingリソースの記述を追加してスタックをアップデートする。
これでデプロイ作業前の状態にシステムを切り戻すことができました。
なお、切り戻し対処時に合わせて表示タイプを「NEW_AND_OLD_IMAGES」に変更することも可能でしたが、復旧優先およびデプロイ方法の仕切り直したのめここでは一旦「NEW_IMAGE」に戻す対応を行っています。
恒久対処(「NEW_AND_OLD_IMAGES」への変更)
表示タイプの「NEW_AND_OLD_IMAGES」への変更は、DynamoDB StreamのArnをSSM Parameterを利用してdynamodb.ymlのスタックからlambda.ymlのスタックに渡す方法を取ることとしました。(Export/ImportによるArnの受け渡しをやめた)また、既存の処理に影響が出ないよう以下のように2回のリリースに分けてデプロイを行う方針としました。
- 次回のリリース
- dynamodb.ymlでDynamoDB StreamのArnを値に持つSSM Parameterのリソースを追加し、スタックをアップデートする。
- lambda.ymlでEventSourceMappingリソースのトリガーとなるArnに1. で作成したSSM Parameterの値を利用するようにし、スタックをアップデートする。
- 次々回のリリース
- dynamodb.ymlでDynamoDBリソースのStreamViewTypeの値を「NEW_AND_OLD_IMAGES」に変更し、スタックをアップデートする。
- lambda.ymlのスタックをデプロイし、「NEW_AND_OLD_IMAGES」による処理を行うLambda Functionに更新する。
なお、ここでも「Lambdaからトリガーを削除し、DynamoDB Streamの表示タイプを「NEW_AND_OLD_IMAGES」に変更してから、再度Lambdaのトリガーを作成する」という方法も考えられますが、CircleCIによるデプロイ自動化を行っているなどの都合により今回はSSM Parameterによる方法を採用しています。
根本的な回避策
システムを構築する早い段階で以下いずれかの方針を取っていれば今回のトラブルは未然に回避できていたと思われます。
- DynamoDBテーブルとそのストリームをトリガーとして起動されるLambdaは別々のスタックでリソースを作成せず、単一のスタックで作成する。
- DynamoDB Streamを利用する際は表示タイプは初回構築時に「NEW_AND_OLD_IMAGES」とする
おわりに
今回の経験によりDynamoDB Streamを実装する際の注意点を把握することができました。どのようにリソースをデプロイするのが最適であるかは、実現したいシステムやプロジェクトによって異なりますし、どの方法にも一長一短があると思うので、臨機応変に立ち向かっていく必要がありますね。
以上