DynamoDBをグローバルテーブル構成にしたうえで、DynamoDB StreamsによるLambdaトリガーを実行してみた

# 疑問と検証結果
__疑問：グローバルテーブル構成かつ、DynamoDBトリガーを有効にした場合、更新が同期・伝搬されるリージョン側でもトリガーが作動するのか？__

__結果：更新が同期・伝搬されるリージョン側でもトリガーが作動した。__

# 検証環境の構成
![構成図①.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/08a95c12-ac4f-9f26-6c9e-0f6e5c1819a3.png)

__構成概要__
* DynamoDBを更新するためにAPI Gateway + LambdaでAPIを作成
* DynamoDBはグローバルテーブル構成にして、東京リージョンとバージニア北部リージョンで同期
* それぞれのリージョンのDynamoDBの更新をトリガーに実行されるLambda関数を作成

__確認観点__
* 片方のリージョンからのAPI実行(データ更新)で複数のリージョンのDynamoDBトリガーが実行されるか
* 複数リージョンに作成したAPIからのデータ更新が、それぞれ双方向に同期できるか

# 検証環境の作成
以下のステップで進める。
1. HTTP APIによるDynamoDB操作機能の作成（東京リージョン）
2. 作成したテーブルのDynamoDBStreamsとグローバルテーブル構成の有効化（東京リージョン）
3. DynamoDBStreamsをトリガーにする関数の作成（東京リージョン）
4. DynamoDBStreamsをトリガーにする関数の作成（バージニア北部リージョン）
5. HTTP APIによるDynamoDB操作機能の作成（バージニア北部リージョン）

## 1. HTTP APIによるDynamoDB操作機能の作成（東京リージョン）
このセクションはAWSのチュートリアルをベースに作成します。
そのため本稿ではポイントに絞って記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/05c5499a-80bb-ebb1-6e30-38d2d7c5be08.png)


https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-dynamo-db.html

### 1-1. （DynamoDB）インプットテーブルの作成
パーティションキーが「id」のテーブル「http-crud-tutorial-items」を作成します。
デーブル名とパーティションキー以外の設定は、この時点では特に不要です。

![dynamodbテーブル作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/c74662a9-449b-23ae-5e0d-8885dfdc9280.png)

### 1-2. （Lambda）インプットテーブルの更新関数の作成
Lambda関数「http-crud-tutorial-function」を作成します。
実行ロールは"AWS ポリシーテンプレートから新しいロールを作成"を選択して、マイクロサービスを選択します。
![Lambda実行ロール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/24a25c7b-65d7-970e-72ee-23bf7a76478d.png)
チュートリアルのページにあるNode.jsのコード(index.mjs)をコピペして、デプロイしておきます。

### 1-3. （API Gateway）Lambda関数のHTTP API統合
API GatewayからHTTP API「http-crud-tutorial-api」を作成します。
以下のパスについてのルートを作成して、Lambda関数と紐づけ(統合)します。
+ GET /items/{id}
+ GET /items
+ PUT /items
+ DELETE /items/{id}
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/9d256cff-6c6a-d1ae-4aa2-d6d067df8acc.png)

### 1-4. テスト
API GatewayからAPI実行URLを確認し、HTTP APIを実行します。
* __データの追加__
``` shell
#リクエスト
curl -X "PUT" -H "Content-Type: application/json" \
  -d "{\"id\": \"123\", \"price\": 12345, \"name\": \"myitem\"}" \
  'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/items'
#レスポンスが返ってくる
"Put item 123" 
```
* __データの取得__
``` shell
#リクエスト
curl -X "GET" 'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/items'
#レスポンスが返ってくる
[{"price":12345,"id":"123","name":"myitem"}]
```

## 2. 作成したテーブルのDynamoDBStreamsとグローバルテーブル構成の有効化（東京リージョン）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/d1d51e7c-8c45-17bd-69ae-82f8618fb43e.png)

作成したDynamoDBのテーブルについて、少し設定を見てみます。
### DynamoDB ストリームの設定状態の確認
DynamoDBのテーブルを選択し、「http-crud-tutorial-items」の"エクスポートおよびストリーム"のタブを選択します。
"DynamoDB ストリームの詳細"を確認すると、ストリームのステータスが「オフ」になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/b8528372-133b-b304-9ff2-af0e21899119.png)

