メニュー
Amazon DynamoDB
開発者ガイド (API Version 2012-08-10)

グローバルセカンダリインデックス

アプリケーションによっては、さまざまな属性をクエリ基準に使用して、いろいろな種類のクエリを実行する必要があります。このような要件に対応するために、1 つ以上の グローバルセカンダリインデックス を作成して、そのインデックスに対して Query リクエストを発行できます。たとえば、GameScores という名前のテーブルがあり、モバイルゲームアプリケーションのユーザーとスコアを記録しているとします。GameScores の各項目は、パーティションキー (UserId) とソートキー (GameTitle) で識別します。次の図は、テーブル内の項目の構成を示しています。一部表示されていない属性もあります。

ここで、各ゲームの最高スコアを表示する順位表アプリケーションを作成すると仮定します。キー属性(UserId と GameTitle)を指定したクエリは非常に効率的ですが、GameTitle だけに基づいて GameScores からデータを取り出す必要があるアプリケーションでは、Scan オペレーションを使用する必要があります。テーブルに追加される項目が増えるにつれ、すべてのデータのスキャンは低速で非効率的になり、次のような質問に答えることが難しくなります。

  • ゲーム Meteor Blasters で記録された最高スコアはいくつですか?

  • Galaxy Invaders で最高スコアを獲得したユーザーは誰ですか?

  • 勝敗の最も高い比率は何ですか?

非キー属性に対するクエリの速度を上げるために、グローバルセカンダリインデックス を作成できます。グローバルセカンダリインデックスには、ベーステーブルからの属性の一部が格納されますが、テーブルのプライマリキーとは異なるプライマリキーによって構成されます。インデックスキーは、テーブルからのキー属性を持つ必要がありません。また、テーブルと同じキースキーマを使用する必要もありません。

たとえば、パーティションキーに GameTitle を使用し、ソートキーに TopScore を使用して、GameTitleIndex という名前のグローバルセカンダリインデックスを作成できます。ベーステーブルのプライマリキー属性は必ずインデックスに射影されるので、UserId 属性も存在します。次の図は、GameTitleIndex インデックスを示しています。

これで、GameTitleIndex に対してクエリを実行し、Meteor Blasters のスコアを簡単に入手できるようになります。結果は、ソートキーの値 TopScore 別に並べられます。ScanIndexForward パラメータを false に設定した場合、結果は降順で返されます。つまり、最高スコアが最初に返されます。

すべてのグローバルセカンダリインデックスには、パーティションキーが必要で、オプションのソートキーを指定できます。インデックスキースキーマは、ベーステーブルスキーマとは異なるものにすることができます。シンプルなプライマリキー (パーティションキー) のあるテーブルを作成したり、複合プライマリキー (パーティションキーおよびソートキー) のあるグローバルセカンダリインデックスを作成でき、その逆も可能です。インデックスキー属性は、ベーステーブルの任意の最上位文字列、数値、またはバイナリ属性で構成できます。他のスカラー型、ドキュメント型、およびセット型は使用できません。

必要な場合、他のベーステーブル属性をインデックスに射影できます。インデックスに対してクエリを実行すると、DynamoDB は、この射影された属性を効率的に取り出すことができますが、グローバルセカンダリインデックスのクエリでは、ベーステーブルから属性をフェッチできません。上記の図に示すように、GameTitleIndex にクエリを実行した場合、クエリは TopScore 以外の非キー属性にアクセスすることはできません (キー属性 GameTitleUserId は自動的に射影されます)。

DynamoDB テーブルでは、各キー値は一意である必要があります。ただし、グローバルセカンダリインデックス のキー値は一意である必要がありません。たとえば、Comet Quest という名前のゲームが特に難しく、多くの新しいユーザーが試しても、ゼロを上回るスコアを獲得することができないとします。ここに、それを表すのに使用できるいくつかのデータがあります。

UserId GameTitle TopScore
123 Comet Quest 0
201 Comet Quest 0
301 Comet Quest 0

このデータを GameScores テーブルに追加すると、DynamoDB はそのデータを GameTitleIndex に伝達します。GameTitle として Comet Quest を、TopScore として 0 を指定してインデックスに対するクエリを実行すると、次のデータが返されます。

指定したキー値を持つ項目だけがレスポンスに表示されます。その一連のデータ内では、項目は特定の順に並んでいません。

グローバルセカンダリインデックス は、キー属性が実際に存在するデータ項目のみを記録します。たとえば、別の新しい項目を GameScores テーブルに追加し、必須のプライマリキー属性だけを指定したとします。

UserId GameTitle
400 Comet Quest

