

# DAX および DynamoDB の整合性モデル
<a name="DAX.consistency"></a>

Amazon DynamoDB Accelerator (DAX) は、DynamoDB テーブルにキャッシュを追加するプロセスを簡素化するために設計された書き込みスルーキャッシュサービスです。DAX は DynamoDB とは別に動作するため、アプリケーションが意図どおりに動作することを確認するには DAX と DynamoDB の両方の整合性モデルを理解することが重要です。

多くのユースケースでは、アプリケーションでの DAX の使用方法が、DAX クラスター内のデータの整合性、および DAX と DynamoDB 間のデータの整合性に影響します。

**Topics**
+ [DAX クラスターノード間の整合性](#DAX.consistency.nodes)
+ [DAX 項目キャッシュの動作](#DAX.consistency.item-cache)
+ [DAX クエリキャッシュの動作](#DAX.consistency.query-cache)
+ [強力な整合性のあるトランザクション読み込み](#DAX.consistency.strongly-consistent-reads)
+ [ネガティブキャッシング](#DAX.consistency.negative-caching)
+ [書き込みの方法](#DAX.consistency.strategies-for-writes)

## DAX クラスターノード間の整合性
<a name="DAX.consistency.nodes"></a>

アプリケーションの高可用性を実現するには、DAX クラスターのプロビジョニングには少なくとも 3 つのノードを使用してください。次にこれらのノードをリージョン内の複数のアベイラビリティーゾーンに配置します。

クラスターの実行中、データがクラスター内のすべてのノードにレプリケートされます (複数のノードをプロビジョニング済みの場合)。DAX を使用して `UpdateItem` を正常に実行するアプリケーションの場合を考えてみましょう。このアクションにより、プライマリノードの項目キャッシュが新しい値で変更されます。この値は、クラスター内の他のすべてのノードにレプリケートされます。このレプリケーションの結果、整合性が保たれます。完了までにかかる時間は通常は 1 秒未満です。

このシナリオでは、2 つのクライアントが同じ DAX クラスターから同じキーを読み取った時に、それぞれのクライアントがアクセスしたノードによって、異なる値を受け取る可能性があります。更新がクラスター内のすべてのノードで完全にレプリケートされると、ノードはすべて整合性がとれます。(この動作は、DynamoDB の結果整合性特性と似ています。)

DAX を使用するアプリケーションを構築する場合、そのアプリケーションは結果整合性データを許容するように設計されている必要があります。

## DAX 項目キャッシュの動作
<a name="DAX.consistency.item-cache"></a>

各 DAX クラスターには、2 つの異なるキャッシュ (*項目*キャッシュと*クエリ*キャッシュ) があります。詳細については、「[DAX: 仕組み](DAX.concepts.md)」を参照してください。

このセクションでは、DAX 項目キャッシュに対する読み込みおよび書き込みの整合性の実装について取り扱います。

### 読み込みの整合性
<a name="DAX.consistency.item-cache.reads"></a>

DynamoDB では、`GetItem` オペレーションはデフォルトで結果整合性のある読み込みを行います。DynamoDB クライアントで `UpdateItem` を使用するとします。すぐ後に同じ項目を読み取ろうとすると、更新前のデータが表示される場合があります。これは、DynamoDB ストレージロケーション全体への伝播が遅れるためです。整合性は通常、数秒以内に達成します。そのため、読み込みを再試行すると、更新された項目が表示されます。

DAX クライアントで `GetItem` を使用する場合、オペレーション (この場合は結果整合性のある読み込み) は次のように進行します。

![\[項目を更新するための番号付きのステップを示すワークフロー図。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/dax-item-cache.png)


1. DAX クライアントが `GetItem` リクエストを発行します。DAX は、項目キャッシュからリクエストされた項目を読み取ろうとします。項目がキャッシュ内にある場合 (*キャッシュヒット*)、DAX によりアプリケーションに返されます。

1. 項目がない場合 (*キャッシュミス*)、DAX は DynamoDB に対して結果整合性 `GetItem` オペレーションを実行します。

1. DynamoDB からリクエストされた項目が返され、DAX で項目キャッシュに保存されます。

1. DAX はその項目をアプリケーションに返します。

1. (非表示) DAX クラスターに複数のノードがある場合、項目がクラスター内の他のすべてのノードにレプリケートされます。

項目は、キャッシュの有効期限 (TTL) 設定および最も長い間使用されていない (LRU) アルゴリズムによりますが、DAX 項目キャッシュに残ります。詳細については、「[DAX: 仕組み](DAX.concepts.md)」を参照してください。

ただし、この期間は、DAX は項目を DynamoDB から再ロードしません。他のユーザーが DynamoDB クライアントを使用して、DAX 全体をバイパスして項目を更新すると、DAX クライアントを使用した `GetItem` リクエストで、DynamoDB クライアントを使用した `GetItem` リクエストとは異なる結果が呼び出されます。このシナリオでは、DAX と DynamoDB は、DAX 項目の TTL が期限切れになるまで、同じキーに対して矛盾する値を保持します。

アプリケーションが をバイパスして基礎となる テーブルのデータを変更する場合は、そのアプリケーションはデータの不整合が発生する可能性を予期し許容する必要があります。

**注記**  
`GetItem` に加えて、DAX クライアントは `BatchGetItem` リクエストもサポートします。`BatchGetItem` は基本的には 1 つ以上の `GetItem` リクエストをまとめるラッパーです。そのため、DAX ではそれぞれを個別の `GetItem` オペレーションとして扱います。

### 書き込みの整合性
<a name="DAX.consistency.item-cache.writes"></a>

DAX は書き込みスルーキャッシュであり、DAX の項目キャッシュが基礎となる DynamoDB テーブルに対して整合性を保つためのプロセスを簡易化します。

DAX クライアントは、DynamoDB と同じ書き込み API オペレーションをサポートします (`PutItem`、`UpdateItem`、`DeleteItem`、`BatchWriteItem`、および `TransactWriteItems`)。DAX クライアントでこれらのオペレーションを使用すると、項目は DAX と DynamoDB の両方で変更されます。DAX は、その TTL 値に関係なく項目キャッシュ内の項目を更新します。

たとえば、DAX クライアントから `GetItem` リクエストを発行して、`ProductCatalog` テーブルから項目を読み取るとします。(パーティションキーは `Id` で、ソートキーはありません。) `Id` が `101` である項目を取得します。その項目の `QuantityOnHand` 値は `42` です。DAX は特定の TTL で項目を項目キャッシュに保存します。この例では、TTL が 10 分とします。3 分後、別のアプリケーションが DAX クライアント使用して、`QuantityOnHand` 値が `41` になるように同じ項目を更新します。その後はその項目が更新されないとすると、その後 10 分間の同じ項目に対する後続の読み込みは、`QuantityOnHand` に対してキャッシュされた値を返します (`41`)。

#### DAX の書き込み処理方法
<a name="DAX.consistency.item-cache.write-consistency.processing-writes"></a>

DAX は、高パフォーマンスの読み込みを必要とするアプリケーションを対象としています。書き込みスルーキャッシュとして、DAX は書き込みを DynamoDB に同期的に渡し、その結果生じた更新をクラスター内のすべてのノードで項目キャッシュに自動的に非同期にレプリケートします。キャッシュ無効化ロジックを管理する必要はありません。DAX がお客様の代わりにそれを行います。

DAX では、`PutItem`、`UpdateItem`、`DeleteItem`、`BatchWriteItem`、および `TransactWriteItems` の各書き込みオペレーションがサポートされます。

これらの `PutItem`、`UpdateItem`、`DeleteItem`、または `BatchWriteItem` リクエストを DAX に送信すると、次のことが発生します。
+ DAX が DynamoDB にリクエストを送信します。
+ DynamoDB が DAX に応答し、書き込みが成功したことが確認されます。
+ DAX がキャッシュ項目に項目を書き込みます。
+ DAX がリクエスタに成功を返します。

これらの `TransactWriteItems` リクエストを DAX に送信すると、次のことが発生します。
+ DAX が DynamoDB にリクエストを送信します。
+ DynamoDB が DAX に応答し、トランザクションが完了したことが確認されます。
+ DAX がリクエスタに成功を返します。
+ バックグラウンドで、DAX は項目キャッシュに項目を格納するために、`TransactWriteItems` リクエスト内の各項目に対して `TransactGetItems` リクエストを行います。`TransactGetItems` は、[直列化可能分離](transaction-apis.md#transaction-isolation-serializable)を確実にするために使用されます。

スロットルなどの理由で DynamoDB への書き込みが失敗した場合、項目は DAX にキャッシュされません。失敗の例外がリクエスタに返されます。これにより、データが先に DynamoDB に正常に書き込まれない限り、DAX には書き込まれません。

**注記**  
DAX へのすべての書き込みは、項目キャッシュの状態を変更します。ただし、項目キャッシュへの書き込みはクエリキャッシュに影響しません。(DAX の項目キャッシュとクエリキャッシュは異なる目的で使用されます。オペレーションは互いに独立しています。)

## DAX クエリキャッシュの動作
<a name="DAX.consistency.query-cache"></a>

DAX は、`Query` および `Scan` リクエストからの結果をクエリキャッシュにキャッシュします。ただし、これらの結果は項目キャッシュにまったく影響しません。アプリケーションが `Query` または `Scan` 要求を発行すると、結果セットは項目キャッシュではなく、クエリキャッシュに保存されます。`Scan` オペレーションを実行して項目キャッシュを「ウォームアップ」することはできません。項目キャッシュとクエリキャッシュは別々の存在です。

### クエリ-更新-クエリの整合性
<a name="DAX.consistency.query-cache.read-consistency"></a>

項目キャッシュ、または基礎となる DynamoDB テーブルに対する更新によって、クエリキャッシュに保存されている結果が無効化されたり変更されたりすることはありません。

説明のため、以下のシナリオを想定してください。アプリケーションは、パーティションキーとして `DocumentRevisions` を使用し、ソートキーとして `DocId` を使用する `RevisionNumber` テーブルを使用しています。

1. `5` より大きいか等しい `Query` を持つすべての項目に対して、クライアントが `DocId` `101` に `RevisionNumber` を発行します。結果を DAX クエリキャッシュに保存し、ユーザーに結果セットを返します。

1. クライアントは、`PutItem` 値が `DocId` の `101` `RevisionNumber` に対して `20` リクエストを発行します。

1. クライアントはステップ 1 の説明と同じ `Query` を発行します (`DocId` `101` および `RevisionNumber` >= `5`)。

このシナリオでは、ステップ 3 で発行された `Query` のキャッシュ結果セットは、ステップ 1 でキャッシュされた結果セットと同じです。これは、DAX では `Query` または `Scan` の結果セットを個別の項目に対する更新に基づいて無効化しないためです。ステップ 2 の `PutItem` オペレーションは、`Query` の TTL の有効期限が切れたときにのみ DAX クエリキャッシュに反映されます。

アプリケーションでは、クエリキャッシュの TTL 値、およびクエリキャッシュと項目キャッシュ間の結果の不一致をアプリケーションで許容できる期間を考慮に入れる必要があります。

## 強力な整合性のあるトランザクション読み込み
<a name="DAX.consistency.strongly-consistent-reads"></a>

強い整合性のある `GetItem`、`BatchGetItem`、`Query`、または `Scan` リクエストを実行するには、`ConsistentRead` パラメータを true に設定します。DAX は強力な整合性のある読み込みリクエストを DynamoDB に渡します。DynamoDB から返答を受け取ると、DAX はクライアントに結果を返しますが、結果をキャッシュしません。DAX は DynamoDB と緊密には連携していないため、単独では強い整合性のある読み込みを提供できません。このため、DAX からの後続の読み込みは、結果整合性のある読み込みである必要があります。そして、後続の強い整合性のある読み込みはすべて DynamoDB に渡す必要があります。

DAX は、`TransactGetItems` リクエストを一貫性のある読み込みと同じ方法で処理します。DAX はすべての `TransactGetItems` リクエストを DynamoDB に渡します。DynamoDB から返答を受け取ると、DAX はクライアントに結果を返しますが、結果をキャッシュしません。

## ネガティブキャッシング
<a name="DAX.consistency.negative-caching"></a>

DAX は、項目キャッシュとクエリキャッシュの両方でネガティブキャッシュエントリをサポートします。*ネガティブキャッシュエントリ*は、DAX が基礎となる DynamoDB テーブルでリクエストされた項目を見つけられなかったときに発生します。DAX は、エラーを生成する代わりに空の結果をキャッシュし、その結果をユーザーに返します。

たとえば、アプリケーションが `GetItem` リクエストを DAX クラスターに送信し、DAX 項目キャッシュの中に一致する項目がなかったとします。この場合、DAX は基礎となる DynamoDB テーブルから対応する項目を読み込むことになります。項目が DynamoDB に存在しない場合、DAX は空の項目を項目キャッシュに保存し、その空の項目をアプリケーションに返します。次に、アプリケーションが別の `GetItem` リクエストを同じ項目に送信するとします。DAX は、項目キャッシュ内に空の項目を見つけ、それをすぐにアプリケーションに返します。DynamoDB は一切参照されません。

ネガティブキャッシュエントリは、項目 TTL の有効期限が切れるか、LRU が呼び出されるか、`PutItem`、`UpdateItem`、または `DeleteItem` を使用して項目が変更されるまで、DAX 項目キャッシュに残ります。

DAX クエリキャッシュも同様の方法でネガティブキャッシュ結果を処理します。アプリケーションが `Query` または `Scan` を実行し、DAX クエリキャッシュにキャッシュされた結果がない場合、DAX は DynamoDB にリクエストを送信します。結果セットに一致する項目がない場合、DAX は空の項目セットをクエリキャッシュに保存し、その空の結果セットをアプリケーションに返します。それ以降の `Query` または `Scan` リクエストは、その結果セットの TTL の有効期限が切れるまで、同じ (空の) 結果セットを呼び出します。

## 書き込みの方法
<a name="DAX.consistency.strategies-for-writes"></a>

DAX の書き込みスルー動作は、多くのアプリケーションパターンに適しています。ただし、書き込みスルーモデルが適切ではないアプリケーションパターンもいくつかあります。

レイテンシーの影響を受けやすいアプリケーションの場合、DAX を介した書き込みでは余分なネットワークホップが発生します。そのため、DAX への書き込みは、DynamoDB への直接の書き込みよりも少し遅くなります。書き込みレイテンシーが重要なアプリケーションの場合は、代わりに DynamoDB に直接書き込むことで、レイテンシーを軽減できます。詳細については、「[書き込み迂回](#DAX.consistency.strategies-for-writes.write-around)」を参照してください。

書き込み負荷の高いアプリケーション (バルクデータのロードを実行するものなど) では、すべてのデータを DAX 経由で書き込むのは望ましくない場合があります。アプリケーションで読み込むのはそのデータのほんのわずかな割合であるためです。大量のデータを DAX 経由で書き込む場合、読み取られる新しい項目用にキャッシュ内に空きを作るため、LRU アルゴリズムが実行される必要があります。そのため、読み込みキャッシュとしての DAX の効果が低下します。

DAX に項目を書き込む場合、項目キャッシュの状態が新しい項目に対応するように変更されます。(たとえば、DAX は新しい項目用の空きを作るために、古いデータを項目キャッシュから削除する必要がある場合があります。) 新しい項目は、キャッシュの LRU アルゴリズムおよびキャッシュの TTL 設定によりますが、項目キャッシュに残ります。項目が項目キャッシュに保持される限り、DAX はその項目を DynamoDB から再ロードしません。

### 書き込みスルー
<a name="DAX.consistency.strategies-for-writes.write-through"></a>

DAX 項目キャッシュには書き込みスルーポリシーが導入されています。詳細については、「[DAX の書き込み処理方法](#DAX.consistency.item-cache.write-consistency.processing-writes)」を参照してください。

項目を書き込むと、DAX はキャッシュされた項目が DynamoDB に存在する項目と同期されていることを確認します。これは、項目を書き込み直後に再ロードする必要があるアプリケーションにとって有用です。ただし、他のアプリケーションが DynamoDB テーブルに直接書き込んだ場合、DAX 項目キャッシュ内のその項目は、DynamoDB と同期しなくなります。

例として、`ProductCatalog` テーブルを使用している 2 人のユーザー (Alice と Bob) がいるとします。Alice は DAX を使用してテーブルにアクセスしますが、Bob は DAX をバイパスして、DynamoDB 内のテーブルに直接アクセスします。

![\[ユーザー の Alice と Bob が DAX と DynamoDB を使用してテーブルにアクセスする方法の番号付きのステップを示すワークフロー図。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/dax-consistency-alice-bob.png)


1. Alice が `ProductCatalog` テーブル内の項目を更新します。DAX が DynamoDB にリクエストを転送し、更新が正常終了します。DAX は、項目を項目キャッシュに書き込み、Alice に成功応答を返します。この時点から、項目が最終的にキャッシュから削除されるまで、この項目を DAX から読み込んだユーザーには Alice が更新した項目が表示されます。

1. しばらくして、Bob は Alice が書き込んだのと同じ `ProductCatalog` 項目を更新します。ただし、ボブは項目を DynamoDB で直接更新します。DAX では、DynamoDB 経由の更新に応じて項目キャッシュが自動的に更新されません。そのため、DAX のユーザーからは、Bob の更新が見えません。

1. Alice が DAX からその項目を再度読み込みます。項目は項目キャッシュにあります。したがって、DAX は DynamoDB テーブルにアクセスせずに Alice に項目を返します。

このシナリオでは、Alice と Bob では、同じ `ProductCatalog` 項目で表示が異なります。DAX がその項目を項目キャッシュから削除するか、別のユーザーが同じ項目を DAX で再度更新するまで、この状態になります。

### 書き込み迂回
<a name="DAX.consistency.strategies-for-writes.write-around"></a>

アプリケーションで大量のデータ (バルクデータのロードなど) を書き込む必要がある場合、DAX をバイパスしてデータを直接 DynamoDB に書き込む方が妥当な場合があります。このような書き込み迂回の方法により、書き込みレイテンシーを軽減します。ただし、項目キャッシュは DynamoDB のデータと同期しません。

書き込み迂回戦略を使用する場合は、DAX で項目キャッシュに書き込まれるのは、アプリケーションが DAX クライアントを使用してデータを読み取るときであることに注意してください。これは一部のケースでは利点になります。最も頻繁に読み取られるデータのみが (最も頻繁に書き込まれるデータとは反対に) キャッシュされるためです。

たとえば、あるユーザー (Charlie) が、DAX を使用して別のテーブルである `GameScores` テーブルを使用するとしましょう。`GameScores` のパーティションキーは `UserId` であるため、Charlie のスコアは同じ `UserId` を持ちます。

![\[Charlie が DAX を使用して DynamoDB テーブルを操作する方法の番号付きのステップを示すワークフロー図。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/dax-consistency-charlie.png)


1. Charlie は自分のスコアをすべて取得するため、`Query` を DAX に送信します。このクエリが以前に発行されていないとすると、DAX は処理のためにクエリを DynamoDB に転送します。結果を DAX クエリキャッシュに保存し、Charlie に結果セットを返します。この結果セットは、削除されるまでクエリキャッシュで使用できます。

1. ここで、Charlie が Meteor Blasters ゲームをプレイし、ハイスコアを達成したとします。Charlie は `UpdateItem` リクエストを DynamoDB に送信して、`GameScores` テーブルの項目を変更します。

1. 最後に、Charlie は先の `Query` を再実行して、自分のデータをすべて `GameScores` から取得することにします。この結果には、Meteor Blasters のハイスコアは表示されません。これは、クエリの結果が項目キャッシュではなくクエリキャッシュから取得されるためです。2 つのキャッシュは互いに独立しており、一方のキャッシュでの変更は他方のキャッシュに影響しません。

DAX では、クエリキャッシュの結果セットを DynamoDB の最新のデータに更新しません。クエリキャッシュ内の各結果セットは、`Query` または `Scan` オペレーションが実行された時点のものです。したがって、Charlie の `Query` の結果は、彼の `PutItem` オペレーションを反映していません。DAX が結果セットをクエリキャッシュから削除するまで、この状態が続きます。