

# Amazon DynamoDB Transactions: 仕組み
<a name="transaction-apis"></a>

Amazon DynamoDB Transactions を使用すれば、複数のアクションをまとめてグループ化し、1 つのオールオアナッシングの `TransactWriteItems` または `TransactGetItems` オペレーションとして送信できます。以下のセクションでは、API オペレーション、容量管理、ベストプラクティス、DynamoDB でのトランザクション操作の使用に関する他の詳細について説明します。

**Topics**
+ [TransactWriteItems API](#transaction-apis-txwriteitems)
+ [TransactGetItems API](#transaction-apis-txgetitems)
+ [DynamoDB トランザクションの分離レベル](#transaction-isolation)
+ [DynamoDB でのトランザクション競合の処理](#transaction-conflict-handling)
+ [DynamoDB アクセラレーター (DAX) でのトランザクション API の使用](#transaction-apis-dax)
+ [トランザクションの容量管理](#transaction-capacity-handling)
+ [トランザクションのベストプラクティス](#transaction-best-practices)
+ [グローバルテーブルでのトランザクション API の使用](#transaction-integration)
+ [DynamoDB トランザクションと AWSLabs トランザクションクライアントライブラリ](#transaction-vs-library)

## TransactWriteItems API
<a name="transaction-apis-txwriteitems"></a>

`TransactWriteItems` は、最大 100 の書き込みアクションを 1 つのオールオアナッシングオペレーションにグループ化する、同期的でべき等な書き込みオペレーションです。これらのアクションは、同じ AWS アカウントおよび同じリージョン内の 1 つ以上の DynamoDB テーブルにある最大 100 個の異なる項目をターゲットにすることができます。トランザクション内のアイテムの合計サイズは 4 MB を超えることはできません。すべて成功するかどれも成功しないかのどちらとなるように、アトミックに実行されます。

**注記**  
 `TransactWriteItems` オペレーションは、含まれるすべてのアクションを正常に完了する必要があり、そうでない場合は変更がまったく行われないという点で `BatchWriteItem` オペレーションとは異なります。`BatchWriteItem` オペレーションでは、バッチ内の一部のアクションのみ成功し、他のアクションは成功しないことがあり得ます。
 インデックスを使用してトランザクションを実行することはできません。

同じトランザクション内の複数のオペレーションが同じ項目をターゲットとすることはできません。たとえば、同じトランザクション内で同じ項目に対して `ConditionCheck` を実行し、`Update` アクションも実行することはできません。

以下のタイプのアクションをトランザクションに追加できます。
+ `Put` — `PutItem` オペレーションを開始し、条件付きで、または条件をまったく指定せずに、新しい項目を作成するか、古い項目を新しい項目に置き換えます。
+ `Update` — `UpdateItem` オペレーションを開始し、既存の項目の属性を編集するか、まだ存在しない場合は新しい項目をテーブルに追加します。条件付きまたは条件なしで既存の項目で属性を追加、削除、更新するには、このアクションを使用します。
+ `Delete` — `DeleteItem` オペレーションを開始し、プライマリキーにより識別される 1 つの項目をテーブルで削除します。
+ `ConditionCheck` — 項目が存在することを確認するか、項目の特定の属性の条件を確認します。

DynamoDB でトランザクションが完了すると、変更のグローバルセカンダリインデックス (GSIs)、ストリーム、バックアップへの伝播が開始されます。この伝播は以下のとおり、徐々に行われます。同じトランザクションからのストリームレコードは異なるタイミングで表示されるため、他のトランザクションからのレコードとインターリーブする可能性があります。ストリームコンシューマーは、トランザクションの原子性や順番が保証されていると想定すべきではありません。

トランザクションで変更された項目のアトミックスナップショットを確保するには、TransactGetItems オペレーションを使用して、関連するすべての項目をまとめて読み取ります。このオペレーションを使用すると、データの一貫したビューが得られ、完了済みのトランザクションからのすべての変更が表示されるか、まったく表示されません。

伝播は即時に実行されるわけでないため、伝播中にテーブルがバックアップから復元された場合や([RestoreTableFromBackup](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_RestoreTableFromBackup.html))、特定時点のエクスポートが行われた場合 ([ExportTableToPointInTime](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ExportTableToPointInTime.html))、含まれるのが最近のトランザクション中に行われた変更の一部のみとなる場合もあります。

### べき等性
<a name="transaction-apis-txwriteitems-idempotency"></a>

`TransactWriteItems` 呼び出しを行ってリクエストが*冪等*であることを確認する場合、オプションでクライアントトークンを含めることができます。トランザクションを冪等にすると、接続のタイムアウトや他の接続問題に伴って同じオペレーションが複数回送信された場合に、アプリケーションエラーを防ぐことができます。

元の `TransactWriteItems` 呼び出しが成功した場合、同じクライアントトークンを持つ以降の `TransactWriteItems` 呼び出しは変更なしで正常に返ります。`ReturnConsumedCapacity` パラメータが設定されている場合、最初の `TransactWriteItems` 呼び出しは変更時に消費された書き込みキャパシティーユニットの数を返します。同じクライアントトークンを持つ以降の `TransactWriteItems` 呼び出しは、項目の読み取り時に消費された読み取りキャパシティユニットの数を返します。

**冪等性について重要な点**
+ クライアントトークンは、それを使用するリクエストが完了してから 10 分間有効です。10 分後、同じクライアントトークンを使用するリクエストは新しいリクエストとして扱われます。10 分が経過してから、同じリクエストに同じクライアントトークンを再利用しないでください。
+ 10 分間のべき等性期間内に同じクライアントトークンを使用してリクエストを繰り返すとき、他の一部のリクエストパラメータを変更した場合、DynamoDB は `IdempotentParameterMismatch` 例外を返します。

### 書き込みのエラー処理
<a name="transaction-apis-txwriteitems-errors"></a>

以下の条件下では、書き込みトランザクションが成功しません。
+ いずれかの条件式の条件が満たされていない場合。
+ 同じ `TransactWriteItems` オペレーション内の複数のアクションが同じ項目をターゲットとしているために、トランザクション検証エラーが発生した場合。
+ `TransactWriteItems` リクエストが、`TransactWriteItems` リクエスト内の 1 つ以上の項目に対する継続中の `TransactWriteItems` オペレーションと競合する場合。この場合、リクエストは `TransactionCanceledException` で失敗します。
+ トランザクションを完了するプロビジョンドキャパシティーが足りない場合。
+ 項目サイズが大きくなりすぎる (400 KB 超)、ローカルセカンダリインデックス (LSI) が大きくなりすぎる、またはトランザクションにより変更が加えられたために同様の検証エラーが発生した場合。
+ 無効なデータ形式などのユーザーエラーがある場合。

 `TransactWriteItems` オペレーションとの競合がどのように処理されるかについて詳しくは、「[DynamoDB でのトランザクション競合の処理](#transaction-conflict-handling)」を参照してください。

## TransactGetItems API
<a name="transaction-apis-txgetitems"></a>

`TransactGetItems` は、最大 100 個の `Get` アクションをまとめてグループ化する同期読み取りオペレーションです。これらのアクションは、同じ AWS アカウントおよびリージョン内の 1 つ以上の DynamoDB テーブルにある最大 100 個の異なる項目をターゲットにすることができます。トランザクション内の項目の合計サイズは 4 MB を超えることはできません。

`Get` アクションは、すべて成功するかすべて失敗するかのどちらとなるように、アトミックに実行されます。
+ `Get` — `GetItem` オペレーションを開始し、指定されたプライマリキーを持つ項目の属性のセットを取得します。一致する項目が見つからない場合、`Get` はデータを返しません。

### 読み込みのエラー処理
<a name="transaction-apis-txgetitems-errors"></a>

以下の条件下では、読み取りトランザクションが成功しません。
+ `TransactGetItems` リクエストが、`TransactWriteItems` リクエスト内の 1 つ以上の項目に対する継続中の `TransactGetItems` オペレーションと競合する場合。この場合、リクエストは `TransactionCanceledException` で失敗します。
+ トランザクションを完了するプロビジョンドキャパシティーが足りない場合。
+ 無効なデータ形式などのユーザーエラーがある場合。

 `TransactGetItems` オペレーションとの競合がどのように処理されるかについて詳しくは、「[DynamoDB でのトランザクション競合の処理](#transaction-conflict-handling)」を参照してください。

## DynamoDB トランザクションの分離レベル
<a name="transaction-isolation"></a>

トランザクションオペレーション (`TransactWriteItems` または `TransactGetItems`) と他のオペレーションの分離レベルは、次のとおりです。

### SERIALIZABLE
<a name="transaction-isolation-serializable"></a>

*直列化可能*分離レベルでは、複数の同時オペレーションの結果は、前のオペレーションが完了するまでオペレーションが開始されない場合と同じになります。

以下のタイプのオペレーション間には、直列化可能分離があります。
+ トランザクションオペレーションと標準書き込みオペレーション (`PutItem`、`UpdateItem`、または `DeleteItem`) の間。
+ トランザクションオペレーションと標準読み取りオペレーション (`GetItem`) の間。
+ `TransactWriteItems` オペレーションと `TransactGetItems` オペレーションの間。

トランザクションオペレーション間と `BatchWriteItem` オペレーション内の個々の標準書き込み間には直列化可能分離がありますが、トランザクションとユニットとしての `BatchWriteItem` オペレーションの間には直列化可能分離はありません。

同様に、トランザクションオペレーションと `GetItems` オペレーションの個別の `BatchGetItem` 間の分離レベルは直列化可能です。ただし、トランザクションとユニットとしての `BatchGetItem` オペレーション間の分離レベルは*コミット済み読み取り*です。

単一の `GetItem` リクエストは、`TransactWriteItems` リクエストの前または後に行う 2 つの方法のいずれかで、`TransactWriteItems` リクエストに関してシリアル化することができます。同時実行 `TransactWriteItems` リクエストのキーに対する複数の `GetItem` リクエストは、任意の順序で実行できるため、結果は*読み込みがコミット*されます。

たとえば、項目 A と項目 B の `GetItem` リクエストが、項目 A と項目 B の両方を変更する `TransactWriteItems` リクエストと同時に実行される場合、次の 4 つの可能性があります。
+ 両方の `GetItem` リクエストは、`TransactWriteItems` リクエストの前に実行されます。
+ 両方の `GetItem` リクエストは、`TransactWriteItems` リクエストの後に実行されます。
+ 項目 A の `GetItem` リクエストは、`TransactWriteItems` リクエストの前に実行されます。項目 B の場合、`GetItem` は `TransactWriteItems` の後に実行されます。
+ 項目 B の `GetItem` リクエストは、`TransactWriteItems` リクエストの前に実行されます。項目 A の場合、`GetItem` は `TransactWriteItems` の後に実行されます。

複数の `GetItem` リクエストにシリアル化可能な分離レベルが望ましい場合は、`TransactGetItems` を使用してください。

処理中に同じトランザクション書き込みリクエストの一部であった複数の項目に対して非トランザクション読み取りが行われた場合、一部の項目の新しい状態と他の項目の古い状態を読み取ることができる可能性があります。トランザクション書き込みリクエストに含まれていたすべての項目の新しい状態を読み取ることができるのは、トランザクションが完了したことを示すトランザクション書き込みの応答が成功した場合のみです。

トランザクションが正常に完了し、応答が受信されると、DynamoDB の結果整合性モデルにより、*結果整合性のある*読み込みオペレーションが短期間、古い状態を返す可能性があります。トランザクションの直後に最新のデータを確実に読み取るには、`ConsistentRead` を true に設定して、[*強力な整合性のある*](HowItWorks.ReadConsistency.md#HowItWorks.ReadConsistency.Strongly)読み込みを使用する必要があります。

### コミット済み読み取り
<a name="transaction-isolation-read-committed"></a>

*コミット済み読み取り*分離により、読み取りオペレーションは常に項目のコミット済み値を返します。つまり、読み取りによって、最終的に成功しなかったトランザクション書き込みの状態を表すビューが項目に表示されることはありません。コミット済み読み取り分離では、読み取りオペレーションの直後に項目の変更が防止されません。

分離レベルは、トランザクションオペレーションと、複数の標準読み取り (`BatchGetItem`、`Query`、または `Scan`) が関係する読み取りオペレーションの間ではコミット済み読み取りです。トランザクション書き込みにより `BatchGetItem`、`Query`、または `Scan` オペレーションの途中で項目が更新された場合、その後の読み取りオペレーションの部分では、新しくコミットされた値 (`ConsistentRead)` を使用) か、場合によってはそれ以前のコミット済み値 (結果整合性のある読み込み) を返します。

### オペレーションの概要
<a name="transaction-isolation-table"></a>

以下の表は、トランザクションオペレーション (`TransactWriteItems` または `TransactGetItems`) と他のオペレーションの間の分離レベルをまとめたものです。


| オペレーション | 分離レベル | 
| --- | --- | 
| `DeleteItem` | *直列化可能* | 
| `PutItem` | *直列化可能* | 
| `UpdateItem` | *直列化可能* | 
| `GetItem` | *直列化可能* | 
| `BatchGetItem` | *コミット済み読み取り*\* | 
| `BatchWriteItem` | *直列化不可*\* | 
| `Query` | *コミット済み読み取り* | 
| `Scan` | *コミット済み読み取り* | 
| 他のトランザクションオペレーション | *直列化可能* | 

アスタリスク (\*) が付いたレベルは、ユニットとしてオペレーションに適用されます。ただし、これらのオペレーション内の個々のアクションの分離レベルは*直列化可能*です。

## DynamoDB でのトランザクション競合の処理
<a name="transaction-conflict-handling"></a>

トランザクション競合は、トランザクション内の項目に対する項目レベルの同時リクエスト中に発生する場合があります。トランザクション競合は、次のシナリオで発生する場合があります。
+ 項目に対する `PutItem`、`UpdateItem`、または `DeleteItem` リクエストが、同じ項目を含む継続中の `TransactWriteItems` リクエストと競合する。
+ `TransactWriteItems` リクエスト内の項目が、継続中の別の `TransactWriteItems` リクエストの一部である。
+ `TransactGetItems` リクエスト内の項目が、継続中の `TransactWriteItems`、`BatchWriteItem`、`PutItem`、`UpdateItem`、または `DeleteItem` リクエストの一部である。

**注記**  
`PutItem`、`UpdateItem`、または `DeleteItem` リクエストが拒否された場合、リクエストは `TransactionConflictException` で失敗します。
`TransactWriteItems` または `TransactGetItems` 内の項目レベルのリクエストが拒否された場合、リクエストは `TransactionCanceledException` で失敗します。そのリクエストが失敗した場合、AWS SDK はリクエストを再試行しません。  
AWS SDK for Java を使用している場合、例外には `TransactItems` リクエストパラメータの項目リスト通りに順序付けられた [CancellationReasons](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CancellationReason.html) のリストが含まれます。他の言語の場合、リストの文字列表現が例外のエラーメッセージに含まれます。
継続中の `TransactWriteItems` オペレーションまたは `TransactGetItems` オペレーションが同時 `GetItem` リクエストと競合している場合、両方のオペレーションが成功する可能性があります。

[TransactionConflict CloudWatch メトリクス](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/metrics-dimensions.html)は、項目レベルのリクエストが失敗するたびに増分されます。

## DynamoDB アクセラレーター (DAX) でのトランザクション API の使用
<a name="transaction-apis-dax"></a>

`TransactWriteItems` と `TransactGetItems` が、どちらも DynamoDB と同じ分離レベルで DynamoDB アクセラレーター (DAX) でサポートされています。

`TransactWriteItems` は DAX を介して書き込みます。DAX は DynamoDB に `TransactWriteItems` コールを渡し、応答を返します。書き込み後にキャッシュにデータを追加するために、DAX は、`TransactWriteItems` オペレーション内の各項目に対してバックグラウンドで `TransactGetItems` をコールします。これにより、追加の読み込み容量単位が消費されます。（詳しくは、[トランザクションの容量管理](#transaction-capacity-handling) を参照してください）。この機能により、アプリケーションロジックをシンプルに保ち、トランザクション処理と非トランザクション処理の両方に DAX を使用できます。

`TransactGetItems` コールは、項目がローカルにキャッシュされることなく DAX を通過します。これは、DAX の強い整合性のある読み込み API と同じです。

## トランザクションの容量管理
<a name="transaction-capacity-handling"></a>

DynamoDB テーブルのトランザクションを有効にするために、追加コストはかかりません。料金の支払いは、トランザクションの一部である読み込みまたは書き込みに対してのみ行われます。DynamoDB は、トランザクション内の各項目の基になっている 2 つの読み込みまたは書き込みを実行します。1 つはトランザクションの準備用で、もう 1 つはトランザクションのコミット用です。基になっている 2 つの読み込み/書き込みオペレーションは、Amazon CloudWatch メトリクスに表示されます。

容量をテーブルにプロビジョニングするとき、トランザクション API により要求される追加の読み取りと書き込みを計画してください。たとえば、アプリケーションが 1 秒あたり 1 件のトランザクションを実行し、各トランザクションは 500 バイトの項目を 3 個テーブルに書き込むとします。各項目には、2 つの書き込みキャパシティーユニット (WCU) が必要です。1 つはトランザクションの準備用で、もう 1 つはトランザクションのコミット用です。したがって、テーブルには WCU を 6 個プロビジョニングする必要があります。

前の例で DynamoDB アクセラレーター (DAX) を使用していた場合、`TransactWriteItems` のコールで項目ごとに 2 つの読み込み容量単位 (RCU) も使用します。したがって、テーブルには追加の RCU を 6 個プロビジョニングする必要があります。

同様に、アプリケーションが 1 秒あたり 1 件の読み込みトランザクションを実行し、各トランザクションは 500 バイトの項目を 3 個テーブルで読み取る場合、読み込み容量単位 (RCU) を 6 個テーブルにプロビジョンする必要があります。各項目を読み取るには、2 つの RCU が必要です。1 つはトランザクションの準備用で、もう 1 つはトランザクションのコミット用です。

さらに、SDK のデフォルトの動作は、`TransactionInProgressException` 例外が発生した場合にトランザクションを再試行します。これらの再試行で消費される追加の読み込みキャパシティーユニット (RCU) を計画してください。同じことは、`ClientRequestToken` を使用して独自のコードでトランザクションを再試行しようとする場合に当てはまります。

## トランザクションのベストプラクティス
<a name="transaction-best-practices"></a>

DynamoDB トランザクションの使用時に推奨される以下の手法を検討してください。
+ テーブルで自動スケーリングを有効にするか、トランザクションの項目ごとに 2 つの読み取りまたは書き込みオペレーションを実行するのに十分なスループット容量をプロビジョニングしたことを確認します。
+ AWS によって提供された SDK を使用していない場合、`TransactWriteItems` 呼び出しを行うときに `ClientRequestToken` 属性を含め、リクエストがべき等となるようにします。
+ 必要でない場合は、トランザクションにオペレーションをまとめてグループ化しないでください。たとえば、10 個のオペレーションを持つ 1 つのトランザクションを、アプリケーションの正確性を低下させずに複数のトランザクションに分割できる場合、トランザクションを分割することをお勧めします。トランザクションをシンプルにするとスループットが向上し、成功する可能性が高まります。
+ 同じ項目を同時に更新する複数のトランザクションによって、トランザクションをキャンセルする競合が発生する可能性があります。そのような競合を最小限に抑えるため、データモデリングには以下の DynamoDB のベストプラクティスをお勧めします。
+ 属性のセットが 1 つのトランザクションの一部として複数の項目間で頻繁に更新される場合、属性を 1 つの項目にグループ化し、トランザクションのスコープを減らすことを検討してください。
+ データを大量に取り込むためにトランザクションを使用しないでください。一括書き込みには、`BatchWriteItem` の使用をお勧めします。

## グローバルテーブルでのトランザクション API の使用
<a name="transaction-integration"></a>

トランザクションオペレーションは、書き込み API が最初に呼び出された AWS リージョン内でのみ、不可分性、一貫性、分離性、および耐久性 (ACID) を保証します。グローバルテーブルのリージョン間では、トランザクションはサポートされていません。たとえば、米国東部 (オハイオ) リージョンと米国西部 (オレゴン) リージョンにレプリカを含むグローバルテーブルがあり、米国東部 (バージニア北部) リージョンで `TransactWriteItems` オペレーションを実行するとします。変更がレプリケートされると、米国西部 (オレゴン) リージョンで部分的に完了したトランザクションを確認できます。変更は、ソースリージョンでコミットされた後でのみ、他のリージョンにレプリケートされます。

## DynamoDB トランザクションと AWSLabs トランザクションクライアントライブラリ
<a name="transaction-vs-library"></a>

DynamoDB トランザクションには、[AWSLabs](https://github.com/awslabs) トランザクションクライアントライブラリを置き換えるより費用対効率、堅牢性、パフォーマンスに優れた手段が用意されています。ネイティブのサーバー側トランザクション API を使用するようにアプリケーションを更新することをお勧めします。