TopScore 属性を指定していないので、DynamoDB は、この項目を GameTitleIndex に反映しません。このため、すべての Comet Quest 項目を対象に、GameScores に対してクエリを実行した場合、次の 4 つの項目が返されます。

GameTitleIndex に対して同様のクエリを実行すると、4 つではなく 3 つの項目が返されます。これは、TopScore が存在しない項目はインデックスに反映されないためです。

属性の射影

射影は、テーブルからセカンダリインデックスにコピーされる属性セットです。テーブルのパーティションキーとソートキーは必ずインデックスに射影されます。アプリケーションのクエリ要件をサポートするために、他の属性を射影することができます。インデックスのクエリを行うと、Amazon DynamoDB は射影内の属性に、それらの属性が独立したテーブル内にあるかのようにアクセスできます。

セカンダリインデックス を作成する場合は、インデックスに射影される属性を指定する必要があります。DynamoDB にはこのための 3 つのオプションが用意されています。

  • KEYS_ONLY – インデックスの各項目は、テーブルのパーティションキーとソートキー値、およびインデックスキー値のみで構成されます。KEYS_ONLY オプションを使用すると、セカンダリインデックス は最小になります。

  • INCLUDEKEYS_ONLY の属性に加えて、セカンダリインデックス にその他の非キー属性が含まれるように指定できます。

  • ALL – セカンダリインデックス にソーステーブルのすべての属性が含まれます。テーブルの全データがインデックスで複製されるため、ALL の射影により、セカンダリインデックス は最大になります。

前の図の GameTitleIndex には、追加の射影された属性がありません。アプリケーションは、クエリで GameTitleTopScore を使用できます。ただし、特定のゲームで最高スコアを獲得したユーザーや、勝率が最も高いユーザーを効率的に特定することはできません。このデータに対するクエリをサポートする最も効率的な方法は、次の図に示すように、これらの属性をベーステーブルからグローバルセカンダリインデックスに射影する方法です。

非キー属性 Wins と Losses がインデックスに射影されるので、アプリケーションは、任意のゲーム、またはゲームとユーザー ID の任意の組み合わせに対して、勝敗の比率を特定できます。

グローバルセカンダリインデックス に射影する属性を選択する場合には、プロビジョニングされるスループットコストとストレージコストのトレードオフを考慮する必要があります。

  • ごく一部の属性だけに最小のレイテンシーでアクセスする必要がある場合は、それらの属性だけを グローバルセカンダリインデックス に射影することを検討してください。インデックスが小さいほど少ないコストで格納でき、書き込みコストも低くなります。

  • アプリケーションが非キー属性に頻繁にアクセスする場合には、それらの属性を グローバルセカンダリインデックス に射影することを検討してください。グローバルセカンダリインデックス の追加のストレージコストは、頻繁なテーブルスキャンを実行するコストを相殺します。

  • ほとんどの非キー属性に頻繁にアクセスする場合は、それらの属性、さらにはベーステーブル全体をグローバルセカンダリインデックスに射影することができます。これにより、最大限の柔軟性が得られますが、ストレージコストは増加するか、倍になります。

  • アプリケーションでテーブルのクエリを頻繁に行う必要がなく、テーブル内のデータに対する書き込みや更新が多数になる場合は、KEYS_ONLY を射影することを検討してください。グローバルセカンダリインデックス のサイズは最小になりますが、クエリに必要なサイズは確保されます。

グローバルセカンダリインデックス のクエリ

Query オペレーションを使用して、グローバルセカンダリインデックスの 1 つ以上の項目にアクセスすることができます。クエリでは、使用するベーステーブル名とインデックス名、クエリ結果で返される属性、および適用するクエリ条件を指定する必要があります。DynamoDB で返される結果は、昇順にも降順にもすることができます。

順位表アプリケーションのゲームデータをリクエストする Query から返される次のデータについて考えます。

Copy
{ "TableName": "GameScores", "IndexName": "GameTitleIndex", "KeyConditionExpression": "GameTitle = :v_title", "ExpressionAttributeValues": { ":v_title": {"S": "Meteor Blasters"} }, "ProjectionExpression": "UserId, TopScore", "ScanIndexForward": false }

このクエリでは次のようになっています。

  • DynamoDB が GameTitle パーティションキーを使用して GameTitleIndex にアクセスし、Meteor Blasters のインデックス項目を特定します。このキーを持つすべてのインデックス項目が、すばやく取り出せるように隣り合わせに格納されます。

  • このゲーム内で、DynamoDB はインデックスを使用して、このゲームのすべてのユーザー ID と最高スコアにアクセスします。

  • ScanIndexForward パラメータが false に設定されているので、結果は降順で返されます。

グローバルセカンダリインデックスのスキャン