DynamoDB ストリームを有効化させる必要がありますが、グローバルテーブルを構成する操作の中で有効にできるので、グローバルテーブルの設定をしてみます。
### DynamoDB グローバルテーブルの設定
DynamoDBのテーブルを選択し、「http-crud-tutorial-items」の"グローバルテーブル"のタブを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/3b3b18c2-62f9-c9d6-2ea8-55f74f4282a4.png)

レプリケーションリージョンに"バージニア北部"を選択して、レプリカを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/606abbbf-1b22-7ff3-45e5-3870273f2fc3.png)

### DynamoDB グローバルテーブルとDynamoDB ストリームの設定状態の確認
しばらくすると"グローバルテーブル"のタブのレプリカに、レプリケーションリージョンがアクティブになっていることが確認できます。
なお、レプリケーションリージョンのリンクを選択すると、バージニア北部リージョンでもテーブルが作成されたこと、同じデータが格納されていることを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/3e954edd-93aa-b7d6-a487-f88a111cfa23.png)

また、"エクスポートおよびストリーム"のタブの"DynamoDB ストリームの詳細"を確認すると、ストリームが有効になり、「新旧タイプ」の表示タイプで設定されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/585ffd7d-79ab-0b43-fae4-54a8fbb7331e.png)

## 3. DynamoDBStreamsをトリガーにする関数の作成（東京リージョン）
次はDynamoDBの更新をトリガーに実行されるLambdaを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/1250ee1e-bc2d-671a-efe5-eb2b28016ea5.png)

### 3-1. （Lambda）関数の作成
Lambda関数「DynamoDBStreams-tutorial」を作成します。

セクション1と同様に、実行ロールは"AWS ポリシーテンプレートから新しいロールを作成"を選択して、マイクロサービスを選択します。 ★ 後の手順でロールに権限を追加します。 ★

実行されたことはCloudWatchLogsから確認できるため、コードはデフォルトのままでもよいですが、参考にさせていただいた記事から以下のようなコードを設定することにしました。
``` Node.js
export const handler = (event, context, callback) => {

    event.Records.forEach((record) => {
        console.log('イベント種別:', record.eventName);
        console.log('DynamoDB Record: %j', record.dynamodb);
    });
};
```

https://qiita.com/Fujimon_fn/items/1f18360ee9ebf6832617

### 3-2. （IAM）Streamを受け取るためのロール設定
今回は
作成したLambda関数の"設定"タブからアクセス権限を選択して、先ほど作成した実行ロールのリンクを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/556b1898-d623-09a4-8f85-12027196e368.png)
遷移したIAMロールのページから「AWSLambdaMicroserviceExecutionRole」で始まる許可ポリシーを選択し、「編集」を選択します。
ポリシーエディタから以下のアクションを許可するように設定を修正します。
* "dynamodb:GetShardIterator"
* "dynamodb:GetRecords"
* "dynamodb:ListStreams"
* "dynamodb:DescribeStream"

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/324d6370-eccb-963d-b739-9b6e33b0332c.png)

### 3-3. （Lambda）DynamoDBをトリガーに設定
ページ上部の関数の概要に表示されている「トリガーの追加」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/52bd3c63-3cac-a743-66ae-abd39f776bfc.png)

トリガーにDynamoDBを選択し、セクション1で作成したテーブル「http-crud-tutorial-items」を選択してトリガーを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/2f3ccf39-c8eb-aec7-50d6-b1edbf7256f2.png)

### 3-4. テスト
ここまでのトリガーの設定がうまくいっているか、一度テストしてみます。
セクション1で実施したデータの追加APIを実行します。 
※IDはパーティションキーのため重複しない値を設定してください。
``` shell
#リクエスト
curl -X "PUT" -H "Content-Type: application/json" \
  -d "{\"id\": \"456\", \"price\": 12345, \"name\": \"myitem\"}" \
  'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/items'
#レスポンスが返ってくる
"Put item 456" 
```

次に、DynamoDBトリガーで実行されるLambdaのログを確認するために、CloudWatchLogsに移動します。
セクション3で作成したLambda関数「DynamoDBStreams-tutorial」の"モニタリング"タブから、「CloudWatch ログを表示」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/e9147404-2345-d063-fd67-fa6ddcaa6e63.png)

ログストリームを選択してログを確認してみると、以下のようにAPIで追加した情報がログに記録されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/de85a81e-b874-2d31-8e06-bcbc3d95f7ab.png)


