DynamoDB StreamsのREMOVEイベントとTTL(Time to Live)でハマった話

こんにちは。
OSS大好きなshoitoです。

最近、DynamoDB Streamsを利用して実装していた機能で、ちょっとハマったことがあったので紹介します。
ユーザーの操作によるDynamoDBテーブルのレコードの削除に伴うREMOVEイベントは考慮していたけど、TTL(Time to Live)によるレコードの削除に伴うREMOVEイベントの考慮が漏れてしまっていた、という内容です。

以下の公式ガイドに気付いてれば良かったです...。

DynamoDB ストリーム および Time To Live https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/time-to-live-ttl-streams.html

DynamoDB StreamsとTTL(Time to Live)

DynamoDB Streamsは、DynamoDBテーブルのストリームの管理から有効化しておくと、レコードの追加、変更、削除のイベントをほぼリアルタイムに受けられるようになります。

DynamoDB ストリーム を使用したテーブルアクティビティのキャプチャ https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.html

DynamoDB Streamsのイベントとしては、以下の3つが定義されています。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/APIReference/API_streams_Record.html

  • INSERT: レコードが追加された
  • MODIFY: レコードの属性が変更された
  • REMOVE: レコードが削除された

TTLは、以下のブログでも紹介されているように、指定した期間経過後にレコードを自動で削除する機能です。

DynamoDBの各データを自動で削除する機能(TTL:Time to Live)を試してみた

やりたかったこととハマったこと

DynamoDB Streamsを利用して実装していた機能なのですが、ユーザー操作により、DynamoDBテーブルのレコードが削除された際に、それをトリガーにLambdaを実行するものでした。

機能実装を終えた気になっていたんですが、開発環境にデプロイしてから数日経過後に
「TTLで消えたレコードのイベントも処理されてません?」 とチームメンバーに訊かれて、TTL設定されてたのか、おやおやとなりました。

DynamoDBテーブルからレコードが消された場合は REMOVE イベントが流れるので、ユーザーによる操作ではなくTTLでレコードが消された場合も同様です。
今回は ユーザー操作により、DynamoDBテーブルのレコードが削除された際に という条件だったので、TTLのケースは除外する必要がありました。

Lambdaが受け取るイベントのJSONを見ると、TTLでレコードが削除された時とそれ以外で削除された時では、内容が一部違いました。

ユーザー操作によりレコードが削除されたときのLambdaが受け取るJSON

{
  'Records':[
    {
      'eventID':'7e3a89f72e3ea8c077aa5fa4de5661da',
      'eventName':'REMOVE',
      'eventVersion':'1.1',
      'eventSource':'aws:dynamodb',
      'awsRegion':'ap-northeast-1',
      'dynamodb':{
        'ApproximateCreationDateTime':1558672104.0,
        'Keys':{
            ....
        },
        'OldImage':{
            ....
        },
        'SequenceNumber':'69657600000000044131896862',
        'SizeBytes':300,
        'StreamViewType':'NEW_AND_OLD_IMAGES'
      },
      'eventSourceARN':'....'
    }
  ]
}

よく見ないと分からないのですが、TTLでレコードが削除された時は以下のように userIdentity が含まれている点が違います。

{
  'Records':[
    {
      'eventID':'8558e9b1387c639f1ac3e73a8ced5a00',
      'eventName':'REMOVE',
      'eventVersion':'1.1',
      'eventSource':'aws:dynamodb',
      'awsRegion':'ap-northeast-1',
      'dynamodb':{
        'ApproximateCreationDateTime':1559538087.0,
        'Keys':{
          ....
        },
        'OldImage':{
            ....
        },
        'SequenceNumber':'116861500000000027471850174',
        'SizeBytes':330,
        'StreamViewType':'NEW_AND_OLD_IMAGES'
      },
      'userIdentity':{
        'principalId':'dynamodb.amazonaws.com',
        'type':'Service'
      },
      'eventSourceARN':'...'
    }
  ]
}

確かに、公式ガイドに記載されている通りですが、後で気付きました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/time-to-live-ttl-streams.html

{
  'Records':[
    {
      ...
      'userIdentity':{
        'principalId':'dynamodb.amazonaws.com',
        'type':'Service'
      },
      ...
    }
  ]
}

今回の解決策

Lambda(Python)では、TTLでレコードが削除されたケースのイベント(上記の userIdentity を含む)は、このようにフィルタリングできました。
ご参考までに。

def lambda_handler(event, context):
    records = event['Records']
    for record in records:
        event_name = record['eventName']
        if event_name == 'REMOVE' and record.get('userIdentity'):
            if record['userIdentity']['type'] == 'Service' and record['userIdentity']['principalId'] == 'dynamodb.amazonaws.com':
                # TTLでレコードが削除されたケースのハンドリング