Scan オペレーションを使用して、グローバルセカンダリインデックス からすべてのデータを取得できます。 リクエストにはベーステーブル名とインデックス名を指定する必要があります。Scan では、DynamoDB はインデックスのすべてのデータを読み取り、それをアプリケーションに返します。また、データの一部のみを返し、残りのデータを破棄するようにリクエストすることもできます。これを行うには、Scan オペレーションの FilterExpression パラメーターを使用します。 詳細については、「Scan のフィルタ式」を参照してください。

テーブルと グローバルセカンダリインデックス 間のデータ同期

DynamoDB は、各グローバルセカンダリインデックスをそのベーステーブルと自動的に同期します。アプリケーションがテーブルに項目を書き込むか、削除すると、そのテーブルのすべての グローバルセカンダリインデックス は、結果整合性のあるモデルを使用して、非同期で更新されます。アプリケーションがインデックスに直接書き込むことはありません。ただし、DynamoDB でこれらのインデックスがどのように維持されるかを理解することは重要です。

グローバルセカンダリインデックスを作成するときは、1 つ以上のインデックスキー属性およびそれらのデータ型を指定します。つまり、ベーステーブルに項目を書き込むとき、それらの属性のデータ型が、インデックスキースキーマのデータ型に一致する必要があります。GameTitleIndex の場合、インデックスの GameTitle パーティションキーは文字列データ型として定義され、インデックス内の TopScore ソートキーは数値型になります。GameScores テーブルに項目を追加するときに、GameTitle または TopScore に対して別のデータ型を指定すると、データ型の不一致により DynamoDB によって ValidationException が返されます。

テーブルに項目を入力するか、削除すると、そのテーブルの グローバルセカンダリインデックス は、結果整合性のある方法で更新されます。テーブルデータへの変更は、通常は、瞬時に グローバルセカンダリインデックス に伝達されます。ただし、万が一障害が発生した場合は、長い伝達遅延が発生することがあります。このため、アプリケーションでは、グローバルセカンダリインデックス に対するクエリによって、最新でない結果が返される状況を予期し、それに対応する必要があります。

テーブルに項目を書き込む場合には、グローバルセカンダリインデックスソートキーに対して属性を指定する必要はありません。GameTitleIndex を例にとると、GameScores テーブルに新しい項目を書き込むために、TopScore 属性に値を指定する必要はありません。この場合、Amazon DynamoDB はこの特定の項目のインデックスにデータを書き込むことはありません。

多数の グローバルセカンダリインデックス があるテーブルは、インデックス数が少ないテーブルに比べて書き込みアクティビティに多くのコストを要します。詳細については、「プロビジョニングされたスループットに関する考慮事項(グローバルセカンダリインデックス)」を参照してください。

プロビジョニングされたスループットに関する考慮事項(グローバルセカンダリインデックス)

グローバルセカンダリインデックス を作成するときには、そのインデックスに対して予想されるワークロードに応じた読み込みおよび書き込みキャパシティーユニットを指定する必要があります。グローバルセカンダリインデックスのプロビジョニングされたスループット設定は、そのベーステーブルの設定から独立しています。グローバルセカンダリインデックスに対する Query オペレーションでは、ベーステーブルではなく、インデックスから読み込みキャパシティーユニットを消費します。テーブルで項目を入力、更新、または削除すると、そのテーブルのグローバルセカンダリインデックスも更新されます。このインデックスの更新では、ベーステーブルからではなく、インデックスから書き込みキャパシティーユニットを消費します。

たとえば、グローバルセカンダリインデックス に対して Query を実行し、そのプロビジョニングされた読み込みキャパシティーを超えた場合、リクエストは調整されます。多量の書き込みアクティビティをテーブルに対して実行するときに、そのテーブルの グローバルセカンダリインデックス に十分な書き込みキャパシティーがない場合、そのテーブルに対する書き込みアクティビティは調整されます。

グローバルセカンダリインデックス のプロビジョニングされたスループット設定を表示するには、DescribeTable オペレーションを使用します。テーブルのすべての グローバルセカンダリインデックス に関する詳細が返されます。

読み込みキャパシティーユニット

Global secondary index では、結果整合性のある読み込みをサポートしており、各読み込みで、1 読み込みキャパシティーユニットの半分を消費します。つまり、1 回の グローバルセカンダリインデックス のクエリでは、1 読み込みキャパシティーユニットあたり最大 2 × 4 KB = 8 KB を取り出すことができます。

