DVA対策としてのDynamoDB掘り下げ基本編

## はじめに

DVA取得に向けてのDynamoDBをBlack Beltや過去のセッション等を参考に少しまとめてみました。

## DynamoDB とは？

フルマネージドな NoSQLDB。
特徴としては以下の通り。

- 3 箇所の AZ にデータが自動的にレプリケーションされる仕様(=Single Point Of Failure の心配がない)
- データは必要に応じて自動的にパーティショニングされる
- ストレージの容量制限なし(=従量課金制であるが、ディスクやノードの増設は自動で行われる)
- スループットはそれぞれ読み書きごとにキャパシティを割り当てる方式(プロビジョンドスループット)でその割り当ての総量で従量課金、オートスケーリングも可。
- 基本的にはプロビジョンドで使うが、オンデマンドでの利用も可。主にどうしてもキャパシティを予測できない場合のサンプル期間としての利用に適している
- トランザクション機能や多少のクエリ機能はあるが、RDB に比べるとそれらの点では劣る反面上記のようにスケーラビリティの面では優れている

## DB としての特徴

![2021-10-18_00h30_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/ebcce59f-01ed-2d76-de4e-b8e1810c8d07.png)

[引用: AWS BlackBelt Amazon DynamoDB](https://d1.awsstatic.com/webinars/jp/pdf/services/20170809_AWS-BlackBelt-DynamoDB.pdf)

基本的には

Table-Items(RDS におけるレコード)-Attributes(RDS におけるカラム)という構造。
RDS と大きく異なる部分は Attributes に入るデータ型に制限がない(RDS であるなら例えば name のカラムは character でないといけない、id なら int……など)ことと、ソート用のキーの作り方にある。
詳しくは後述。

- スループットはそれぞれ読み書きごとにキャパシティを割り当てる方式(プロビジョンドスループット)でその割り当ての総量で従量課金
- 結果整合性モデルであり、その強度を強くするオプションもある(ただし、その場合読み出しのコストが上がる。通常の結果整合性は 0.5 リクエストに対してより強い結果整合性は 1 リクエストとなる)

- 自動で 3 つの AZ にレプリケーションされるので、Write 処理は 2 つの AZ において書き込みが完了した場合に初めて肯定応答が返る
- Key-Value 或いは Value の部分に JSON・XML も入れられることからドキュメント指向 DB としても利用できる
- データがパーティショニングされるので、RDS と比べて目的のデータへのアクセスが早い反面、複数のパーティションを跨ぐような検索には向かない(=故に複雑なクエリは NG)
- 主キーの構成要素となりうる Partition、Sort キーに該当する Attribute 以外のそれは事前の定義はいらない(put 時に自動で格納される)
- TTL を設定することで有効期限が切れている Item を自動削除できる(即解除ではなく 48 時間は期限切れ Item として表示されてしまうので Query 等で取得しないように設定する必要がある)
- DAX(DynamoDB 用のインメモリキャッシュ)を使って高速なキャッシングもできる

### Key と Index

Index はそれぞれプロビジョニングしたスループットやストレージとは別にさらにそれらを用意しないといけない。

#### Partition Key

まず Primary Key だが DynamoDB においては Partition Key と呼ばれる。
Attributes の中から最も一意になるような要素がこれに該当する、普通は id とか。
なぜ Partition Key と呼ばれるかというと前述の通り、DynamoDB は Table の概念はあるもののそれを必要に応じて分割するという機能があるためどの Partition にその Item が格納されているかを示す要素が必要になるからというわけである。
3 つの AZ にレプリケーションされるということと合わせると以下のような例がわかりやすい。

![2021-10-18_01h25_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/0cf968d4-7a41-3ac5-9ac4-c0ec47d80eae.png)
[引用: AWS BlackBelt Amazon DynamoDB](https://d1.awsstatic.com/webinars/jp/pdf/services/20170809_AWS-BlackBelt-DynamoDB.pdf)

#### Sort Key

次に Sort Key。
これは Partition Key と合わせてその Attribute でパーティション内をソートしたいといった場合に設定する。
また RDB における複合主キーのように Partition Key + Sort Key で 1 つの Primary Key と見なすという処理もできる。
具体的な例は下図の通り。

![2021-10-18_01h32_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/8ea4c2a1-f2a7-3d1b-e083-7fecc090610f.png)
[引用: AWS BlackBelt Amazon DynamoDB](https://d1.awsstatic.com/webinars/jp/pdf/services/20170809_AWS-BlackBelt-DynamoDB.pdf)

#### Local Secondary Index

Sort Key 以外に絞り込むを行いたい場合にこれを設定できる。
ただし、LSI はテーブル作成時にしか作成できないので設計時に考慮すべきである。
で、これを設定するとテーブルから Index が作成されてそこを検索できるようになる……という代物。
以下公式から引用。

![2021-10-18_02h43_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/dd8657f0-89f9-2d64-bac4-6699401445ed.png)
[引用: AWS デベロッパーガイドよりローカルセカンダリインデックス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html)

こういうテーブルがあったとする。
で、このテーブルの LastPostDateTime を LSI に指定すると以下の通り Index が作成される。

![2021-10-18_02h45_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/21a1e84d-2987-4973-2887-d1f650b57711.png)
[引用: AWS デベロッパーガイドよりローカルセカンダリインデックス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html)

これで必要に応じてこの Index も検索できるようになり、元のテーブルでは Scan などの全件検索等でやらなければいけなかったところが解消される……といったメリットが有る。

ただし、各 Partition Key ごとにそのテーブルとインデックスのサイズの合計が 10GB にならなければならないため必然的に Partition Key と LSI は相互に依存した作成数の制限がある。

#### Global Secondary Index

今度はそもそも設定した Partition Key ではソートに不都合が出るという場合にその代わりとして別の Attribute から設定し、それを基に index を作る。
これは前述までの通り、基本的には Partition(+Sort)key でしか DynamoDB では検索ができない(=パーティション毎の検索にしか向かない)という点の 1 つの解消策のような感じ。
以下公式から引用。

![2021-10-18_03h08_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/b6561315-4c05-005d-c87d-137c65d2ca07.png)
[引用: AWS デベロッパーガイドよりグローバルセカンダリインデックス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html)

こういうテーブルがあったとする。
このテーブルの場合は UserID、または UserID+GameTitle をキーとしてしか検索ができないので例えば

- Meteor Blasters のスコアだけを抽出したい(2 件データはあるが別パーティションに存在する)

といった条件の場合全件検索を行わないといけない。
これではコストもかかるし、アクセスも遅いという問題が生じる。
そこで、GSI として GameTitle、その Sort Key として TopScore を指定して Index を作ると以下のような Index ができる。

![2021-10-18_03h09_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428779/69b18fdb-53e8-3f9c-b842-bd4bd372f329.png)
[引用: AWS デベロッパーガイドよりグローバルセカンダリインデックス](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html)

これで前述の条件(他の GameTitle の場合でも)の際にはこちらの Index を使えば Scan を使わずに済むという寸法。
ちなみに UserID も射影といって自動的にテーブルから Index にコピーされる。

#### スループットについて

DynamoDB は前述までの通り基本的にスループットについて Read・Write それぞれに容量を割り当てるようにプロビジョニングする形でスループット性能を決めるのが特徴。
それぞれ Read Capacity Units、Write Capacity Units と呼ばれ計算方法は

RCU……1 秒あたりの読み込み項目数 × 項目のサイズ(4kB 単位で計算される)× 整合性強度(通常なら 1/2、強い整合性であれば 1)
WCU……1 秒あたりの読み込み項目数 × 項目のサイズ(1kB 単位で計算される)

例えば

- A = 2KB の Item を 1 秒あたり 1000 回強い整合性で読み込む = (2/4 → 1(小数点以下は切り上げ)) × 1000 = 1000RCU
- A を通常の結果整合性で読み込む = 1 × 1000 × 1/2 = 500RCU
- 6KB の Item を 1 秒あたり 1000 項目書き込む = 6000RCU
- 512B の Item を 1 秒あたり 1000 項目書き込む = (0.512 / 1 → 1(小数点以下は切り上げ)) × 1000 = 1000RCU

という計算になる。
前述の通り Auto Scaling ができるが、従量課金であることと急激な上昇でスケーリングが追いつかなかった場合はバーストキャパシティが消費されそれもなくなるとバーストキャパシティが貯まるまで処理は中断される。

[参考: DynamoDB Auto Scaling によるスループット容量の自動管理](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/AutoScaling.html)
[参考: バースト容量を効率的に使用する](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-partition-key-design.html#bp-partition-key-throughput-bursting)

#### パーティショニング

スループットに関連するのがパーティショニングの仕組み。
DynamoDB はプロビジョニングされたスループットを確保するためにパーティショニングを行う。
問題はパーティションが作られた場合、プロビジョニングされたスループットは各パーティションに均等に割り振られるという点。
これで各パーティションの性能を均等化しているが、Partition Key を適当に設計してしまうとアクセスされる Key に偏りが発生するためその仕組みが無意味になってしまうことに注意。
解決策としては Adaptive Capacity を導入することが考えられる。
Adaptive Capacity はあるパーティションにスループットが集中してしまった場合、通常均等で割り当てられるスループットを負荷がかかっているパーティションへ集中的に割り当てる仕組み。
[Amazon DynamoDB の adaptive capacity が不均一なデータアクセスパターンに対応する仕組み（または、なぜ DynamoDB について知っている情報が古くなっているのか）](https://aws.amazon.com/jp/blogs/news/how-amazon-dynamodb-adaptive-capacity-accommodates-uneven-data-access-patterns-or-why-what-you-know-about-dynamodb-might-be-outdated/)

##### パーティショニングとスループットの関係性

よってパーティションの数はストレージの容量とスループットで決まることになる。
具体的には

- 1 つのパーティションにつき、実際の RCU/3000RCU + 実際の WCU/1000WCU のキャパシティユニットを割り当てられる(もちろん RCU のみとかでも可)
- 1 つのパーティションには 10GB までデータを収められる
- ストレージサイズとスループット、どちらを基準してパーティション数を決めるかは大きい方を基準とする

例えば

スループット…… 5000RCU/3000RCU + 500WCU/1000WCU = 3(≒ 2.17 小数点の場合切り上げ)
ストレージサイズ…… 8GB/10GB = 0.2 = 1

この場合だとスループットの方を基準としてパーティションを 3 つ作るということになる。
なお、各パーティションのキャパシティユニットは

RCU…… 5000/3
WCU…… 500/3

となる。
よってこのことから

キャパシティが増加した場合……パーティション数が増える
キャパシティが減少した場合……各パーティションあたりのキャパシティが減少する(キャパシティエラーを起こす危険性がある)

という点には注意しないといけない。

## Item 操作について

DynamoDB は API を用いてアクセスする。
で、そのアクセスの仕方は扱う言語によって SDK が用意されているのでそれに従って行う。
Get/Put/Update/Delete は Partition Key と Sort Key を指定して対象 Item を決定する。

Put と Update に関しては以下も Topic として参照したい。
[【AWS CDK】AWS AppSync で DynamoDB を直接 UpdateItem するマッピングテンプレートを使いたい](https://dev.classmethod.jp/articles/aws-appsync-dynamodb-direct-update-item/)

### 読み込み操作

#### GetItem

> GetItem オペレーションは、指定された主キーを持つアイテムの属性セットを返します。一致するアイテムがない場合、GetItem はデータを返さず、応答に Item 要素はありません。
> GetItem は、デフォルトで eventually consistent read を提供します。アプリケーションで一貫性の高い読み取りが必要な場合は、ConsistentRead を true に設定します。強い一貫性のある読み取りは、最終的に一貫した読み取りよりも時間がかかる場合がありますが、常に最後に更新された値を返します。

つまり、一般的な Get 操作に当たるもの。
一貫性の高い読み取り(結果整合性をより強くした読み取り)はコスト増になるので注意。

#### BatchGetItem

> BatchGetItem オペレーションは、1 つまたは複数のテーブルから 1 つまたは複数のアイテムの属性を返します。要求されたアイテムは主キーで識別します。
> 1 回の操作で最大 16 MB のデータを取得でき、その中には 100 個のアイテムが含まれることもあります。
> BatchGetItem は、応答サイズの制限を超えた場合、テーブルのプロビジョニングされたスループットを超えた場合、または内部処理の障害が発生した場合に、部分的な結果を返します。
> 部分的な結果が返された場合、オペレーションは UnprocessedKeys の値を返します。この値を使用して、次に取得するアイテムから操作を再試行することができます。

つまり、目的の Item を一括で Get するアクション。
ただし

- 1 回の操作で得られるのは 16MB 分のデータ(Item 換算でおよそ 100)まで
- 16MB を超えた場合・キャパシティの上限に引っかかった場合・何らかのエラーが発生した場合は取得できた分だけデータが返る
- 中断された結果は UnprocessedKeys で返ってきてこれを利用して続きを再試行することができる

という特性がある。

#### TransactGetItems

> TransactGetItems は、1 つのアカウントとリージョン内の 1 つまたは複数のテーブル（インデックスからではない）から複数のアイテムを原始的に取得する同期操作です。TransactGetItems の呼び出しには、最大 25 個の TransactGetItem オブジェクトを含めることができ、それぞれのオブジェクトには、アカウントとリージョンのテーブルから取得するアイテムを指定する Get 構造が含まれます。
> TransactGetItems の呼び出しは、複数の AWS アカウントまたはリージョンのテーブルからアイテムを取得することはできません。トランザクション内のアイテムの合計サイズは、4MB を超えることはできません。
> DynamoDB は、以下のいずれかに該当する場合、TransactGetItems リクエスト全体を拒否します。
> 競合する操作が、読み取り対象のアイテムを更新中である。
> トランザクションを完了するためのプロビジョニング容量が不足している。
> 無効なデータ形式などのユーザーエラーが発生している。
> トランザクション内のアイテムのサイズの合計が 4MB を超えてはならない。

つまり

- ある 1 つのアカウントとその 1 リージョン内の DynamoDB リソース(1 テーブル or 複数のテーブル)から複数の Item を Get するアクション
- Index に対しては行えない
- Get された Item は TransactGetItem オブジェクトとして取得され、1 回のアクションで最大 25 個、4MB までのサイズで取得できる
- 読み取り対象のアイテムに競合している操作がある(ex. Update 処理が先に行われていた)・トランザクションを行うためのキャパシティの不足・その他エラーの場合リクエストは Deny となる

となる。

#### Query

> クエリ操作は、主キーの値に基づいてアイテムを検索します。複合プライマリ・キー（パーティション・キーとソート・キー）を持つテーブルまたはセカンダリ・インデックスにクエリを実行できます。
> KeyConditionExpression パラメータを使用して、パーティション・キーに特定の値を指定します。Query オペレーションは、そのパーティション・キーの値を持つテーブルまたはインデックスのすべてのアイテムを返します。オプションで、KeyConditionExpression にソート・キーの値と比較演算子を指定することで、Query オペレーションの範囲を狭めることができます。Query の結果をさらに絞り込むために、オプションで FilterExpression を指定することができます。FilterExpression は、結果の中でどの項目を返すかを決定します。その他の結果はすべて破棄されます。
> Query オペレーションは、常に結果セットを返します。一致する項目が見つからない場合、結果セットは空になります。結果を返さないクエリは、そのタイプの読み取り操作の最小数の読み取り容量ユニットを消費します。

つまり

- 複合主キーを持つテーブルまたは LSI・GSI に対して Partition Key を用いてクエリを実行できる
- Partition Key に加えて、Sort Key や比較演算子を用いることでより詳細なクエリを行え、FilterExpression オプションで返り値をフィルタリングできる
- 結果が空だった場合でも返り値として結果セットが返り、最小値ではあるが RCU を消費してしまう

となる。

#### Scan

> Scan オペレーションは、テーブルやセカンダリインデックスのすべてのアイテムにアクセスすることで、1 つ以上のアイテムとアイテムの属性を返します。DynamoDB が返すアイテムの数を少なくするには、FilterExpression オペレーションを用意します。
> スキャンされたアイテムの総数がデータセットの最大サイズ制限である 1MB を超えた場合、スキャンは停止し、結果は次の操作でスキャンを継続するために LastEvaluatedKey 値としてユーザーに返されます。結果には、制限を超えたアイテムの数も含まれます。スキャンの結果、フィルタ条件を満たすテーブルデータがないこともあります。
> 1 回の Scan 操作では、設定された最大アイテム数（Limit パラメータを使用した場合）または最大 1MB のデータを読み取り、FilterExpression を使用して結果にフィルタリングを適用します。応答に LastEvaluatedKey がある場合は、結果セットをページ分割する必要があります。詳細は、『Amazon DynamoDB Developer Guide』の「Paginate the Results」を参照してください。

つまり

- 全件取得のアクションに当たるが同時に全件走査を行う処理であるのでデータの総件数と取得したい件数にアンマッチがある場合は無駄なアクションとなってしまう
- Query と同じくフィルタリングはできるが、Key ではなく Attribute でしかフィルタリングできない(逆に Query は Key でしかできない)
- 全件取得のアクションではあるが、取得できるデータセットの最大サイズはフィルタリング前のデータセット換算で 1MB まで
- 1MB を超えた場合、スキャンが停止され LastEvaluatedKey としてそれまでの結果が返されるのでそれを使って再試行する必要がある
- Limit=10 の場合、Scan(10 件取得)→ フィルタリング……という処理になる、つまり取得した件数に対してフィルタリングが行われる
- LastEvaluatedKey が返ってきた場合、Pagenation を行う必要がある

### 書き込み

#### PutItem

> 新しい項目を作成するか、古い項目を新しい項目で置き換えます。新しいアイテムと同じ主キーを持つアイテムが指定したテーブルにすでに存在する場合、新しいアイテムは既存のアイテムを完全に置き換えます。
> 条件付きのプット操作（指定された主キーを持つアイテムが存在しない場合に新しいアイテムを追加する）や、既存のアイテムが特定の属性値を持つ場合に置き換えることができます。ReturnValues パラメータを使用して、同じ操作でアイテムの属性値を返すことができます
> アイテムを追加する際には、主キーの属性が唯一の必須属性となります。属性値は NULL は許容できません。
> 空の String および Binary 型の属性値は許可されています。
> String および Binary 型の属性値は、その属性がテーブルまたはインデックスのキー属性として使用されている場合、0 より大きな長さを持つ必要があります。
> セットタイプの属性値を空にすることはできません。
> 空の値を持つ無効なリクエストは ValidationException 例外で拒否されます。

つまり

- Put 処理を行うアクションで Create ではなく置き換え処理の場合は問答無用で新しい Item として置き換えを行う
- Put 処理については条件つき操作も一応はできる
- 必ず主キーが必要な操作になる

となる。

#### UpdateItem

> 既存のアイテムの属性を編集したり、まだ存在していない場合は新しいアイテムをテーブルに追加したりします。属性の値を入れたり、削除したり、追加したりすることができます。また、既存のアイテムに対して条件付きの更新を行うこともできます（新しい属性の名前と値のペアが存在しない場合はそれを挿入し、既存の名前と値のペアが特定の期待される属性値を持っている場合はそれを置き換える）。
> また、ReturnValues パラメータを使用して、同じ UpdateItem 操作でアイテムの属性値を返すことができます。

つまり

- Update 処理を行うアクション、PutItem との違いは置き換えではなく Attribute 単位での更新処理になる点
- 条件付き操作もできる

となる。

#### TransactGetItems

> TransactWriteItems は、最大 25 個のアクションリクエストをグループ化する同期書き込み操作です。これらのアクションは、異なるテーブルのアイテムを対象とすることができますが、異なる AWS アカウントやリージョンを対象とすることはできず、2 つのアクションが同じアイテムを対象とすることはできません。例えば、同じアイテムに対して ConditionCheck と Update の両方を行うことはできません。トランザクション内のアイテムの合計サイズは、4MB を超えることはできません。
> アクションは原始的に行われ、すべてが成功するか、すべてが失敗するかのどちらかにしかならない。
> アクションは以下のオブジェクトによって定義されます。
> Put - 新しいアイテムを書き込む PutItem オペレーションを開始します。この構造体は、書き込まれるアイテムの主キー、それを書き込むテーブルの名前、書き込みが成功するために満たさなければならないオプションの条件式、アイテムの属性のリスト、条件が満たされない場合にアイテムの属性を取得するかどうかを示すフィールドを指定します。
> Update - 既存のアイテムを更新するために UpdateItem オペレーションを開始します。この構造体は、更新されるアイテムの主キー、それが存在するテーブルの名前、更新が成功するために満たされなければならないオプションの条件式、更新される 1 つ以上の属性を定義する式、および条件が満たされない場合にアイテムの属性を取得するかどうかを示すフィールドを指定します。
> Delete - 既存のアイテムを削除する DeleteItem オペレーションを開始します。この構造体は、削除されるアイテムの主キー、それが存在するテーブルの名前、削除が成功するために満たす必要のあるオプションの条件式、および条件が満たされない場合にアイテムの属性を取得するかどうかを示すフィールドを指定します。
> ConditionCheck - トランザクションによって変更されていないアイテムに条件を適用します。この構造体は、チェックされるアイテムの主キー、それが存在するテーブルの名前、トランザクションが成功するために満たさなければならない条件式、および条件が満たされない場合にアイテムの属性を取得するかどうかを示すフィールドを指定します。

> DynamoDB は、以下のいずれかに該当する場合、TransactWriteItems リクエスト全体を拒否します。
> いずれかの条件式の条件が満たされていない。
> 進行中の操作が同じアイテムの更新を行っている。
> トランザクションを完了するためのプロビジョニング容量が不足している。
> トランザクションでの変更により、アイテムのサイズが大きくなりすぎた（400KB 以上）、ローカルセカンダリインデックス（LSI）が大きくなりすぎた、または同様の検証エラーが発生した。
> トランザクション内のアイテムのサイズの合計が 4MB を超えた。
> 無効なデータ形式などのユーザーエラーがあります。

つまり

- 最大 25 のアクションリクエストを 1 つのトランザクションとして一括に処理してくれるアクション(同期処理として実行される)
- ある 1 アカウント及びその 1 リージョンの DynamoDB リソースに対してしか実行できないが、異なるテーブルに対して行える
- 行えるアクションは Put・Update・Delete・ConditionCheck で同じ Item に 2 つのアクションを行うことはできない(1Item1 アクション)
- トランザクションとして処理するので、アクションのうち 1 つでもエラーが出た場合はリクエスト全体が Deny となる

となる。

#### BatchWriteItem

> BatchWriteItem オペレーションは、1 つまたは複数のテーブルに複数のアイテムを入れたり、削除したりします。BatchWriteItem を 1 回呼び出すと、最大で 16MB のデータを書き込むことができ、25 件の書き込みまたは削除要求を構成することができます。書き込まれる個々のアイテムは、最大で 400KB になります。(ただし、Update に当たるアクションは行えない)
> BatchWriteItem で指定された個々の PutItem と DeleteItem の操作は原始的ですが、BatchWriteItem 全体では原始的ではありません。テーブルのプロビジョニングされたスループットを超えたり、内部処理に失敗したりして、リクエストされたオペレーションが失敗した場合、失敗したオペレーションが UnprocessedItems レスポンス・パラメータで返されます。お客様は、リクエストを調査し、必要に応じてリクエストを再送することができます。通常は、BatchWriteItem をループで呼び出します。反復ごとに未処理のアイテムをチェックし、すべてのアイテムが処理されるまで、それらの未処理アイテムで新しい BatchWriteItem リクエストを送信します。
> 要求内のすべてのテーブルのプロビジョニングされたスループットが不十分なために、どのアイテムも処理できない場合、BatchWriteItem は ProvisionedThroughputExceededException を返します。
> BatchWriteItem を使用すると、Amazon EMR などからの大量のデータの書き込みや削除、他のデータベースから DynamoDB へのデータのコピーなどを効率的に行うことができます。このような大規模な操作のパフォーマンスを向上させるために、BatchWriteItem は、個々の PutItem や DeleteItem の呼び出しと同じようには動作しません。例えば、個々の Put や Delete リクエストに条件を指定することはできませんし、BatchWriteItem はレスポンスに削除されたアイテムを返しません。
> 並行処理をサポートするプログラミング言語を使用している場合は、スレッドを使用してアイテムを並行して書き込むことができます。アプリケーションには、スレッドを管理するために必要なロジックを組み込む必要があります。スレッドをサポートしていない言語では、指定したアイテムを 1 つずつ更新または削除する必要があります。いずれの場合も、BatchWriteItem は、指定されたプットおよびデリート操作を並列に実行するので、アプリケーションに複雑さを持ち込むことなく、スレッドプールアプローチのパワーを得ることができます。
> 並列処理によりレイテンシーは減少しますが、指定されたプットとデリートの各リクエストは、並列処理されてもされなくても、同じ数のライトキャパシティユニットを消費します。存在しないアイテムに対する削除操作は、1 つの書き込み容量ユニットを消費します。

> 以下の 1 つ以上の項目が当てはまる場合、DynamoDB はバッチライト操作全体を拒否します。

> BatchWriteItem リクエストで指定された 1 つ以上のテーブルが存在しない。
> 要求でアイテムに指定された主キー属性が、対応するテーブルの主キースキーマの属性と一致しない。
> 同じ BatchWriteItem 要求で、同じアイテムに対して複数の操作を行おうとしています。たとえば、同じ BatchWriteItem リクエストで、同じアイテムの配置と削除を行うことはできません。
> リクエストには、ハッシュキーとレンジキーが同一のアイテムが 2 つ以上含まれています（実質的には 2 つのプット操作になります）。
> バッチ内に 25 個以上のリクエストがあります。
> バッチ内の個々のアイテムが 400 KB を超えています。
> リクエストの合計サイズが 16MB を超える。

つまり

- 1 つのテーブルまたは複数のテーブルの複数の Item に対して Write 処理(Delete も含むが Update は行えない)を行えるアクション
- 個々の Item ごとに Put また DeleteItem を行いそれぞれに成功フラグは個別に管理されている(1 つのアクションが失敗しても全体がその時点で Deny になるわけではない)
- 失敗したアクション(リクエスト)は再送することができる
- 並列処理を行う場合レイテンシーの減少が期待できるが、並列処理に指定されたリクエストは実際に並列処理が行われたかに関わらずその分の WCU を消費する
- NULL な Item に対して削除処理を行ってしまった場合でも WCU を 1 消費する
- 条件によっては BatchWriteItem 全体が Deny になる(リクエストの合計サイズが 16MB を超える等 BatchWriteItem リクエスト自体に対してのエラーの場合)

となる。

#### DeleteItem

> テーブルの主キーで 1 つの項目を削除します。項目が存在する場合や、期待する属性値がある場合には、その項目を削除する条件付きの削除操作を行うことができます。
> 項目を削除するだけでなく、ReturnValues パラメータを使用して、同じ操作で項目の属性値を返すこともできます。
> 条件を指定しない限り、DeleteItem は冪等な操作であり、同じアイテムや属性に対して複数回実行しても、エラーレスポンスは発生しません。
> 条件付き削除は、特定の条件を満たした場合にのみアイテムを削除するのに便利です。これらの条件が満たされた場合、DynamoDB は削除を実行します。それ以外の場合は、アイテムは削除されません。

つまり

- Delete 処理を行うアクション
- 削除した値を返すこともできる
- 条件付き削除もできる

となる。

## ユースケースとして

### Lambda と一緒に使う

DynamoDB Stream を有効にしてモニタリングを行い、それと Lambda を併用して以下のようなケースで利用することが考えられる。

- 書き込みを検知したら値チェックを行い、Lambda を使って別テーブルの更新や通知を実行する
- DynamoDB の更新状況の監査ログを S3 に保存
- ゲームデータのランキング集計を非同期に実行する

### 書き込みに対する負荷調整

- SQS・Kinesis と併用して書き込みリクエストをキューやストリームで Worker として受け取り、それを必要に応じてスケーリングさせることで特定キー(パーティション)への偏りを防ぐ
- 同時アクセス数に応じてスループットを増加するか、Key・Item サイズ・リクエストレートごとに RCU/WCU を再検討する

## DynamoDB を利用する上で大切なこと

- アクセスパターン(実装したい機能とそれを実現するアクション)を洗い出しておく
- アクセスパターンを元に基本となる Primary Table を作る
- 洗い出したアクセスパターンから Query Condition(アクセスパターン毎にそれぞれどう Query を投げれば適切にデータを取得できるかを当該 Index・利用する Key・Filter の内容等々とセットにした表)を作成する

- Query Condition を参考に、Primary Table から GSI 等の Index を作る。

## 参考

[Amazon DynamoDB Deep Dive | AWS Summit Tokyo 2019](https://www.youtube.com/watch?v=16RYHfe89WY)
[【AWS Black Belt Online Seminar】Amazon DynamoDB Advanced Design Pattern](https://www.youtube.com/watch?v=Wd5gbLQ2a1E)