Amazon DynamoDB
開発者ガイド (API バージョン 2012-08-10)

DAX と DynamoDB の整合性モデル

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

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

DAX クラスターノード間の整合性

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

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

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

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

DAX 項目キャッシュの動作

各 DAX クラスターには 2 つの異なるキャッシュがあります。項目キャッシュとクエリキャッシュです。 (詳細については、「概念」を参照してください)。このセクションでは、DAX 項目キャッシュに対する読み込みおよび書き込みの整合性の実装について取り扱います。

読み込みの整合性

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

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

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

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

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

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

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

項目は、キャッシュの TTL 設定および LRU アルゴリズムによりますが、DAX の項目キャッシュに残ります (概念を参照)。ただし、この期間は、DAX は項目を DynamoDB から再ロードしません。他者が DynamoDB クライアントを使用し、DAX 全体をバイパスして項目を更新すると、DAX クライアントを使用した GetItem リクエストで、DynamoDB クライアントを使用した GetItem リクエストとは異なる結果が呼び出されます。このシナリオでは、DAX と DynamoDB は、DAX 項目の TTL が期限切れになるまで、同じキーに対して矛盾する値を保持します。

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

注記

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

書き込みの整合性

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

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

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

DAX の書き込み処理方法

DAX は、高パフォーマンスの読み込みを必要とするアプリケーションを対象としています。書き込みスルーキャッシュである DAX は、直接書き込みを発行できるため、書き込みは即時項目キャッシュに反映されます。キャッシュ無効化ロジックを管理する必要はありません。DAX がお客様の代わりにそれを行います。

DAX では、PutItemUpdateItemBatchWriteItem、および DeleteItem の各書き込みオペレーションがサポートされます。これらのリクエストのいずれかを DAX に送信すると、次のことを行います。

  • DAX は DynamoDB にリクエストを送信します。

  • DynamoDB が DAX に返答し、書き込みが成功したことが確認されます。

  • DAX がキャッシュ項目に項目を書き込みます。

  • DAX がリクエスタに成功を返します。

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

注記

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

DAX クエリキャッシュの動作

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

クエリ-更新-クエリの整合性

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

例として、次のシナリオで考えてみます。ここでは、アプリケーションは DocumentRevisions という名前のテーブルを使用しており、このテーブルにはパーティションキーとして DocId、ソートキーとして RevisionNumber があります。

  1. クライアントが DocId 101 に対して、RevisionNumber5 以上のすべての項目を求める Query を発行します。DAX は結果セットをクエリキャッシュに保存し、ユーザーに結果セットを返します。

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

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

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

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

強力な整合性のある読み込み

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

ネガティブキャッシング

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

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

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

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

書き込みの戦略

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

レイテンシーが重要なアプリケーションでは、書き込みスルー DAX を使用するとネットワークホップが増えるため、DAX への書き込みが DynamoDB に直接書き込むよりもわずかに遅くなります。書き込みレイテンシーが重要なアプリケーションの場合は、代わりに DynamoDB に直接書き込むことで、レイテンシーを軽減できます。(詳細については、「書き込み迂回」を参照してください。)

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

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

書き込みスルー

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

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

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

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

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

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

書き込み迂回

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

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

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

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

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

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

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