グローバルセカンダリインデックス のクエリの場合、DynamoDB は、プロビジョニングされた読み込みアクティビティを、テーブルに対するクエリと同じ方法で計算します。唯一の違いは、ベーステーブル内の項目のサイズではなくインデックスエントリのサイズに基づいて計算が行われることです。読み込みキャパシティーユニットの数は、返されたすべての項目について射影されたすべての属性のサイズの合計です。結果は、次の 4 KB 境界まで切り上げられます。DynamoDB がプロビジョニング済みスループットの利用率を計算する方法の詳細については、「テーブルの読み書き要件の指定」を参照してください。

Query オペレーションによって返される結果の最大サイズは、1 MB です。これには、返されるすべての項目にわたる、すべての属性の名前と値のサイズが含まれます。

たとえば、各項目に 2000 バイトのデータが格納されている グローバルセカンダリインデックス があるとします。このインデックスに対して Query を実行したらクエリが 8 項目戻したとします。一致する項目の合計サイズは、2000 バイト × 8 項目 = 16,000 バイトです。これは、最も近い 4 KB 境界に切り上げられます。グローバルセカンダリインデックス のクエリは結果整合性があるので、合計コストは、0.5 ×(16 KB/4 KB)、つまり、2 読み込みキャパシティーユニットです。

書き込みキャパシティーユニット

テーブルの項目が追加、更新、または削除されたときに、グローバルセカンダリインデックス がこの影響を受ける場合、グローバルセカンダリインデックス は、そのオペレーションに対してプロビジョニングされた書き込みキャパシティーユニットを消費します。書き込み用にプロビジョニングされたスループットの合計コストは、ベーステーブルに対する書き込みと、グローバルセカンダリインデックスの更新で消費された書き込みキャパシティーユニットの合計になります。テーブルへの書き込みには、グローバルセカンダリインデックス の更新は必要ないので、インデックスから消費される書き込みキャパシティーはありません。

テーブルへの書き込みを正常に実行するために、テーブルとそのすべての グローバルセカンダリインデックス に対するプロビジョニングされたスループット設定は、書き込みに対応できるだけの十分な書き込みキャパシティーを備えている必要があります。十分でない場合、書き込みは調整されます。

グローバルセカンダリインデックス に項目を書き込むコストは、いくつかの要因に左右されます。

  • インデックス付き属性が定義されたテーブルに新しい項目を書き込む場合、または既存の項目を更新して未定義のインデックス付き属性を定義する場合には、インデックスへの項目の挿入に 1 回の書き込みオペレーションが必要です。

  • テーブルに対する更新によってインデックス付きキー属性の値が(A から B に)変更された場合には、インデックスから既存の項目を削除し、インデックスに新しい項目を挿入するために、2 回の書き込みが必要です。 

  • インデックス内に既存の項目があって、テーブルに対する書き込みによってインデックス付き属性が削除された場合は、インデックスから古い項目の射影を削除するために、1 回の書き込みが必要です。

  • 項目の更新の前後にインデックス内に項目が存在しない場合は、インデックスで追加の書き込みコストは発生しません。

  • テーブルに対する更新によってインデックスキースキーマの射影された属性の値のみが変更され、インデックス付きキー属性の値は変更されない場合は、インデックスに射影された属性の値を更新するために、1 回の書き込みが必要です。

これらすべての要因は、インデックス内の各項目のサイズが 1 KB 以下であるという前提で書き込みキャパシティーユニット数を算出します。インデックスエントリがそれよりも大きい場合は、書き込みキャパシティーユニットを追加する必要があります。クエリが返す必要がある属性を特定し、それらの属性だけをインデックスに射影することで、書き込みコストは最小になります。

グローバルセカンダリインデックス のストレージに関する考慮事項

アプリケーションがテーブルに項目を書き込むと、DynamoDB では正しい属性のサブセットが、それらの属性が現れる必要がある グローバルセカンダリインデックス に自動的にコピーされます。AWS アカウントでは、テーブル内の項目のストレージと、そのベーステーブルのグローバルセカンダリインデックスにある属性のストレージに対して課金されます。

インデックス項目が使用するスペースの量は、次の量の合計になります。

  • ベーステーブルのプライマリキー (パーティションキーとソートキー) のサイズのバイト数

  • インデックスキー属性のサイズのバイト数

  • 射影された属性(存在する場合)のサイズのバイト数

  • インデックス項目あたり 100 bytes のオーバーヘッド

グローバルセカンダリインデックスのストレージ要件の見積もりは、インデックス内の 1 項目の平均サイズの見積もり値に、ベーステーブル内のグローバルセカンダリインデックスキー属性を持つ項目の数を掛けて算出します。

特定の属性が定義されていない項目がテーブルに含まれていて、その属性がインデックスパーティションキーまたはソートキーとして定義されている場合、DynamoDB はその項目のデータをインデックスに書き込みません。