## 4. DynamoDBStreamsをトリガーにする関数の作成（バージニア北部リージョン）
続いてバージニア北部リージョンでセクション3で作成した関数を作成してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/1b506538-bfc8-7fd8-3dce-348ac915a2c4.png)

マネジメントコンソール右上からリージョンを変更して、同じ作業を実施します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/dc043a46-e247-296a-f62d-332b6212488a.png)


### 4-1. （Lambda）関数の作成
セクション3-1と同様です。
なお、セクション3と同じ実行ロールを転用すると、リージョンが違うためResource部分の修正が必要になります。
### 4-2. （IAM）Streamを受け取るためのロール設定
セクション3-1と同様です。
### 4-3. （Lambda）DynamoDBをトリガーに設定
セクション3-1と同様です。
### 4-4. テスト
セクション3-4と同様にAPIを実行し、DynamoDBを更新してみます。(nameの値を変えてます)
``` shell
#リクエスト
curl -X "PUT" -H "Content-Type: application/json" \
  -d "{\"id\": \"789\", \"price\": 12345, \"name\": \"replitem\"}" \
  'https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/items'
#レスポンスが返ってくる
"Put item 789"
```
同様にCloudWatchLogsを確認してみます。
まずはDynamoDBが直接更新された __東京リージョン側__ を見てみると、セクション3と同様にLambdaが実行されたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/e3a8889d-8610-ae0e-c609-b14b9495712e.png)

続いてDynamoDBの更新が伝搬された __バージニア北部リージョン側__ を見てみると、こちらもセクション3と同様にLambdaが実行されたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/5afde80c-9d4e-689d-b271-ad4bbb2aad82.png)

## 5. HTTP APIによるDynamoDB操作機能の作成（バージニア北部リージョン）
最後に、データ更新をバージニア北部リージョン側で実施するためのAPIを作成します。
手順はセクション1と同様です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/f5a8b5ed-93dc-c4aa-d5e0-82ccffe7fd52.png)

### 5-1. （DynamoDB）インプットテーブルの作成
セクション2のグローバルテーブルの設定で作成されているので、追加で作成不要です。
### 5-2. （Lambda）インプットテーブルの更新関数の作成
セクション1-2と同様です。
### 5-3. （API Gateway）Lambda関数のHTTP API統合
セクション1-3と同様です。
### 5-4. テスト
バージニア北部リージョンのAPI GatewayからAPI実行URLを確認し、HTTP APIを実行します。

* __データの取得__
``` shell
#リクエスト
curl -X "GET" 'https://hogehoge.execute-api.us-east-1.amazonaws.com/items'
#レスポンスが返ってくる。
# バージニア北部リージョンで取得(Lambda)を実行しても、
# 更新が同期されているため、これまでのテストで東京リージョンに追加したデータが表示される
[{"price":12345,"id":"456","name":"myitem"},{"price":12345,"id":"123","name":"myitem"},{"price":12345,"id":"789","name":"replitem"}]
```

* __データの追加__
``` shell
#リクエスト
curl -X "PUT" -H "Content-Type: application/json" \
  -d "{\"id\": \"000\", \"price\": 12345, \"name\": \"secondlyitem\"}" \
  'https://hogehoge.execute-api.us-east-1.amazonaws.com/items'
#レスポンスが返ってくる
"Put item 000" 
```
合わせてDynamoDBトリガーのLambdaが実行されたか、CloudWatchLogsを確認してみます。
まずはDynamoDBが直接更新された __バージニア北部リージョン側__ 、これまでと同様にLambdaが実行されたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/7ebbfb0d-a3d3-bbce-21ef-ec4cc4fc32f1.png)

続いて今度はDynamoDBの更新が伝搬された __東京リージョン側__ を見てみると、こちらも同様にLambdaが実行されたことが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429161/323a1afd-63d4-2b85-7a14-17910dc3e6fe.png)

# まとめ
* グローバルテーブル構成の場合、直接更新されたか・他リージョンの更新が反映されたかは問わずにDynamoDBトリガーが動くようでした。
* グローバルテーブルはグローバルなデータアクセスやリージョン障害対策に有効ですが、トリガーも設定する場合はイベントフィルターをかけるか、ロックなどの排他制御が必要になりそうですね。


# 参考

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-dynamo-db.html

https://dev.classmethod.jp/articles/dynamodb-global-table-creating/

https://qiita.com/Fujimon_fn/items/1f18360ee9ebf6832617