

# DynamoDB のグローバルセカンダリインデックスの使用
<a name="GSI"></a>

アプリケーションによっては、さまざまな属性をクエリ基準に使用して、いろいろな種類のクエリを実行する必要があります。このような要件に対応するために、1 つまたは複数の*グローバルセカンダリインデックス*を作成して、Amazon DynamoDB でこれらのインデックスに対して `Query` リクエストを発行できます。

**Topics**
+ [シナリオ: グローバルセカンダリインデックスの使用](#GSI.scenario)
+ [属性の射影](#GSI.Projections)
+ [マルチ属性キースキーマ](#GSI.MultiAttributeKeys)
+ [グローバルセカンダリインデックスからのデータの読み込み](#GSI.Reading)
+ [テーブルとグローバルセカンダリインデックス間のデータ同期](#GSI.Writes)
+ [グローバルセカンダリインデックスがあるテーブルクラス](#GSI.tableclasses)
+ [グローバルセカンダリインデックスに対するプロビジョニングされたスループットに関する考慮事項](#GSI.ThroughputConsiderations)
+ [グローバルセカンダリインデックスのストレージに関する考慮事項](#GSI.StorageConsiderations)
+ [パターンの設計](GSI.DesignPatterns.md)
+ [DynamoDB のグローバルセカンダリインデックスの管理](GSI.OnlineOps.md)
+ [DynamoDB でのインデックスキー違反の検出と修正](GSI.OnlineOps.ViolationDetection.md)
+ [グローバルセカンダリインデックスの操作: Java](GSIJavaDocumentAPI.md)
+ [グローバルセカンダリインデックスの操作: .NET](GSILowLevelDotNet.md)
+ [AWS CLI を使用した DynamoDBでのグローバルセカンダリインデックスの操作](GCICli.md)

## シナリオ: グローバルセカンダリインデックスの使用
<a name="GSI.scenario"></a>

たとえば、`GameScores` という名前のテーブルがあり、モバイルゲームアプリケーションのユーザーとスコアを記録しているとします。`GameScores` の各項目は、パーティションキー (`UserId`) およびソートキー (`GameTitle`) で特定されます。次の図は、テーブル内の項目の構成を示しています。一部表示されていない属性もあります。

![\[ユーザー ID、タイトル、スコア、日付、および勝敗のリストを含む GameScores テーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_01.png)


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

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

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

![\[タイトル、スコア、およびユーザー ID のリストを含む GameTitleIndex テーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_02.png)


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

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

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

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 を指定してインデックスに対するクエリを実行すると、次のデータが返されます。

![\[タイトル、最上位スコア、およびユーザー ID のリストを含むテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_05.png)


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

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


****  

| UserId | GameTitle | 
| --- | --- | 
| 400 | Comet Quest | 

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

![\[4 つのタイトル、最上位スコア、およびユーザー ID のリストを含むテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_04.png)


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

![\[3 つのタイトル、最上位スコア、およびユーザー ID のリストを含むテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_05.png)


## 属性の射影
<a name="GSI.Projections"></a>

*射影*とは、テーブルからセカンダリインデックスにコピーされる属性のセットです。テーブルのパーティションキーとソートキーは常にインデックスに射影されます。アプリケーションのクエリ要件をサポートするために、他の属性を射影できます。インデックスをクエリすると、Amazon DynamoDB は、その属性が自身のテーブル内にあるかのように、プロジェクション内の任意の属性にアクセスできます。

セカンダリインデックスを作成するときは、インデックスに射影される属性を指定する必要があります。DynamoDB には、そのために次の 3 つのオプションが用意されています。
+ *KEYS\$1ONLY* – インデックス内の各項目は、テーブルパーティションキーとソートキーの値、およびインデックスキーの値のみで構成されます。`KEYS_ONLY` オプションを指定すると、セカンダリインデックスが最小になります。
+ *INCLUDE* – `KEYS_ONLY` の属性に加えて、セカンダリインデックスにその他の非キー属性が含まれるように指定できます。
+ *ALL* – セカンダリインデックスには、ソーステーブルのすべての属性が含まれます。すべてのテーブルデータがインデックスに複製されるため、`ALL` の射影にすると、セカンダリインデックスが最大になります。

前の図では、`GameTitleIndex` には 1 つの射影された属性 `UserId` のみがあります。そのため、アプリケーションはクエリで `UserId` および `GameTitle` を使用して各ゲームのトップスコアラーの `TopScore` を効率的に判断できますが、トップスコアラーの勝敗の最も高い比率は効率的に判断できません。そのためには、アプリケーションは基本テーブルで追加のクエリを実行して、各トップスコアラーの勝敗を取得する必要があります。このデータに対するクエリのサポートを効率化する方法として、次の図に示すように、これらの属性をベーステーブルからグローバルセカンダリインデックスに射影する方法があります。

![\[効率的なクエリのサポートのために非キー属性を GSI に射影する説明図。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/GSI_06.png)


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

グローバルセカンダリインデックスに射影する属性を選択する場合には、プロビジョニングされるスループットコストとストレージコストのトレードオフを考慮する必要があります。
+ ごく一部の属性だけに最小のレイテンシーでアクセスする必要がある場合は、それらの属性だけをグローバルセカンダリインデックスに射影することを検討してください。インデックスが小さいほど少ないコストで格納でき、書き込みコストも低くなります。
+ アプリケーションが非キー属性に頻繁にアクセスする場合には、それらの属性をグローバルセカンダリインデックスに射影することを検討してください。グローバルセカンダリインデックスのストレージコストの増加は、頻繁にテーブルスキャンを実行するコストの減少で相殺されます。
+ ほとんどの非キー属性に頻繁にアクセスする場合は、それらの属性を (場合によっては、ベーステーブル全体を) グローバルセカンダリインデックスに射影することができます。これにより、最大限の柔軟性が得られます。ただし、ストレージコストは増加するか、倍になります。
+ アプリケーションでテーブルのクエリを頻繁に行う必要がなく、テーブル内のデータに対する書き込みや更新が多数になる場合は、`KEYS_ONLY` を射影することを検討してください。グローバルセカンダリインデックスは、最小サイズになりますが、それでもクエリのアクティビティに必要な場合は利用可能です。

## マルチ属性キースキーマ
<a name="GSI.MultiAttributeKeys"></a>

グローバルセカンダリインデックスはマルチ属性キーをサポートしているため、複数の属性からパーティションキーとソートキーを作成できます。マルチ属性キーを使用すると、最大 4 つの属性からパーティションキーを、最大 4 つの属性からソートキーを、キースキーマごとに合計で最大 8 つの属性に作成できます。

マルチ属性キーは、属性を合成キーに手動で連結する必要がなくなるため、データモデルを簡素化します。`TOURNAMENT#WINTER2024#REGION#NA-EAST` のような複合文字列を作成する代わりに、ドメインモデルから自然属性を直接使用できます。DynamoDB は複合キーロジックを自動的に処理し、データ分散のために複数のパーティションキー属性をハッシュし、複数のソートキー属性にわたって階層型のソート順序を維持します。

例えば、試合をトーナメントとリージョン別に整理するゲームトーナメントシステムを考えてみましょう。マルチ属性キーを使用すると、パーティションキーを `tournamentId` と `region` の 2 つの異なる属性として定義できます。同様に、`round`、`bracket`、`matchId` などの複数の属性を使用してソートキーを定義し、自然な階層を作成できます。このアプローチでは、文字列の操作や解析を行わずに、データ型とコードをクリーンに保ちます。

マルチ属性キーを使用してグローバルセカンダリインデックスをクエリする場合は、等号条件を使用してすべてのパーティションキー属性を指定する必要があります。ソートキー属性の場合、キースキーマで定義されている順序で、左から右にクエリを実行できます。つまり、最初のソートキー属性のみ、最初の 2 つの属性を一緒に、またはすべての属性を一緒にクエリできますが、途中で属性をスキップすることはできません。`>`、`<`、`BETWEEN`、`begins_with()` などの不等条件は、クエリの最後の条件である必要があります。

マルチ属性キーは、既存のテーブルにグローバルセカンダリインデックスを作成する場合、特に効果的です。データ全体で合成キーをバックフィルすることなく、テーブルに既に存在する属性を使用できます。これにより、さまざまな属性の組み合わせを使用してデータを再編成するインデックスを作成することで、アプリケーションに新しいクエリパターンを簡単に追加できます。

マルチ属性キーの各属性は、独自のデータ型 `String` (S)、`Number` (N)、または `Binary` (B) を持つことができます。データ型を選択するときは、ゼロパディングを必要とせずに `Number` 属性を数値順にソートし、`String` 属性を辞書順にソートすることを検討してください。例えば、スコア属性に `Number` 型を使用する場合、値 5、50、500、1000 は自然な数値順にソートされます。同じ値を `String` 型とする場合は、先頭にゼロを埋めない限り、「1000」、「5」、「50」、「500」とソートされます。

マルチ属性キーを設計する場合は、属性を最も一般的なものから最も具体的な順に並べてください。パーティションキーの場合、常に一緒にクエリされ、適切なデータ分散を提供する属性を組み合わせます。ソートキーの場合、クエリの柔軟性を最大化するために、頻繁にクエリされる属性を階層の最初に配置します。この順序により、アクセスパターンに一致する任意の詳細度のレベルでクエリを実行できます。

実装例については、「[マルチ属性キー](GSI.DesignPattern.MultiAttributeKeys.md)」を参照してください。

## グローバルセカンダリインデックスからのデータの読み込み
<a name="GSI.Reading"></a>

グローバルセカンダリインデックスから項目を取得するには、`Query` および `Scan` オペレーションを使用します。`GetItem` および `BatchGetItem` オペレーションは、グローバルセカンダリインデックスでは使用できません。

### グローバルセカンダリインデックスのクエリ
<a name="GSI.Querying"></a>

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

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

```
{
    "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 に設定されているので、結果は降順で返されます。

### グローバルセカンダリインデックスのスキャン
<a name="GSI.Scanning"></a>

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

## テーブルとグローバルセカンダリインデックス間のデータ同期
<a name="GSI.Writes"></a>

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

 グローバルセカンダリーインデックスは、基本テーブルから読み込み/書き込みキャパシティーモードを継承します。詳細については、「[DynamoDB でキャパシティモードを切り替える際の考慮事項](bp-switching-capacity-modes.md)」を参照してください。

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

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

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

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

## グローバルセカンダリインデックスがあるテーブルクラス
<a name="GSI.tableclasses"></a>

グローバルセカンダリインデックスは、常にベーステーブルと同じテーブルクラスを使用します。テーブルに新しいグローバルセカンダリインデックスが追加されるたびに、新しいインデックスはベーステーブルと同じテーブルクラスを使用します。テーブルのテーブルクラスが更新されると、関連するすべてのグローバルセカンダリインデックスも更新されます。

## グローバルセカンダリインデックスに対するプロビジョニングされたスループットに関する考慮事項
<a name="GSI.ThroughputConsiderations"></a>

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

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

**重要**  
 スロットリングを避けるため、グローバルセカンダリインデックスのプロビジョニングされた書き込みキャパシティーは、ベーステーブルの書き込みキャパシティーと同じかそれより大きい必要があります。これは、新しい更新ではベーステーブルとグローバルセカンダリインデックスの両方に書き込むためです。

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

### 読み込みキャパシティユニット
<a name="GSI.ThroughputConsiderations.Reads"></a>

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

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

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

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

### 書き込みキャパシティユニット
<a name="GSI.ThroughputConsiderations.Writes"></a>

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

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

**重要**  
グローバルセカンダリインデックス (GSI) を作成する際、基本テーブルへの書き込みによって生じる GSI アクティビティが GSI のプロビジョニングされた書き込み容量を超える場合、基本テーブルへの書き込み操作がスロットリングされる可能性があります。このスロットリングは、インデックス作成プロセスから本番ワークロードの中断の可能性に至るまで、すべての書き込み操作に影響します。詳細については、「[Amazon DynamoDB でのスロットリングのトラブルシューティング](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingThrottling.html)」を参照してください。

グローバルセカンダリインデックスに項目を書き込むコストは、いくつかの要因に左右されます。
+ インデックス付き属性が定義されたテーブルに新しい項目を書き込む場合、または既存の項目を更新して未定義のインデックス付き属性を定義する場合には、インデックスへの項目の挿入に 1 回の書き込みオペレーションが必要です。
+ テーブルに対する更新によってインデックス付きキー属性の値が（A から B に）変更された場合には、インデックスから既存の項目を削除し、インデックスに新しい項目を挿入するために、2 回の書き込みが必要です。  
+ インデックス内に既存の項目があって、テーブルに対する書き込みによってインデックス付き属性が削除された場合は、インデックスから古い項目の射影を削除するために、1 回の書き込みが必要です。
+ 項目の更新の前後にインデックス内に項目が存在しない場合は、インデックスで追加の書き込みコストは発生しません。
+ テーブルに対する更新によってインデックスキースキーマの射影された属性の値のみが変更され、インデックス付きキー属性の値は変更されない場合は、インデックスに射影された属性の値を更新するために、1 回の書き込みが必要です。

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

## グローバルセカンダリインデックスのストレージに関する考慮事項
<a name="GSI.StorageConsiderations"></a>

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

インデックス項目が使用するスペースの量は、次の量の合計になります。
+ ベーステーブルのプライマリキー (パーティションキーとソートキー) のサイズのバイト数
+ インデックスキー属性のサイズのバイト数
+ 射影された属性（存在する場合）のサイズのバイト数
+ インデックス項目あたり 100 バイトのオーバーヘッド

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

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

# パターンの設計
<a name="GSI.DesignPatterns"></a>

設計パターンは、グローバルセカンダリインデックスを使用する際の一般的な課題に対する実証済みのソリューションを提供します。これらのパターンは、特定のユースケースに合わせてインデックスを構築する方法を示すことで、効率的でスケーラブルなアプリケーションを構築するのに役立ちます。

各パターンには、独自のアプリケーションにパターンを適用するのに役立つコード例、ベストプラクティス、実際のユースケースを含む完全な実装ガイドが含まれています。

**Topics**
+ [マルチ属性キー](GSI.DesignPattern.MultiAttributeKeys.md)

# マルチ属性キーパターン
<a name="GSI.DesignPattern.MultiAttributeKeys"></a>

## 概要
<a name="GSI.DesignPattern.MultiAttributeKeys.Overview"></a>

マルチ属性キーを使用すると、それぞれ最大 4 つの属性で構成されるグローバルセカンダリインデックス (GSI) パーティションとソートキーを作成できます。これにより、クライアント側のコードが減り、最初にデータをモデル化し、後で新しいアクセスパターンを追加することが容易になります。

一般的なシナリオを考えてみましょう。複数の階層属性で項目をクエリする GSI を作成するには、従来、値を連結して合成キーを作成する必要があります。例えば、ゲームアプリでは、トーナメント、リージョン、ラウンドでトーナメントマッチをクエリするために、TOURNAMENT\$1WINTER2024\$1REGION\$1NA-EAST などの合成 GSI パーティションキーと ROUND\$1SEMIFINALS\$1BRACKET\$1UPPER などの合成ソートキーを作成できます。このアプローチは機能しますが、既存のテーブルに GSI を追加する場合は、データを書き込むときに文字列を連結し、読み取り時に解析し、既存のすべての項目にわたって合成キーをバックフィルする必要があります。これにより、コードが整理整頓され、個々のキーコンポーネントで型の安全性を維持することが困難になります。

マルチ属性キーは、GSI のこの問題を解決します。GSI パーティションキーは、tournamentId や region などの複数の既存の属性を使用して定義します。DynamoDB は複合キーロジックを自動的に処理し、データ分散のためにそれらをハッシュします。ドメインモデルから自然属性を使用して項目を記述すると、GSI は自動的にインデックスを作成します。連結なし、解析なし、バックフィルなし。コードはきれいに保たれ、データは入力され、クエリはシンプルになります。このアプローチは、自然属性のグループ化を含む階層データがある場合に特に役立ちます (トーナメント → リージョン → ラウンド、または組織 → 部門 → チームなど)。

## アプリケーションの例
<a name="GSI.DesignPattern.MultiAttributeKeys.ApplicationExample"></a>

このガイドでは、e スポーツプラットフォームのトーナメントマッチ追跡システムを構築する方法について説明します。プラットフォームは、ブラケット管理のためのトーナメントとリージョン、マッチング履歴のための選手、スケジューリングのための日付など、複数のディメンションにわたって試合を効率的にクエリする必要があります。

## データモデル
<a name="GSI.DesignPattern.MultiAttributeKeys.DataModel"></a>

このチュートリアルでは、トーナメントマッチ追跡システムは 3 つの主要なアクセスパターンをサポートし、それぞれに異なるキー構造が必要です。

**アクセスパターン 1:** 一意の ID で特定の試合を検索する
+ **解決策:** パーティションキーとして `matchId` をベーステーブルと使用する

**アクセスパターン 2:** 特定のトーナメントとリージョンのすべての試合をクエリし、オプションでラウンド、ブラケット、または試合でフィルタリングする
+ **解決策:** マルチ属性パーティションキー (`tournamentId` \$1 `region`) とマルチ属性ソートキー (`round` \$1 `bracket` \$1 `matchId`) を持つグローバルセカンダリインデックス
+ **クエリの例:** 「NA-EAST リージョン内の All WINTER2024 の試合」、または「WINTER2024/NA-EAST の UPPER ブラケット内の All SEMIFINALS の試合」

**アクセスパターン 3:** 選手の試合履歴をクエリし、オプションで日付範囲またはトーナメントラウンドでフィルタリングする
+ **解決策:** 単一パーティションキー (`player1Id`) とマルチ属性ソートキー (`matchDate` \$1 `round`) を持つグローバルセカンダリインデックス
+ **クエリの例:** 「プレイヤー 101 のすべての試合」または「2024 年 1 月の選手 101 の試合」

従来およびマルチ属性アプローチの主な違いは、項目構造を調べるときにはっきりします。

**従来のグローバルセカンダリインデックスのアプローチ (連結キー):**

```
// Manual concatenation required for GSI keys
const item = {
    matchId: 'match-001',                                          // Base table PK
    tournamentId: 'WINTER2024',
    region: 'NA-EAST',
    round: 'SEMIFINALS',
    bracket: 'UPPER',
    player1Id: '101',
    // Synthetic keys needed for GSI
    GSI_PK: `TOURNAMENT#${tournamentId}#REGION#${region}`,       // Must concatenate
    GSI_SK: `${round}#${bracket}#${matchId}`,                    // Must concatenate
    // ... other attributes
};
```

**マルチ属性のグローバルセカンダリインデックスのアプローチ (ネイティブキー):**

```
// Use existing attributes directly - no concatenation needed
const item = {
    matchId: 'match-001',                                          // Base table PK
    tournamentId: 'WINTER2024',
    region: 'NA-EAST',
    round: 'SEMIFINALS',
    bracket: 'UPPER',
    player1Id: '101',
    matchDate: '2024-01-18',
    // No synthetic keys needed - GSI uses existing attributes directly
    // ... other attributes
};
```

マルチ属性キーでは、自然ドメイン属性を使用して項目を 1 回書き込みます。DynamoDB は、合成連結キーを必要とせずに、複数の GSI 間で自動的にインデックスを作成します。

**ベーステーブルスキーマ:**
+ パーティションキー: `matchId` (1 属性)

**グローバルセカンダリインデックススキーマ (マルチ属性キーを持つ TournamentRegionIndex):**
+ パーティションキー: `tournamentId`、`region` (2 属性)
+ ソートキー: `round`、`bracket`、`matchId` (3 属性)

**グローバルセカンダリインデックススキーマ (マルチ属性キーを持つ PlayerMatchHistoryIndex):**
+ パーティションキー: `player1Id` (1 属性)
+ ソートキー: `matchDate`、`round` (2 属性)

### ベーステーブル: TournamentMatches
<a name="GSI.DesignPattern.MultiAttributeKeys.BaseTable"></a>


| matchId (PK) | tournamentId | リージョン | round | ブラケット | player1Id | player2Id | matchDate | 勝者 | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| match-001 | WINTER2024 | NA-EAST | FINALS | CHAMPIONSHIP | 101 | 103 | 2024-01-20 | 101 | 3-1 | 
| match-002 | WINTER2024 | NA-EAST | SEMIFINALS | UPPER | 101 | 105 | 2024-01-18 | 101 | 3-2 | 
| match-003 | WINTER2024 | NA-EAST | SEMIFINALS | UPPER | 103 | 107 | 2024-01-18 | 103 | 3-0 | 
| match-004 | WINTER2024 | NA-EAST | QUARTERFINALS | UPPER | 101 | 109 | 2024-01-15 | 101 | 3-1 | 
| match-005 | WINTER2024 | NA-WEST | FINALS | CHAMPIONSHIP | 102 | 104 | 2024-01-20 | 102 | 3-2 | 
| match-006 | WINTER2024 | NA-WEST | SEMIFINALS | UPPER | 102 | 106 | 2024-01-18 | 102 | 3-1 | 
| match-007 | SPRING2024 | NA-EAST | QUARTERFINALS | UPPER | 101 | 108 | 2024-03-15 | 101 | 3-0 | 
| match-008 | SPRING2024 | NA-EAST | QUARTERFINALS | LOWER | 103 | 110 | 2024-03-15 | 103 | 3-2 | 

### GSI: TournamentRegionIndex (マルチ属性キー)
<a name="GSI.DesignPattern.MultiAttributeKeys.TournamentRegionIndexTable"></a>


| tournamentId (PK) | リージョン (PK) | ラウンド (SK) | ブラケット (SK) | matchId (SK) | player1Id | player2Id | matchDate | 勝者 | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| WINTER2024 | NA-EAST | FINALS | CHAMPIONSHIP | match-001 | 101 | 103 | 2024-01-20 | 101 | 3-1 | 
| WINTER2024 | NA-EAST | QUARTERFINALS | UPPER | match-004 | 101 | 109 | 2024-01-15 | 101 | 3-1 | 
| WINTER2024 | NA-EAST | SEMIFINALS | UPPER | match-002 | 101 | 105 | 2024-01-18 | 101 | 3-2 | 
| WINTER2024 | NA-EAST | SEMIFINALS | UPPER | match-003 | 103 | 107 | 2024-01-18 | 103 | 3-0 | 
| WINTER2024 | NA-WEST | FINALS | CHAMPIONSHIP | match-005 | 102 | 104 | 2024-01-20 | 102 | 3-2 | 
| WINTER2024 | NA-WEST | SEMIFINALS | UPPER | match-006 | 102 | 106 | 2024-01-18 | 102 | 3-1 | 
| SPRING2024 | NA-EAST | QUARTERFINALS | LOWER | match-008 | 103 | 110 | 2024-03-15 | 103 | 3-2 | 
| SPRING2024 | NA-EAST | QUARTERFINALS | UPPER | match-007 | 101 | 108 | 2024-03-15 | 101 | 3-0 | 

### GSI: PlayerMatchHistoryIndex (マルチ属性キー)
<a name="GSI.DesignPattern.MultiAttributeKeys.PlayerMatchHistoryIndexTable"></a>


| player1Id (PK) | matchDate (SK) | ラウンド (SK) | tournamentId | リージョン | ブラケット | matchId | player2Id | 勝者 | score | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| 101 | 2024-01-15 | QUARTERFINALS | WINTER2024 | NA-EAST | UPPER | match-004 | 109 | 101 | 3-1 | 
| 101 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-EAST | UPPER | match-002 | 105 | 101 | 3-2 | 
| 101 | 2024-01-20 | FINALS | WINTER2024 | NA-EAST | CHAMPIONSHIP | match-001 | 103 | 101 | 3-1 | 
| 101 | 2024-03-15 | QUARTERFINALS | SPRING2024 | NA-EAST | UPPER | match-007 | 108 | 101 | 3-0 | 
| 102 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-WEST | UPPER | match-006 | 106 | 102 | 3-1 | 
| 102 | 2024-01-20 | FINALS | WINTER2024 | NA-WEST | CHAMPIONSHIP | match-005 | 104 | 102 | 3-2 | 
| 103 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-EAST | UPPER | match-003 | 107 | 103 | 3-0 | 
| 103 | 2024-03-15 | QUARTERFINALS | SPRING2024 | NA-EAST | LOWER | match-008 | 110 | 103 | 3-2 | 

## 前提条件
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites"></a>

開始する前に、以下を確認してください。

### アカウントとアクセス許可
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.AWSAccount"></a>
+ アクティブな AWS アカウント (必要に応じて[ここで作成](https://aws.amazon.com/free/))
+ DynamoDB オペレーションの IAM アクセス許可:
  + `dynamodb:CreateTable`
  + `dynamodb:DeleteTable`
  + `dynamodb:DescribeTable`
  + `dynamodb:PutItem`
  + `dynamodb:Query`
  + `dynamodb:BatchWriteItem`

**注記**  
**セキュリティ上の注意:** 本番稼働用には、必要なアクセス許可のみを持つカスタム IAM ポリシーを作成します。このチュートリアルでは、AWS 管理ポリシー `AmazonDynamoDBFullAccessV2` を使用できます。

### 開発環境
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.DevEnvironment"></a>
+ マシンにインストールされている「Node.js」
+ 次のいずれかの方法を使用して設定される AWS 認証情報

**オプション 1: AWS CLI**

```
aws configure
```

**オプション 2: 環境変数**

```
export AWS_ACCESS_KEY_ID=your_access_key_here
export AWS_SECRET_ACCESS_KEY=your_secret_key_here
export AWS_DEFAULT_REGION=us-east-1
```

### 必要なパッケージのインストール
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.InstallPackages"></a>

```
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
```

## 実装
<a name="GSI.DesignPattern.MultiAttributeKeys.Implementation"></a>

### ステップ 1: マルチ属性キーを使用して GSI でテーブルを作成する
<a name="GSI.DesignPattern.MultiAttributeKeys.CreateTable"></a>

シンプルなベースキー構造と、マルチ属性キーを使用する GSI を持つテーブルを作成します。

#### コード例
<a name="w2aac19c13c45c23b9c11b3b5b1"></a>

```
import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });

const response = await client.send(new CreateTableCommand({
    TableName: 'TournamentMatches',
    
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'matchId', KeyType: 'HASH' }              // Simple PK
    ],
    
    AttributeDefinitions: [
        { AttributeName: 'matchId', AttributeType: 'S' },
        { AttributeName: 'tournamentId', AttributeType: 'S' },
        { AttributeName: 'region', AttributeType: 'S' },
        { AttributeName: 'round', AttributeType: 'S' },
        { AttributeName: 'bracket', AttributeType: 'S' },
        { AttributeName: 'player1Id', AttributeType: 'S' },
        { AttributeName: 'matchDate', AttributeType: 'S' }
    ],
    
    // GSIs with multi-attribute keys
    GlobalSecondaryIndexes: [
        {
            IndexName: 'TournamentRegionIndex',
            KeySchema: [
                { AttributeName: 'tournamentId', KeyType: 'HASH' },    // GSI PK attribute 1
                { AttributeName: 'region', KeyType: 'HASH' },          // GSI PK attribute 2
                { AttributeName: 'round', KeyType: 'RANGE' },          // GSI SK attribute 1
                { AttributeName: 'bracket', KeyType: 'RANGE' },        // GSI SK attribute 2
                { AttributeName: 'matchId', KeyType: 'RANGE' }         // GSI SK attribute 3
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'PlayerMatchHistoryIndex',
            KeySchema: [
                { AttributeName: 'player1Id', KeyType: 'HASH' },       // GSI PK
                { AttributeName: 'matchDate', KeyType: 'RANGE' },      // GSI SK attribute 1
                { AttributeName: 'round', KeyType: 'RANGE' }           // GSI SK attribute 2
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    
    BillingMode: 'PAY_PER_REQUEST'
}));

console.log("Table with multi-attribute GSI keys created successfully");
```

**主要な設計上の決定事項:**

**ベーステーブル:** ベーステーブルは、直接一致検索にシンプルな `matchId` パーティションキーを使用し、GSI が複雑なクエリパターンを提供する間、ベーステーブル構造を分かりやすく維持します。

**TournamentRegionIndex グローバルセカンダリインデックス:** `TournamentRegionIndex` グローバルセカンダリインデックスは、`tournamentId` \$1 `region` をマルチ属性パーティションキーとして使用し、両方の属性を組み合わせたハッシュによってデータが分散されるトーナメントリージョンの分離を作成し、特定のトーナメントリージョンのコンテキスト内で効率的なクエリを可能にします。マルチ属性ソートキー (`round` \$1 `bracket` \$1 `matchId`) は、階層の任意のレベルでクエリをサポートする階層ソートを提供し、一般的な (ラウンド) から特定の (マッチ ID) へ自然な順序で並べ替えます。

**PlayerMatchHistoryIndex グローバルセカンダリインデックス:** `PlayerMatchHistoryIndex` グローバルセカンダリインデックスは、パーティションキーとして `player1Id` を使用して選手ごとにデータを再編成し、特定の選手のクロストーナメントクエリを有効にします。マルチ属性ソートキー (`matchDate` \$1 `round`) は、日付範囲または特定のトーナメントラウンドでフィルタリングできる時系列の順序を提供します。

### ステップ 2: ネイティブ属性を使用してデータを挿入する
<a name="GSI.DesignPattern.MultiAttributeKeys.InsertData"></a>

自然属性を使用してトーナメントマッチデータを追加します。GSI は、合成キーを必要とせずに、これらの属性を自動的にインデックス化します。

#### コード例
<a name="w2aac19c13c45c23b9c11b5b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Tournament match data - no synthetic keys needed for GSIs
const matches = [
    // Winter 2024 Tournament, NA-EAST region
    {
        matchId: 'match-001',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'FINALS',
        bracket: 'CHAMPIONSHIP',
        player1Id: '101',
        player2Id: '103',
        matchDate: '2024-01-20',
        winner: '101',
        score: '3-1'
    },
    {
        matchId: 'match-002',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '105',
        matchDate: '2024-01-18',
        winner: '101',
        score: '3-2'
    },
    {
        matchId: 'match-003',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '103',
        player2Id: '107',
        matchDate: '2024-01-18',
        winner: '103',
        score: '3-0'
    },
    {
        matchId: 'match-004',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '109',
        matchDate: '2024-01-15',
        winner: '101',
        score: '3-1'
    },
    
    // Winter 2024 Tournament, NA-WEST region
    {
        matchId: 'match-005',
        tournamentId: 'WINTER2024',
        region: 'NA-WEST',
        round: 'FINALS',
        bracket: 'CHAMPIONSHIP',
        player1Id: '102',
        player2Id: '104',
        matchDate: '2024-01-20',
        winner: '102',
        score: '3-2'
    },
    {
        matchId: 'match-006',
        tournamentId: 'WINTER2024',
        region: 'NA-WEST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '102',
        player2Id: '106',
        matchDate: '2024-01-18',
        winner: '102',
        score: '3-1'
    },
    
    // Spring 2024 Tournament, NA-EAST region
    {
        matchId: 'match-007',
        tournamentId: 'SPRING2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '108',
        matchDate: '2024-03-15',
        winner: '101',
        score: '3-0'
    },
    {
        matchId: 'match-008',
        tournamentId: 'SPRING2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'LOWER',
        player1Id: '103',
        player2Id: '110',
        matchDate: '2024-03-15',
        winner: '103',
        score: '3-2'
    }
];

// Insert all matches
for (const match of matches) {
    await docClient.send(new PutCommand({
        TableName: 'TournamentMatches',
        Item: match
    }));
    
    console.log(`Added: ${match.matchId} - ${match.tournamentId}/${match.region} - ${match.round} ${match.bracket}`);
}

console.log(`\nInserted ${matches.length} tournament matches`);
console.log("No synthetic keys created - GSIs use native attributes automatically");
```

**データ構造の説明:**

**自然属性の使用:** 各属性は、文字列の連結や解析を必要とせずに実際のトーナメントの概念を表し、ドメインモデルへの直接マッピングを提供します。

**自動グローバルセカンダリインデックスの作成:** GSI、合成連結キーを必要とせずに、既存の属性 (TournamentRegionIndex `matchId`の場合は `tournamentId`、`region`、`round`、`bracket`、PlayerMatchHistoryIndex の場合は `player1Id`、`matchDate`、`round`) を使用して項目を自動的にインデックス作成します。

**バックフィルが不要:** マルチ属性キーを持つ新しいグローバルセカンダリインデックスを既存のテーブルに追加すると、DynamoDB は自然属性を使用して既存のすべての項目を自動的にインデックス化します。合成キーを使用して項目を更新する必要はありません。

### ステップ 3: すべてのパーティションキー属性を使用して TournamentRegionIndex グローバルセカンダリインデックスをクエリする
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryAllPartitionKeys"></a>

この例では、マルチ属性パーティションキー (`tournamentId` \$1 `region`) を持つ TournamentRegionIndex グローバルセカンダリインデックスをクエリします。すべてのパーティションキー属性は、クエリで等号条件で指定する必要があります。`tournamentId` 単独でクエリを実行したり、パーティションキー属性で不等価演算子を使用したりすることはできません。

#### コード例
<a name="w2aac19c13c45c23b9c11b7b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query GSI: All matches for WINTER2024 tournament in NA-EAST region
const response = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region',
    ExpressionAttributeNames: {
        '#region': 'region',  // 'region' is a reserved keyword
        '#tournament': 'tournament'
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST'
    }
}));

console.log(`Found ${response.Items.length} matches for WINTER2024/NA-EAST:\n`);
response.Items.forEach(match => {
    console.log(`  ${match.round} | ${match.bracket} | ${match.matchId}`);
    console.log(`    Players: ${match.player1Id} vs ${match.player2Id}`);
    console.log(`    Winner: ${match.winner}, Score: ${match.score}\n`);
});
```

**正常な出力:**

```
Found 4 matches for WINTER2024/NA-EAST:

  FINALS | CHAMPIONSHIP | match-001
    Players: 101 vs 103
    Winner: 101, Score: 3-1

  QUARTERFINALS | UPPER | match-004
    Players: 101 vs 109
    Winner: 101, Score: 3-1

  SEMIFINALS | UPPER | match-002
    Players: 101 vs 105
    Winner: 101, Score: 3-2

  SEMIFINALS | UPPER | match-003
    Players: 103 vs 107
    Winner: 103, Score: 3-0
```

**無効なクエリ:**

```
// Missing region attribute
KeyConditionExpression: 'tournamentId = :tournament'

// Using inequality on partition key attribute
KeyConditionExpression: 'tournamentId = :tournament AND #region > :region'
```

**パフォーマンス:** マルチ属性パーティションキーは共にハッシュされ、単一属性キーと同じ O(1) 検索パフォーマンスを提供します。

### ステップ 4: グローバルセカンダリインデックスのソートキー left-to-right をクエリする
<a name="GSI.DesignPattern.MultiAttributeKeys.QuerySortKeysLeftToRight"></a>

ソートキー属性は、グローバルセカンダリインデックスで定義されている順序で左から右にクエリする必要があります。この例では、TournamentRegionIndex をさまざまな階層レベルでクエリする方法を示します。フィルタリングは、`round` のみ、`round` \$1 `bracket`、または 3 つのソートキー属性すべてで行います。途中で属性をスキップすることはできません。例えば、`bracket` のスキップ中に `round` や `matchId` でクエリを実行することはできません。

#### コード例
<a name="w2aac19c13c45c23b9c11b9b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: Filter by first sort key attribute (round)
console.log("Query 1: All SEMIFINALS matches");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS'
    }
}));
console.log(`  Found ${query1.Items.length} matches\n`);

// Query 2: Filter by first two sort key attributes (round + bracket)
console.log("Query 2: SEMIFINALS UPPER bracket matches");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':bracket': 'UPPER'
    }
}));
console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Filter by all three sort key attributes (round + bracket + matchId)
console.log("Query 3: Specific match in SEMIFINALS UPPER bracket");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket AND matchId = :matchId',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':bracket': 'UPPER',
        ':matchId': 'match-002'
    }
}));
console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: INVALID - skipping round
console.log("Query 4: Attempting to skip first sort key attribute (WILL FAIL)");
try {
    const query4 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND bracket = :bracket',
        ExpressionAttributeNames: {
            '#region': 'region'  // 'region' is a reserved keyword
        },
        ExpressionAttributeValues: {
            ':tournament': 'WINTER2024',
            ':region': 'NA-EAST',
            ':bracket': 'UPPER'
        }
    }));
} catch (error) {
    console.log(`  Error: ${error.message}`);
    console.log(`  Cannot skip sort key attributes - must query left-to-right\n`);
}
```

**正常な出力:**

```
Query 1: All SEMIFINALS matches
  Found 2 matches

Query 2: SEMIFINALS UPPER bracket matches
  Found 2 matches

Query 3: Specific match in SEMIFINALS UPPER bracket
  Found 1 matches

Query 4: Attempting to skip first sort key attribute (WILL FAIL)
  Error: Query key condition not supported
  Cannot skip sort key attributes - must query left-to-right
```

**Left-to-right クエリルール:** なにもスキップせずに左から右の順にクエリする必要があります。

**有効なパターン:**
+ 最初の属性のみ: `round = 'SEMIFINALS'`
+ 最初の 2 つの属性: `round = 'SEMIFINALS' AND bracket = 'UPPER'`
+ 3 つの属性すべて: `round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId = 'match-002'`

**無効なパターン:**
+ 最初の属性をスキップする: `bracket = 'UPPER'` (ラウンドをスキップ)
+ 順不同のクエリ: `matchId = 'match-002' AND round = 'SEMIFINALS'`
+ ギャップを残す: `round = 'SEMIFINALS' AND matchId = 'match-002'` (ブラケットをスキップ)

**注記**  
**設計のヒント:** クエリの柔軟性を最大化するために、ソートキー属性を最も一般的なものから最も具体的な順に並べます。

### ステップ 5: グローバルセカンダリインデックスのソートキーで不等価条件を使用する
<a name="GSI.DesignPattern.MultiAttributeKeys.InequalityConditions"></a>

不等価条件は、クエリの最後の条件である必要があります。この例では、ソートキー属性で比較演算子 (`>=`、`BETWEEN`) とプレフィックスマッチング (`begins_with()`) を使用する方法を示します。不等価演算子を使用すると、その後にソートキー条件を追加することはできません。不等価はキー条件式の最終条件である必要があります。

#### コード例
<a name="w2aac19c13c45c23b9c11c11b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: Round comparison (inequality on first sort key attribute)
console.log("Query 1: Matches from QUARTERFINALS onwards");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round >= :round',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'QUARTERFINALS'
    }
}));
console.log(`  Found ${query1.Items.length} matches\n`);

// Query 2: Round range with BETWEEN
console.log("Query 2: Matches between QUARTERFINALS and SEMIFINALS");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round BETWEEN :start AND :end',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':start': 'QUARTERFINALS',
        ':end': 'SEMIFINALS'
    }
}));
console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Prefix matching with begins_with (treated as inequality)
console.log("Query 3: Matches in brackets starting with 'U'");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND begins_with(bracket, :prefix)',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':prefix': 'U'
    }
}));
console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: INVALID - condition after inequality
console.log("Query 4: Attempting condition after inequality (WILL FAIL)");
try {
    const query4 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round > :round AND bracket = :bracket',
        ExpressionAttributeNames: {
            '#region': 'region'  // 'region' is a reserved keyword
        },
        ExpressionAttributeValues: {
            ':tournament': 'WINTER2024',
            ':region': 'NA-EAST',
            ':round': 'QUARTERFINALS',
            ':bracket': 'UPPER'
        }
    }));
} catch (error) {
    console.log(`  Error: ${error.message}`);
    console.log(`  Cannot add conditions after inequality - it must be last\n`);
}
```

**不等価演算子ルール:** 比較演算子 (`>`、`>=`、`<`、`<=`) を使用できます。`BETWEEN` は範囲クエリに、`begins_with()` はプレフィックスマッチングに使用できます。不等価は、クエリの最後の条件である必要があります。

**有効なパターン:**
+ 等価条件の後に不等価が続きます: `round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId > 'match-001'`
+ 最初の属性の不等価: `round BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'`
+ 最終条件としてのプレフィックスマッチング: `round = 'SEMIFINALS' AND begins_with(bracket, 'U')`

**無効なパターン:**
+ 不等価の後に条件を追加する: `round > 'QUARTERFINALS' AND bracket = 'UPPER'`
+ 複数の不等価の使用: `round > 'QUARTERFINALS' AND bracket > 'L'`

**重要**  
`begins_with()` は不等価条件として扱われるため、追加のソートキー条件をフォローすることはできません。

### ステップ 6: マルチ属性ソートキーを使用して PlayerMatchHistoryIndex グローバルセカンダリインデックスをクエリする
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryPlayerHistory"></a>

この例では、単一のパーティションキー (`player1Id`) とマルチ属性ソートキー (`matchDate` \$1 `round`) を持つ PlayerMatchHistoryIndex をクエリします。これにより、トーナメント ID を知らずに特定の選手のすべてのマッチングをクエリすることで、クロストーナメント分析が可能になります。ベーステーブルでは、トーナメントとリージョンの組み合わせごとに個別のクエリが必要になります。

#### コード例
<a name="w2aac19c13c45c23b9c11c13b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: All matches for Player 101 across all tournaments
console.log("Query 1: All matches for Player 101");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player',
    ExpressionAttributeValues: {
        ':player': '101'
    }
}));

console.log(`  Found ${query1.Items.length} matches for Player 101:`);
query1.Items.forEach(match => {
    console.log(`    ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`);
});
console.log();

// Query 2: Player 101 matches on specific date
console.log("Query 2: Player 101 matches on 2024-01-18");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate = :date',
    ExpressionAttributeValues: {
        ':player': '101',
        ':date': '2024-01-18'
    }
}));

console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Player 101 SEMIFINALS matches on specific date
console.log("Query 3: Player 101 SEMIFINALS matches on 2024-01-18");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate = :date AND round = :round',
    ExpressionAttributeValues: {
        ':player': '101',
        ':date': '2024-01-18',
        ':round': 'SEMIFINALS'
    }
}));

console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: Player 101 matches in date range
console.log("Query 4: Player 101 matches in January 2024");
const query4 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate BETWEEN :start AND :end',
    ExpressionAttributeValues: {
        ':player': '101',
        ':start': '2024-01-01',
        ':end': '2024-01-31'
    }
}));

console.log(`  Found ${query4.Items.length} matches\n`);
```

## パターンのバリエーション
<a name="GSI.DesignPattern.MultiAttributeKeys.PatternVariations"></a>

### マルチ属性キーを使用した時系列データ
<a name="GSI.DesignPattern.MultiAttributeKeys.TimeSeries"></a>

階層時間属性を使用して時系列クエリを最適化する

#### コード例
<a name="w2aac19c13c45c23b9c13b3b5b1"></a>

```
{
    TableName: 'IoTReadings',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'readingId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'readingId', AttributeType: 'S' },
        { AttributeName: 'deviceId', AttributeType: 'S' },
        { AttributeName: 'locationId', AttributeType: 'S' },
        { AttributeName: 'year', AttributeType: 'S' },
        { AttributeName: 'month', AttributeType: 'S' },
        { AttributeName: 'day', AttributeType: 'S' },
        { AttributeName: 'timestamp', AttributeType: 'S' }
    ],
    // GSI with multi-attribute keys for time-series queries
    GlobalSecondaryIndexes: [{
        IndexName: 'DeviceLocationTimeIndex',
        KeySchema: [
            { AttributeName: 'deviceId', KeyType: 'HASH' },
            { AttributeName: 'locationId', KeyType: 'HASH' },
            { AttributeName: 'year', KeyType: 'RANGE' },
            { AttributeName: 'month', KeyType: 'RANGE' },
            { AttributeName: 'day', KeyType: 'RANGE' },
            { AttributeName: 'timestamp', KeyType: 'RANGE' }
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query patterns enabled via GSI:
// - All readings for device in location
// - Readings for specific year
// - Readings for specific month in year
// - Readings for specific day
// - Readings in time range
```

**メリット:** 自然時間階層 (年 → 月 → 日 → タイムスタンプ) を使用すると、日付の解析や操作を行わずに、いつでも効率的にきめ細やかなクエリを実行できます。グローバルセカンダリインデックスは、自然時間属性を使用してすべての読み取り値を自動的にインデックス化します。

### マルチ属性キーを使用した e コマース注文
<a name="GSI.DesignPattern.MultiAttributeKeys.ECommerce"></a>

複数のディメンションを持つ注文を追跡する

#### コード例
<a name="w2aac19c13c45c23b9c13b5b5b1"></a>

```
{
    TableName: 'Orders',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'orderId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'orderId', AttributeType: 'S' },
        { AttributeName: 'sellerId', AttributeType: 'S' },
        { AttributeName: 'region', AttributeType: 'S' },
        { AttributeName: 'orderDate', AttributeType: 'S' },
        { AttributeName: 'category', AttributeType: 'S' },
        { AttributeName: 'customerId', AttributeType: 'S' },
        { AttributeName: 'orderStatus', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'SellerRegionIndex',
            KeySchema: [
                { AttributeName: 'sellerId', KeyType: 'HASH' },
                { AttributeName: 'region', KeyType: 'HASH' },
                { AttributeName: 'orderDate', KeyType: 'RANGE' },
                { AttributeName: 'category', KeyType: 'RANGE' },
                { AttributeName: 'orderId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'CustomerOrdersIndex',
            KeySchema: [
                { AttributeName: 'customerId', KeyType: 'HASH' },
                { AttributeName: 'orderDate', KeyType: 'RANGE' },
                { AttributeName: 'orderStatus', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// SellerRegionIndex GSI queries:
// - Orders by seller and region
// - Orders by seller, region, and date
// - Orders by seller, region, date, and category

// CustomerOrdersIndex GSI queries:
// - Customer's orders
// - Customer's orders by date
// - Customer's orders by date and status
```

### 階層組織データ
<a name="GSI.DesignPattern.MultiAttributeKeys.Hierarchical"></a>

モデル組織階層

#### コード例
<a name="w2aac19c13c45c23b9c13b7b5b1"></a>

```
{
    TableName: 'Employees',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'employeeId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'employeeId', AttributeType: 'S' },
        { AttributeName: 'companyId', AttributeType: 'S' },
        { AttributeName: 'divisionId', AttributeType: 'S' },
        { AttributeName: 'departmentId', AttributeType: 'S' },
        { AttributeName: 'teamId', AttributeType: 'S' },
        { AttributeName: 'skillCategory', AttributeType: 'S' },
        { AttributeName: 'skillLevel', AttributeType: 'S' },
        { AttributeName: 'yearsExperience', AttributeType: 'N' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'OrganizationIndex',
            KeySchema: [
                { AttributeName: 'companyId', KeyType: 'HASH' },
                { AttributeName: 'divisionId', KeyType: 'HASH' },
                { AttributeName: 'departmentId', KeyType: 'RANGE' },
                { AttributeName: 'teamId', KeyType: 'RANGE' },
                { AttributeName: 'employeeId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'SkillsIndex',
            KeySchema: [
                { AttributeName: 'skillCategory', KeyType: 'HASH' },
                { AttributeName: 'skillLevel', KeyType: 'RANGE' },
                { AttributeName: 'yearsExperience', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'INCLUDE', NonKeyAttributes: ['employeeId', 'name'] }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// OrganizationIndex GSI query patterns:
// - All employees in company/division
// - Employees in specific department
// - Employees in specific team

// SkillsIndex GSI query patterns:
// - Employees by skill and experience level
```

### スパースマルチ属性キー
<a name="GSI.DesignPattern.MultiAttributeKeys.Sparse"></a>

マルチ属性キーを組み合わせてスパース GSI を作成する

#### コード例
<a name="w2aac19c13c45c23b9c13b9b5b1"></a>

```
{
    TableName: 'Products',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'productId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'productId', AttributeType: 'S' },
        { AttributeName: 'categoryId', AttributeType: 'S' },
        { AttributeName: 'subcategoryId', AttributeType: 'S' },
        { AttributeName: 'averageRating', AttributeType: 'N' },
        { AttributeName: 'reviewCount', AttributeType: 'N' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'CategoryIndex',
            KeySchema: [
                { AttributeName: 'categoryId', KeyType: 'HASH' },
                { AttributeName: 'subcategoryId', KeyType: 'HASH' },
                { AttributeName: 'productId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'ReviewedProductsIndex',
            KeySchema: [
                { AttributeName: 'categoryId', KeyType: 'HASH' },
                { AttributeName: 'averageRating', KeyType: 'RANGE' },  // Optional attribute
                { AttributeName: 'reviewCount', KeyType: 'RANGE' }     // Optional attribute
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// Only products with reviews appear in ReviewedProductsIndex GSI
// Automatic filtering without application logic
// Multi-attribute sort key enables rating and count queries
```

### SaaS とマルチテナンシー
<a name="GSI.DesignPattern.MultiAttributeKeys.SaaS"></a>

顧客分離を備えたマルチテナント SaaS プラットフォーム

#### コード例
<a name="w2aac19c13c45c23b9c13c11b5b1"></a>

```
// Table design
{
    TableName: 'SaasData',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'resourceId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'resourceId', AttributeType: 'S' },
        { AttributeName: 'tenantId', AttributeType: 'S' },
        { AttributeName: 'customerId', AttributeType: 'S' },
        { AttributeName: 'resourceType', AttributeType: 'S' }
    ],
    // GSI with multi-attribute keys for tenant-customer isolation
    GlobalSecondaryIndexes: [{
        IndexName: 'TenantCustomerIndex',
        KeySchema: [
            { AttributeName: 'tenantId', KeyType: 'HASH' },
            { AttributeName: 'customerId', KeyType: 'HASH' },
            { AttributeName: 'resourceType', KeyType: 'RANGE' },
            { AttributeName: 'resourceId', KeyType: 'RANGE' }
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query GSI: All resources for tenant T001, customer C001
const resources = await docClient.send(new QueryCommand({
    TableName: 'SaasData',
    IndexName: 'TenantCustomerIndex',
    KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer',
    ExpressionAttributeValues: {
        ':tenant': 'T001',
        ':customer': 'C001'
    }
}));

// Query GSI: Specific resource type for tenant/customer
const documents = await docClient.send(new QueryCommand({
    TableName: 'SaasData',
    IndexName: 'TenantCustomerIndex',
    KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer AND resourceType = :type',
    ExpressionAttributeValues: {
        ':tenant': 'T001',
        ':customer': 'C001',
        ':type': 'document'
    }
}));
```

**メリット:** テナントと顧客のコンテキストと自然データ組織内の効率的なクエリ。

### 金融取引
<a name="GSI.DesignPattern.MultiAttributeKeys.Financial"></a>

GSI を使用した銀行システム追跡アカウントトランザクション

#### コード例
<a name="w2aac19c13c45c23b9c13c13b5b1"></a>

```
// Table design
{
    TableName: 'BankTransactions',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'transactionId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'transactionId', AttributeType: 'S' },
        { AttributeName: 'accountId', AttributeType: 'S' },
        { AttributeName: 'year', AttributeType: 'S' },
        { AttributeName: 'month', AttributeType: 'S' },
        { AttributeName: 'day', AttributeType: 'S' },
        { AttributeName: 'transactionType', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'AccountTimeIndex',
            KeySchema: [
                { AttributeName: 'accountId', KeyType: 'HASH' },
                { AttributeName: 'year', KeyType: 'RANGE' },
                { AttributeName: 'month', KeyType: 'RANGE' },
                { AttributeName: 'day', KeyType: 'RANGE' },
                { AttributeName: 'transactionId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'TransactionTypeIndex',
            KeySchema: [
                { AttributeName: 'accountId', KeyType: 'HASH' },
                { AttributeName: 'transactionType', KeyType: 'RANGE' },
                { AttributeName: 'year', KeyType: 'RANGE' },
                { AttributeName: 'month', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query AccountTimeIndex GSI: All transactions for account in 2023
const yearTransactions = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'AccountTimeIndex',
    KeyConditionExpression: 'accountId = :account AND #year = :year',
    ExpressionAttributeNames: { '#year': 'year' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':year': '2023'
    }
}));

// Query AccountTimeIndex GSI: Transactions in specific month
const monthTransactions = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'AccountTimeIndex',
    KeyConditionExpression: 'accountId = :account AND #year = :year AND #month = :month',
    ExpressionAttributeNames: { '#year': 'year', '#month': 'month' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':year': '2023',
        ':month': '11'
    }
}));

// Query TransactionTypeIndex GSI: Deposits in 2023
const deposits = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'TransactionTypeIndex',
    KeyConditionExpression: 'accountId = :account AND transactionType = :type AND #year = :year',
    ExpressionAttributeNames: { '#year': 'year' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':type': 'deposit',
        ':year': '2023'
    }
}));
```

## 完全な例
<a name="GSI.DesignPattern.MultiAttributeKeys.CompleteExample"></a>

次の例は、セットアップからクリーンアップまでのマルチ属性キーを示しています。

### コード例
<a name="w2aac19c13c45c23b9c15b5b1"></a>

```
import { 
    DynamoDBClient, 
    CreateTableCommand, 
    DeleteTableCommand, 
    waitUntilTableExists 
} from "@aws-sdk/client-dynamodb";
import { 
    DynamoDBDocumentClient, 
    PutCommand, 
    QueryCommand 
} from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

async function multiAttributeKeysDemo() {
    console.log("Starting Multi-Attribute GSI Keys Demo\n");
    
    // Step 1: Create table with GSIs using multi-attribute keys
    console.log("1. Creating table with multi-attribute GSI keys...");
    await client.send(new CreateTableCommand({
        TableName: 'TournamentMatches',
        KeySchema: [
            { AttributeName: 'matchId', KeyType: 'HASH' }
        ],
        AttributeDefinitions: [
            { AttributeName: 'matchId', AttributeType: 'S' },
            { AttributeName: 'tournamentId', AttributeType: 'S' },
            { AttributeName: 'region', AttributeType: 'S' },
            { AttributeName: 'round', AttributeType: 'S' },
            { AttributeName: 'bracket', AttributeType: 'S' },
            { AttributeName: 'player1Id', AttributeType: 'S' },
            { AttributeName: 'matchDate', AttributeType: 'S' }
        ],
        GlobalSecondaryIndexes: [
            {
                IndexName: 'TournamentRegionIndex',
                KeySchema: [
                    { AttributeName: 'tournamentId', KeyType: 'HASH' },
                    { AttributeName: 'region', KeyType: 'HASH' },
                    { AttributeName: 'round', KeyType: 'RANGE' },
                    { AttributeName: 'bracket', KeyType: 'RANGE' },
                    { AttributeName: 'matchId', KeyType: 'RANGE' }
                ],
                Projection: { ProjectionType: 'ALL' }
            },
            {
                IndexName: 'PlayerMatchHistoryIndex',
                KeySchema: [
                    { AttributeName: 'player1Id', KeyType: 'HASH' },
                    { AttributeName: 'matchDate', KeyType: 'RANGE' },
                    { AttributeName: 'round', KeyType: 'RANGE' }
                ],
                Projection: { ProjectionType: 'ALL' }
            }
        ],
        BillingMode: 'PAY_PER_REQUEST'
    }));
    
    await waitUntilTableExists({ client, maxWaitTime: 120 }, { TableName: 'TournamentMatches' });
    console.log("Table created\n");
    
    // Step 2: Insert tournament matches
    console.log("2. Inserting tournament matches...");
    const matches = [
        { matchId: 'match-001', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '101', player2Id: '103', matchDate: '2024-01-20', winner: '101', score: '3-1' },
        { matchId: 'match-002', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '105', matchDate: '2024-01-18', winner: '101', score: '3-2' },
        { matchId: 'match-003', tournamentId: 'WINTER2024', region: 'NA-WEST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '102', player2Id: '104', matchDate: '2024-01-20', winner: '102', score: '3-2' },
        { matchId: 'match-004', tournamentId: 'SPRING2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '108', matchDate: '2024-03-15', winner: '101', score: '3-0' }
    ];
    
    for (const match of matches) {
        await docClient.send(new PutCommand({ TableName: 'TournamentMatches', Item: match }));
    }
    console.log(`Inserted ${matches.length} tournament matches\n`);
    
    // Step 3: Query GSI with multi-attribute partition key
    console.log("3. Query TournamentRegionIndex GSI: WINTER2024/NA-EAST matches");
    const gsiQuery1 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region',
        ExpressionAttributeNames: { '#region': 'region' },
        ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST' }
    }));
    
    console.log(`  Found ${gsiQuery1.Items.length} matches:`);
    gsiQuery1.Items.forEach(match => {
        console.log(`    ${match.round} - ${match.bracket} - ${match.winner} won`);
    });
    
    // Step 4: Query GSI with multi-attribute sort key
    console.log("\n4. Query PlayerMatchHistoryIndex GSI: All matches for Player 101");
    const gsiQuery2 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'PlayerMatchHistoryIndex',
        KeyConditionExpression: 'player1Id = :player',
        ExpressionAttributeValues: { ':player': '101' }
    }));
    
    console.log(`  Found ${gsiQuery2.Items.length} matches for Player 101:`);
    gsiQuery2.Items.forEach(match => {
        console.log(`    ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`);
    });
    
    console.log("\nDemo complete");
    console.log("No synthetic keys needed - GSIs use native attributes automatically");
}

async function cleanup() {
    console.log("Deleting table...");
    await client.send(new DeleteTableCommand({ TableName: 'TournamentMatches' }));
    console.log("Table deleted");
}

// Run demo
multiAttributeKeysDemo().catch(console.error);

// Uncomment to cleanup:
// cleanup().catch(console.error);
```

**最小コードの雛形**

### コード例
<a name="w2aac19c13c45c23b9c15b9b1"></a>

```
// 1. Create table with GSI using multi-attribute keys
await client.send(new CreateTableCommand({
    TableName: 'MyTable',
    KeySchema: [
        { AttributeName: 'id', KeyType: 'HASH' }        // Simple base table PK
    ],
    AttributeDefinitions: [
        { AttributeName: 'id', AttributeType: 'S' },
        { AttributeName: 'attr1', AttributeType: 'S' },
        { AttributeName: 'attr2', AttributeType: 'S' },
        { AttributeName: 'attr3', AttributeType: 'S' },
        { AttributeName: 'attr4', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [{
        IndexName: 'MyGSI',
        KeySchema: [
            { AttributeName: 'attr1', KeyType: 'HASH' },    // GSI PK attribute 1
            { AttributeName: 'attr2', KeyType: 'HASH' },    // GSI PK attribute 2
            { AttributeName: 'attr3', KeyType: 'RANGE' },   // GSI SK attribute 1
            { AttributeName: 'attr4', KeyType: 'RANGE' }    // GSI SK attribute 2
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}));

// 2. Insert items with native attributes (no concatenation needed for GSI)
await docClient.send(new PutCommand({
    TableName: 'MyTable',
    Item: {
        id: 'item-001',
        attr1: 'value1',
        attr2: 'value2',
        attr3: 'value3',
        attr4: 'value4',
        // ... other attributes
    }
}));

// 3. Query GSI with all partition key attributes
await docClient.send(new QueryCommand({
    TableName: 'MyTable',
    IndexName: 'MyGSI',
    KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2',
    ExpressionAttributeValues: {
        ':v1': 'value1',
        ':v2': 'value2'
    }
}));

// 4. Query GSI with sort key attributes (left-to-right)
await docClient.send(new QueryCommand({
    TableName: 'MyTable',
    IndexName: 'MyGSI',
    KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2 AND attr3 = :v3',
    ExpressionAttributeValues: {
        ':v1': 'value1',
        ':v2': 'value2',
        ':v3': 'value3'
    }
}));

// Note: If any attribute name is a DynamoDB reserved keyword, use ExpressionAttributeNames:
// KeyConditionExpression: 'attr1 = :v1 AND #attr2 = :v2'
// ExpressionAttributeNames: { '#attr2': 'attr2' }
```

## その他のリソース
<a name="GSI.DesignPattern.MultiAttributeKeys.AdditionalResources"></a>
+ [DynamoDB のベストプラクティス](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html)
+ [テーブルとデータの操作](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)
+ [グローバルセカンダリインデックス](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html): 
+ [クエリおよびスキャンオペレーション](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html)

# DynamoDB のグローバルセカンダリインデックスの管理
<a name="GSI.OnlineOps"></a>

このセクションでは、Amazon DynamoDB でグローバルセカンダリインデックスを作成、変更、削除する方法について説明します。

**Topics**
+ [グローバルセカンダリインデックスを持つテーブルの作成](#GSI.Creating)
+ [テーブルのグローバルセカンダリインデックスの説明](#GSI.Describing)
+ [グローバルセカンダリインデックスの既存テーブルへの追加](#GSI.OnlineOps.Creating)
+ [グローバルセカンダリインデックスの削除](#GSI.OnlineOps.Deleting)
+ [作成時のグローバルセカンダリインデックスの変更](#GSI.OnlineOps.Creating.Modify)

## グローバルセカンダリインデックスを持つテーブルの作成
<a name="GSI.Creating"></a>

1 つ以上のグローバルセカンダリインデックスを持つテーブルを作成するには、`CreateTable` オペレーションと `GlobalSecondaryIndexes` パラメータを使用します。最大限のクエリの柔軟性を得るために、テーブルごとに最大 20 個のグローバルセカンダリインデックス (デフォルトのクォータ) を作成できます。

インデックスパーティションキーとして機能する属性を 1 つ指定する必要があります。必要に応じて、インデックスソートキーに別の属性を指定できます。これらのキー属性のいずれも、テーブルのキー属性と同じである必要はありません。例えば、*GameScores* テーブル (「[DynamoDB のグローバルセカンダリインデックスの使用](GSI.md)」参照) では、`TopScore` も `TopScoreDateTime` もキー属性ではありません。パーティションキーとして `TopScore`、ソートキーとして `TopScoreDateTime` を使用して、グローバルセカンダリインデックスを作成できます。このようなインデックスを使用して、ハイスコアとゲームのプレイ時刻との間に相関があるかどうかを判定できる可能性があります。

各インデックスキー属性は、`String`、`Number`、または `Binary` タイプのスカラーである必要があります。(ドキュメントまたはセットは指定できません)。グローバルセカンダリインデックスには、任意のデータ型の属性を射影できます。これには、スカラー、ドキュメント、およびセットが含まれます。データ型の詳細なリストについては、「[データ型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)」を参照してください。

プロビジョニング済みモードを使用する場合は、インデックスに `ReadCapacityUnits` および `WriteCapacityUnits` で構成される `ProvisionedThroughput` 設定をする必要があります。これらのプロビジョニングされたスループット設定は、テーブルの設定とは別ですが、同様の動作をします。詳細については、「」を参照してください[グローバルセカンダリインデックスに対するプロビジョニングされたスループットに関する考慮事項](GSI.md#GSI.ThroughputConsiderations)

 グローバルセカンダリーインデックスは、基本テーブルから読み込み/書き込みキャパシティーモードを継承します。詳細については、「[DynamoDB でキャパシティモードを切り替える際の考慮事項](bp-switching-capacity-modes.md)」を参照してください。

**注記**  
 新しい GSI を作成する際、選択したパーティションキーが、新しいインデックスのパーティションキー値全体でデータやトラフィックの分散が不均一だったり、狭くなっているかを確認することが重要になります。この場合、バックフィル操作と書き込み操作が同時に発生し、ベーステーブルへの書き込みをスロットリングしている可能性があります。このサービスでは、このシナリオの可能性を最小限に抑えるための対策を講じていますが、インデックスパーティションキー、選択した射影、またはインデックスプライマリキーのスパース性について、カスタマーデータのインサイトを見える形で取得していません。  
新しいグローバルセカンダリインデックスで、パーティションキー値全体でデータやトラフィックの分散が狭められたり、歪められたりしている可能性がある場合は、運用上重要なテーブルに新しいインデックスを追加する前に、次の点を考慮してください。  
アプリケーションのトラフィック量が最も少ない時間帯にインデックスを追加するのが、最も安全な方法と言えます。
ベーステーブルとインデックスで CloudWatch Contributor Insights 有効にすることを検討してください。これにより、トラフィックの分散に関する貴重なインサイトが得られます。
 プロセス全体を通して、CloudWatch メトリクス `WriteThrottleEvents`、`ThrottledRequests`、および `OnlineIndexPercentageProgress` を表示します。必要に応じて、プロビジョニングされた書き込みキャパシティを調整し、進行中の操作に対してスロットリングによる大きな影響を与えることなく、適切な時間内にバックフィルを完了します。`OnlineIndexConsumedWriteCapacity` と `OnlineThrottleEvents` は、インデックスバックフィル中は 0 と表示されると予想されます。
書き込みスロットリングによる運用上の影響が発生した場合は、インデックスの作成をキャンセルする準備をしてください。

## テーブルのグローバルセカンダリインデックスの説明
<a name="GSI.Describing"></a>

テーブルのすべてのグローバルセカンダリインデックスのステータスを表示するには、`DescribeTable` オペレーションを使用します。レスポンスの `GlobalSecondaryIndexes` 部分は、テーブル上のすべてのインデックスと、それぞれの現在のステータス (`IndexStatus`) を示しています。

グローバルセカンダリインデックスの `IndexStatus` は、次のいずれかになります。
+ `CREATING` – インデックスは現在作成されていますが、まだ使用することはできません。
+ `ACTIVE` – インデックスは使用可能になり、アプリケーションはインデックスに対して `Query` オペレーションを実行できます。
+ `UPDATING` – インデックスのプロビジョニングされたスループット設定の変更中です。
+ `DELETING` – インデックスは現在削除されているため、使用できなくなっています。

DynamoDB がグローバルセカンダリインデックスの作成を完了すると、インデックスのステータスが `CREATING` から `ACTIVE` に変わります。

## グローバルセカンダリインデックスの既存テーブルへの追加
<a name="GSI.OnlineOps.Creating"></a>

既存のテーブルにグローバルセカンダリインデックスを追加するには、`UpdateTable` オペレーションと `GlobalSecondaryIndexUpdates` パラメータを使用します。以下の情報が必要です。
+ インデックス名。名前は、テーブル内のすべてのインデックスにおいて一意である必要があります。
+ インデックスのキースキーマ。インデックスパーティションキーには 1 つの属性を指定する必要があります。必要に応じて、インデックスソートキーに別の属性を指定できます。これらのキー属性のいずれも、テーブルのキー属性と同じである必要はありません。各スキーマ属性のデータ型は、`String`、`Number`、または `Binary` のスカラーである必要があります。
+ テーブルからインデックスに射影される属性は以下の通りです。
  + `KEYS_ONLY` – インデックス内の各項目は、テーブルパーティションキーとソートキーの値、およびインデックスキーの値のみで構成されます。
  + `INCLUDE` – `KEYS_ONLY` の属性に加えて、セカンダリインデックスにその他の非キー属性が含まれるように指定できます。
  + `ALL` – インデックスには、ソーステーブルのすべての属性が含まれます。
+ インデックスのプロビジョニングされたスループット設定で、`ReadCapacityUnits` および `WriteCapacityUnits` で構成されます。これは、テーブルのプロビジョニングされたスループット設定とは異なります。

`UpdateTable` オペレーションについて作成できるグローバルセカンダリインデックスは 1 つだけです。

### インデックス作成のフェーズ
<a name="GSI.OnlineOps.Creating.Phases"></a>

新しいグローバルセカンダリインデックスを既存のテーブルに追加すると、インデックスの構築中もテーブルが使用可能になります。ただし、新しいインデックスは、そのステータスが `CREATING` から `ACTIVE` に変わるまでクエリオペレーションに使用できません。

**注記**  
グローバルセカンダリインデックスの作成では、Application Auto Scaling を使用しません。`MIN` Application Auto Scaling の容量を増やしても、グローバルセカンダリインデックスの作成時間は短縮されません。

その裏では、DynamoDB は 2 つのフェーズでインデックスを構築しています。

**リソース割り当て**  
DynamoDB は、インデックスの構築に必要なコンピューティングリソースとストレージリソースを割り当てます。  
リソース割り当てフェーズでは、`IndexStatus` 属性は `CREATING`、`Backfilling` 属性は false です。`DescribeTable` オペレーションを使用して、テーブルとそのセカンダリインデックスすべてのステータスを取得します。  
インデックスがリソース割り当てフェーズにある間は、インデックスやその親テーブルを削除することはできません。インデックスまたはテーブルのプロビジョニングされたスループットを変更することもできません。テーブル上の他のインデックスを追加したり削除したりすることはできません。ただし、これらの他のインデックスのプロビジョニングされたスループットは変更できます。

**バックフィル**  
テーブルの各項目について、DynamoDB は、その射影 (`KEYS_ONLY`、`INCLUDE`、または `ALL`) に基づいて、インデックスに書き込む属性のセットを決定します。次に、これらの属性をインデックスに書き込みます。バックフィルフェーズでは、DynamoDB はテーブルで追加、削除、または更新される項目を追跡します。これらの項目の属性も、必要に応じてインデックスで追加、削除、または更新されます。  
バックフィルフェーズでは、`IndexStatus` 属性は `CREATING` に設定されていて、`Backfilling` 属性は true です。`DescribeTable` オペレーションを使用して、テーブルとそのセカンダリインデックスすべてのステータスを取得します。  
インデックスがバックフィルされている間は、親テーブルを削除することはできません。ただし、インデックスを削除したり、テーブルとそのグローバルセカンダリインデックスのプロビジョニングされたスループットを変更したりすることはできます。  
バックフィルフェーズでは、違反する項目の書き込みの一部が成功し、他の項目が拒否されることがあります。バックフィル後、新しいインデックスのキースキーマに違反する項目への書き込みはすべて拒否されます。バックフィルフェーズの終了後に Violation Detector ツールを実行して、発生した可能性のあるキー違反を検出して解決することをお勧めします。詳細については、「」を参照してください[DynamoDB でのインデックスキー違反の検出と修正](GSI.OnlineOps.ViolationDetection.md)

リソース割り当てフェーズとバックフィルフェーズの進行中、インデックスは `CREATING` 状態になっています。この間、DynamoDB はテーブルに対して読み込みオペレーションを実行します。グローバルセカンダリインデックスを設定するためのベーステーブルからの読み込みオペレーションに対しては課金されません。

インデックス構築が完了すると、ステータスは `ACTIVE` に変わります。インデックスが `ACTIVE` になるまで、`Query` や `Scan` はできません。

**注記**  
場合によっては、DynamoDB は、インデックスキーの違反のため、テーブルからインデックスにデータを書き込めない場合があります。これは、以下の場合に発生します。  
属性値のデータ型が、インデックスキーのスキーマデータ型のデータ型と一致しません。
属性のサイズが、インデックスキー属性の最大長を超えています。
インデックスキー属性には、空の文字列または空のバイナリ属性値があります。
インデックスキー違反があっても、グローバルセカンダリインデックスの作成はできます。ただし、インデックスが `ACTIVE` になると、違反しているキーはインデックスに存在しなくなります。  
DynamoDB には、これらの問題を検出して解決するためのスタンドアロンツールが用意されています。詳細については、「[DynamoDB でのインデックスキー違反の検出と修正](GSI.OnlineOps.ViolationDetection.md)」を参照してください。

### 大きなテーブルへのグローバルセカンダリインデックスの追加
<a name="GSI.OnlineOps.Creating.LargeTable"></a>

グローバルセカンダリインデックスの構築に必要な時間は、次のようないくつかの要因によって異なります。
+ テーブルのサイズ
+ インデックスに含めることができるテーブル内の項目の数
+ インデックスに射影される属性の数。
+ インデックス構築中のメインテーブルへの書き込みアクティビティ

非常に大きなテーブルにグローバルセカンダリインデックスを追加する場合、作成プロセスが完了するまで時間がかかることがあります。進行状況をモニタリングし、インデックスに十分な書き込み容量があるかどうかを判断するには、次の Amazon CloudWatch メトリクスを参照してください。
+ `OnlineIndexPercentageProgress`

DynamoDB に関する CloudWatch メトリクスの詳細については、「[DynamoDB のメトリクス](metrics-dimensions.md#dynamodb-metrics)」を参照してください。

**重要**  
グローバルセカンダリインデックスを作成または更新する前に、非常に大きなテーブルを許可リストする必要がある場合があります。AWS サポートに連絡して、テーブルの許可リストを作成してください。

インデックスがバックフィルされている間、DynamoDB は内部システム容量を使用してテーブルから読み込みます。これは、インデックスの作成による影響を最小限に抑え、テーブルの読み込みキャパシティーが不足しないようにするためです。

## グローバルセカンダリインデックスの削除
<a name="GSI.OnlineOps.Deleting"></a>

グローバルセカンダリインデックスが不要になった場合には、`UpdateTable` オペレーションを使用して削除することができます。

グローバルセカンダリインデックスは、1 回の `UpdateTable` オペレーションで 1 つだけ削除できます。

グローバルセカンダリインデックスが削除されている間、親テーブルの読み込みまたは書き込みアクティビティに影響はありません。削除の進行中でも、他のインデックスでプロビジョニングされたスループットを変更できます。

**注記**  
`DeleteTable` アクションを使用してテーブルを削除すると、そのテーブルのすべてのグローバルセカンダリインデックスも削除されます。
アカウントではグローバルセカンダリインデックスの削除操作に対して課金されません。

## 作成時のグローバルセカンダリインデックスの変更
<a name="GSI.OnlineOps.Creating.Modify"></a>

インデックスが作成されている間は、`DescribeTable` オペレーションを使用して、どのフェーズにあるかを判断します。インデックスの説明に含まれているブール属性 `Backfilling` は、DynamoDB がテーブルから項目を含むインデックスを現在ロードしているかどうかを示します。`Backfilling` が true の場合、リソース割り当てフェーズは完了していて、インデックスがバックフィルされています。

バックフィルフェーズでは、作成中のインデックスを削除できます。このフェーズでは、テーブルの他のインデックスを追加または削除することはできません。

**注記**  
`CreateTable` オペレーションの一部として作成されたインデックスの場合、`Backfilling` 属性は `DescribeTable` 出力に現れません。詳細については、「[インデックス作成のフェーズ](#GSI.OnlineOps.Creating.Phases)」を参照してください。

# DynamoDB でのインデックスキー違反の検出と修正
<a name="GSI.OnlineOps.ViolationDetection"></a>

グローバルセカンダリインデックスの作成のバックフィルフェーズでは、Amazon DynamoDB はテーブルの各項目を調べて、インデックスに含める適格性があるかどうかを判断します。一部の項目は、インデックスキー違反の原因となるため、適格でない場合があります。このような場合、項目はテーブルに残りますが、インデックスにはその項目に対応するエントリがなくなります。

インデックスキー違反は次の場合に発生します。
+ 属性値とインデックスキーのスキーマのデータ型が一致しない場合。たとえば、`GameScores` テーブルの項目の 1 つが、`String` 型の `TopScore` 値を持っていたとします。`Number` 型の `TopScore` のパーティションキーを持つグローバルセカンダリインデックスを追加した場合、テーブルの項目はインデックスキーに違反します。
+ テーブルの属性値が、インデックスキー属性の最大長を超えている場合。パーティションキーの最大長は 2048 バイトで、ソートキーの最大長は 1024 バイトです。テーブル内の対応する属性値のいずれかがこれらの制限を超えると、テーブルの項目はインデックスキーに違反します。

**注記**  
インデックスキーとして使用される属性に String 属性または Binary 属性値が設定されている場合、属性値の長さは 0 より大きくする必要があります。そうしないと、テーブルの項目がインデックスキーに違反します。  
現時点では、このツールはこのインデックスキー違反にフラグを設定しません。

インデックスキー違反が発生した場合でも、バックフィルフェーズは中断することなく続行されます。ただし、違反している項目は、インデックスに含まれません。バックフィルフェーズ完了後は、新しいインデックスのキースキーマに違反する項目へのすべての書き込みが拒否されます。

インデックスキーに違反するテーブル内の属性値を識別して修正するには、Violation Detector ツールを使用します。Violation Detector を実行するには、スキャンするテーブルの名前、グローバルセカンダリインデックスパーティションキーとソートキーの名前とデータ型、インデックスキー違反が見つかった場合に実行するアクションを指定する設定ファイルを作成します。Violation Detector は、次の 2 つのモードのいずれかで実行できます。
+ **検出モード** – インデックスキー違反を検出します。検出モードを使用して、グローバルセカンダリインデックスでキー違反の原因となるテーブル内の項目を報告します。(必要に応じて、違反しているテーブル項目が見つかるとすぐに削除するように要求できます)。検出モードからの出力はファイルに書き込まれるので、これを使用してさらに分析できます。
+ **修正モード** – インデックスキー違反を修正します。修正モードでは、Violation Detector は検出モードからの出力ファイルと同じ形式の入力ファイルを読み込みます。修正モードでは、入力ファイルからレコードを読み込み、各レコードについて、テーブル内の対応する項目を削除または更新します。(項目の更新を選択した場合は、入力ファイルを編集して、これらの更新に適切な値を設定する必要があります)。

## Violation Detector のダウンロードおよび実行
<a name="GSI.OnlineOps.ViolationDetection.Running"></a>

Violation Detector は、実行可能な Java Archive (`.jar` ファイル) で、Windows、macOS、または Linux コンピュータ上で実行されます。Violation Detector には Java 1.7 (以降) と Apache Maven が必要です。
+ [GitHub から Violation Detector をダウンロードします](https://github.com/awslabs/dynamodb-online-index-violation-detector)

`README.md` ファイルの指示に従って、Maven を使用して Violation Detector をダウンロードしてインストールします。

Violation Detector を起動するには、`ViolationDetector.java` を構築したディレクトリで、次のコマンドを入力します。

```
java -jar ViolationDetector.jar [options]
```

Violation Detector ツールのコマンドラインでは、以下のオプションが使用できます。
+ `-h | --help` – Violation Detector の使用方法の概要とオプションを出力します。
+ `-p | --configFilePath` `value` – Violation Detector 設定ファイルの完全修飾名。詳細については、「」を参照してください[Violation Detector 設定ファイル](#GSI.OnlineOps.ViolationDetection.ConfigFile)
+ `-t | --detect` `value` – テーブル内のインデックスキー違反を検出し、Violation Detector の出力ファイルに書き込みます。このパラメータの値が `keep` に設定されている場合、キー違反のある項目は変更されません。値が `delete` に設定されている場合、キー違反のある項目はテーブルから削除されます。
+ `-c | --correct` `value` – 入力ファイルからインデックスキー違反を読み込み、テーブル内の項目に対して修正アクションを実行します。このパラメータの値が `update` に設定されている場合、キー違反のある項目は、違反していない新しい値で更新されます。値が `delete` に設定されている場合、キー違反のある項目はテーブルから削除されます。

## Violation Detector 設定ファイル
<a name="GSI.OnlineOps.ViolationDetection.ConfigFile"></a>

実行時に、Violation Detector ツールには設定ファイルが必要です。このファイルのパラメータによって、Violation Detector がアクセスできる DynamoDB リソースと、プロビジョニングされたスループットを使用できる量が決まります。以下の表は、これらのパラメータの説明です。


****  

| パラメータ名 | 説明 | 必須? | 
| --- | --- | --- | 
|  `awsCredentialsFile`  |  AWS 認証情報を含むファイルの完全修飾名。認証情報ファイルの形式は、次のようになります。 <pre>accessKey = access_key_id_goes_here<br />secretKey = secret_key_goes_here </pre>  |  はい  | 
|  `dynamoDBRegion`  |  テーブルが存在する AWS リージョン。例: `us-west-2`。  |  はい  | 
|  `tableName`  | スキャンされる DynamoDB テーブルの名前。 |  はい  | 
|  `gsiHashKeyName`  |  インデックスパーティションキーの名前。  |  はい  | 
|  `gsiHashKeyType`  |  インデックスパーティションキーのデータ型 – `String`、`Number`、または `Binary`: `S \| N \| B`  |  はい  | 
|  `gsiRangeKeyName`  |  インデックスソートキーの名前。インデックスにシンプルなプライマリキー (パーティションキー) のみがある場合は、このパラメータを指定しないでください。  |  いいえ  | 
|  `gsiRangeKeyType`  |  インデックスソートキーのデータ型 – `String`、`Number`、または `Binary`: `S \| N \| B`  インデックスにシンプルなプライマリキー (パーティションキー) のみがある場合は、このパラメータを指定しないでください。  |  いいえ  | 
|  `recordDetails`  |  インデックスキー違反の詳細を出力ファイルに書き込むかどうか。設定が `true` の場合 (デフォルト)、違反項目に関する完全な情報が報告されます。設定が `false` の場合、違反の数のみが報告されます。  |  いいえ  | 
|  `recordGsiValueInViolationRecord`  |  違反しているインデックスキーの値を出力ファイルに書き込むかどうか。設定が `true` の場合 (デフォルト)、キー値が報告されます。設定が `false` の場合、キー値は報告されません。  |  いいえ  | 
|  `detectionOutputPath`  |  Violation Detector の出力ファイルのフルパス。このパラメータは、ローカルディレクトリまたはAmazon Simple Storage Service (Amazon S3) への書き込みをサポートしています。次に例を示します。 `detectionOutputPath = ``//local/path/filename.csv` `detectionOutputPath = ``s3://bucket/filename.csv` 出力ファイル内の情報は、CSV (Comma-Separated Values) 形式で表示されます。`detectionOutputPath` を設定しない場合、出力ファイル名は `violation_detection.csv` になり、現在の作業ディレクトリに書き込まれます。  |  いいえ  | 
|  `numOfSegments`  | Violation Detector がテーブルをスキャンするときに使用される並列スキャンセグメントの数。デフォルト値は 1 です。これは、テーブルがシーケンシャルにスキャンされることを意味します。値が 2 以上の場合、Violation Detector はテーブルをその数の論理セグメントに分割し、同じ数のスキャンスレッドにします。`numOfSegments` の設定は最大 4,096 です。大きなテーブルの場合、並列スキャンはシーケンシャルスキャンよりも一般的に高速です。さらに、テーブルが複数のパーティションにまたがるほど大きい場合、並列スキャンによって読み込みアクティビティが複数のパーティションに均等に分散されます。DynamoDB の並列スキャンの詳細については、「[並列スキャン](Scan.md#Scan.ParallelScan)」を参照してください。 |  いいえ  | 
|  `numOfViolations`  |  出力ファイルに書き込むインデックスキー違反の上限値。設定が `-1` の場合 (デフォルト)、テーブル全体がスキャンされます。正の整数に設定すると、Violation Detector は設定した数の違反を検出した後に停止します。  |  いいえ  | 
|  `numOfRecords`  |  スキャンされるテーブル内の項目数。設定が -1 の場合(デフォルト)、テーブル全体がスキャンされます。正の整数に設定すると、Violation Detector はテーブル内の設定した数の項目をスキャンした後に停止します。  |  いいえ  | 
|  `readWriteIOPSPercent`  |  テーブルスキャン中に使用される、プロビジョニングされた読み込みキャパシティーユニットの割合を調整します。有効な値の範囲は `1`～`100` です。デフォルト値 (`25`) は、Violation Detector がテーブルのプロビジョニングされた読み込みスループットの 25% 以下を使用することを意味します。  |  いいえ  | 
|  `correctionInputPath`  |  Violation Detector 修正入力ファイルのフルパス。Violation Detector を修正モードで実行すると、このファイルの内容を使用して、グローバルセカンダリインデックスに違反するテーブル内のデータ項目を変更または削除します。 `correctionInputPath` ファイルの形式は、`detectionOutputPath` ファイルの形式と同じです。これにより、検出モードからの出力を修正モードの入力として処理できます。  |  いいえ  | 
|  `correctionOutputPath`  |  Violation Detector 修正出力ファイルのフルパス。このファイルは、更新エラーがある場合にのみ作成されます。 このパラメータは、ローカルディレクトリまたは Amazon S3 への書き込みをサポートします。次に例を示します。 `correctionOutputPath = ``//local/path/filename.csv` `correctionOutputPath = ``s3://bucket/filename.csv` 出力ファイルの情報は CSV 形式で表示されます。`correctionOutputPath` を設定しない場合、出力ファイル名は `violation_update_errors.csv` になり、現在の作業ディレクトリに書き込まれます。  |  いいえ  | 

## 検出
<a name="GSI.OnlineOps.ViolationDetection.Detection"></a>

インデックスキー違反を検出するには、Violation Detector を `--detect` コマンドラインオプションで使用します。このオプションがどのように機能するかを示すには、`ProductCatalog` テーブルを検討してください。以下は、テーブルの項目の一覧です。プライマリキー (`Id`) と `Price` 属性のみを表示しています。


****  

| ID (プライマリキー) | 価格 | 
| --- | --- | 
| 101 |  5  | 
| 102 |  20  | 
| 103 | 200  | 
| 201 |  100  | 
| 202 |  200  | 
| 203 |  300  | 
| 204 |  400  | 
| 205 |  500  | 

`Price` の値はすべて、`Number` 型です。ただし、DynamoDB はスキーマレスであるため、数値以外の `Price` の項目を追加することができます。たとえば、`ProductCatalog` テーブルに別の項目を追加するとします。


****  

| ID (プライマリキー) | 価格 | 
| --- | --- | 
| 999 | "Hello" | 

これで、テーブルには合計 9 つの項目が追加されました。

ここで、新しいグローバルセカンダリインデックスをテーブルに追加します: `PriceIndex`。このインデックスのプライマリキーは、パーティションキー `Price` で、`Number` 型です。インデックスが構築されると、8 つの項目が含まれますが、`ProductCatalog` テーブルには 9 つの項目があります。この不一致の理由は、値 `"Hello"` は `String` 型ですが、`PriceIndex` は `Number` 型のプライマリキーを持っているからです。`String` 値がグローバルセカンダリインデックスキーに違反するため、インデックスには存在しません。

この状況で Violation Detector を使用するには、まず次のような設定ファイルを作成します。

```
# Properties file for violation detection tool configuration.
# Parameters that are not specified will use default values.

awsCredentialsFile = /home/alice/credentials.txt
dynamoDBRegion = us-west-2
tableName = ProductCatalog
gsiHashKeyName = Price
gsiHashKeyType = N
recordDetails = true
recordGsiValueInViolationRecord = true
detectionOutputPath = ./gsi_violation_check.csv
correctionInputPath = ./gsi_violation_check.csv
numOfSegments = 1
readWriteIOPSPercent = 40
```

続いて、次の例のように、Violation Detector を実行します。

```
$  java -jar ViolationDetector.jar --configFilePath config.txt --detect keep

Violation detection started: sequential scan, Table name: ProductCatalog, GSI name: PriceIndex
Progress: Items scanned in total: 9,    Items scanned by this thread: 9,    Violations found by this thread: 1, Violations deleted by this thread: 0
Violation detection finished: Records scanned: 9, Violations found: 1, Violations deleted: 0, see results at: ./gsi_violation_check.csv
```

`recordDetails` 設定パラメータが `true` に設定されている場合、Violation Detector は、次の例のように、各違反の詳細を出力ファイルに書き込みます。

```
Table Hash Key,GSI Hash Key Value,GSI Hash Key Violation Type,GSI Hash Key Violation Description,GSI Hash Key Update Value(FOR USER),Delete Blank Attributes When Updating?(Y/N) 

999,"{""S"":""Hello""}",Type Violation,Expected: N Found: S,,
```

出力ファイルは CSV 形式です。ファイルの最初の行はヘッダーで、2 行目以降はインデックスキーに違反する項目ごとに 1 つのレコードが続きます。これらの違反レコードのフィールドは次のとおりです。
+ **テーブルハッシュキー** – テーブル内の項目のパーティションキー値です。
+ **テーブル範囲キー** – テーブル内の項目のソートキー値です。
+ **GSI ハッシュキー値** – グローバルセカンダリインデックスのパーティションキー値です。
+ **GSI ハッシュキー違反タイプ** – `Type Violation` または `Size Violation`。
+ **GSI ハッシュキー違反の説明** – 違反の原因です。
+ **GSI ハッシュキー更新値 (ユーザー用)** – 修正モードでは、ユーザーが指定した属性の新しい値。
+ **GSI 範囲キー値** – グローバルセカンダリインデックスのソートキー値です。
+ **GSI 範囲キー違反タイプ** – `Type Violation` または `Size Violation`。
+ **GSI 範囲キー違反の説明** – 違反の原因です。
+ **GSI 範囲キーの更新値 (ユーザー用)** – 修正モードでは、ユーザーが指定した属性の新しい値。
+ **更新時に空白の属性を削除 (Y/N)** – 修正モードでは、テーブル内の違反項目を削除する (Y) か、保持する (N) かを決定します。ただし、次のフィールドのいずれかが空白の場合のみ実行されます。
  + `GSI Hash Key Update Value(FOR USER)`
  + `GSI Range Key Update Value(FOR USER)`

  これらのフィールドのいずれかが空白でない場合、`Delete Blank Attribute When Updating(Y/N)` は何も実行しません。

**注記**  
出力形式は、設定ファイルとコマンドラインオプションによって異なることがあります。例えば、テーブルに単純なプライマリキー (ソートキーなし) がある場合、出力にはソートキーフィールドは表示されません。  
ファイル内の違反レコードがソート順でない可能性があります。

## 修正
<a name="GSI.OnlineOps.ViolationDetection.Correction"></a>

インデックスキーの違反を修正するには、Violation Detector を `--correct` コマンドラインオプションで使用します。修正モードでは、Violation Detector は `correctionInputPath` パラメータで指定された入力ファイルを読み込みます。このファイルの形式は `detectionOutputPath` ファイルの形式と同じなので、検出からの出力を修正のための入力として使用できます。

Violation Detector には、インデックスキー違反を修正する 2 種類の方法があります。
+ **違反の削除** – 属性値に違反しているテーブル項目を削除します。
+ **違反の更新** – テーブル項目を更新し、違反している属性を違反しない値に置き換えます。

いずれの場合も、検出モードからの出力ファイルを修正モードの入力として使用できます。

`ProductCatalog` の例に進みます。違反している項目をテーブルから削除するとします。これを行うには、次のコマンドラインを使用します。

```
$  java -jar ViolationDetector.jar --configFilePath config.txt --correct delete
```

この時点で、違反している項目を削除するかどうかを確認するメッセージが表示されます。

```
Are you sure to delete all violations on the table?y/n
y
Confirmed, will delete violations on the table...
Violation correction from file started: Reading records from file: ./gsi_violation_check.csv, will delete these records from table.
Violation correction from file finished: Violations delete: 1, Violations Update: 0
```

`ProductCatalog` と `PriceIndex` の項目の数が同じになりました。

# グローバルセカンダリインデックスの操作: Java
<a name="GSIJavaDocumentAPI"></a>

AWS SDK for Java ドキュメント API を使用して、1 つ以上のグローバルセカンダリインデックスを持つ Amazon DynamoDB テーブルを作成し、テーブルのインデックスを記述し、インデックスを使用してクエリを実行できます。

以下に、テーブルオペレーションの一般的な手順を示します。

1. `DynamoDB` クラスのインスタンスを作成します。

1. 対応するリクエストオブジェクトを作成して、オペレーションについて必要なパラメータとオプションパラメータを入力します。

1. 前のステップで作成したクライアントが提供する適切なメソッドを呼び出します。

**Topics**
+ [グローバルセカンダリインデックスを持つテーブルを作成します](#GSIJavaDocumentAPI.CreateTableWithIndex)
+ [グローバルセカンダリインデックスを持つテーブルの説明](#GSIJavaDocumentAPI.DescribeTableWithIndex)
+ [グローバルセカンダリインデックスのクエリ](#GSIJavaDocumentAPI.QueryAnIndex)
+ [例: AWS SDK for Java ドキュメント API を使用したグローバルセカンダリインデックス](GSIJavaDocumentAPI.Example.md)

## グローバルセカンダリインデックスを持つテーブルを作成します
<a name="GSIJavaDocumentAPI.CreateTableWithIndex"></a>

グローバルセカンダリインデックスは、テーブルの作成と同時に作成できます。これを行うには、`CreateTable` を使用し、1 つ以上のグローバルセカンダリインデックスの仕様を指定します。次の Java コード例では、気象データに関する情報を保持するテーブルを作成しています。パーティションキーは `Location` で、ソートキーは `Date` です。グローバルセカンダリインデックス `PrecipIndex` は、さまざまな場所の降水データへの高速アクセスを可能にします。

DynamoDB ドキュメント API を使用して、グローバルセカンダリインデックスを持つテーブルを作成する手順を次に示します。

1. `DynamoDB` クラスのインスタンスを作成します。

1. リクエスト情報を指定する `CreateTableRequest` クラスのインスタンスを作成します。

   テーブル名、プライマリキー、およびプロビジョニングされたスループット値を指定する必要があります。グローバルセカンダリインデックスの場合は、インデックス名、プロビジョニングされたスループット設定、インデックスソートキーの属性定義、インデックスのキースキーマ、および属性射影を指定する必要があります。

1. リクエストオブジェクトをパラメータとして指定して、`createTable` メソッドを呼び出します。

以下の Java コード例は、前述のステップの例です。このコードは、グローバルセカンダリインデックス (`PrecipIndex`) を持つテーブル (`WeatherData`) を作成します。インデックスパーティションキーは `Date` で、ソートキーは `Precipitation` です。すべてのテーブル属性がインデックスに射影されます。ユーザーは、このインデックスに対するクエリを実行して、特定の日付の気象データを取得できます。必要に応じて、降水量によってデータを並べ替えることもできます。

`Precipitation` はテーブルのキー属性ではないため、必須ではありません。ただし、`Precipitation` のない `WeatherData` 項目は `PrecipIndex` に表示されません。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

// Attribute definitions
ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>();

attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Location")
    .withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Date")
    .withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Precipitation")
    .withAttributeType("N"));

// Table key schema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement()
    .withAttributeName("Location")
    .withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement()
    .withAttributeName("Date")
    .withKeyType(KeyType.RANGE));  //Sort key

// PrecipIndex
GlobalSecondaryIndex precipIndex = new GlobalSecondaryIndex()
    .withIndexName("PrecipIndex")
    .withProvisionedThroughput(new ProvisionedThroughput()
        .withReadCapacityUnits((long) 10)
        .withWriteCapacityUnits((long) 1))
        .withProjection(new Projection().withProjectionType(ProjectionType.ALL));

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();

indexKeySchema.add(new KeySchemaElement()
    .withAttributeName("Date")
    .withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement()
    .withAttributeName("Precipitation")
    .withKeyType(KeyType.RANGE));  //Sort key

precipIndex.setKeySchema(indexKeySchema);

CreateTableRequest createTableRequest = new CreateTableRequest()
    .withTableName("WeatherData")
    .withProvisionedThroughput(new ProvisionedThroughput()
        .withReadCapacityUnits((long) 5)
        .withWriteCapacityUnits((long) 1))
    .withAttributeDefinitions(attributeDefinitions)
    .withKeySchema(tableKeySchema)
    .withGlobalSecondaryIndexes(precipIndex);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

DynamoDB がテーブルを作成し、テーブルのステータスを `ACTIVE` に設定するまで待機する必要があります。その後、テーブルへのデータ項目の入力を開始できます。

## グローバルセカンダリインデックスを持つテーブルの説明
<a name="GSIJavaDocumentAPI.DescribeTableWithIndex"></a>

テーブルでグローバルセカンダリインデックスに関する情報を取得するには、`DescribeTable` を使用します。インデックスごとに、名前、キースキーマ、および射影された属性にアクセスできます。

テーブルのグローバルセカンダリインデックス情報にアクセスする手順を次に示します。

1. `DynamoDB` クラスのインスタンスを作成します。

1. 操作対象のインデックスを表すために、`Table` クラスのインスタンスを作成します。

1. `Table` オブジェクトの `describe` メソッドを呼び出します。

以下の Java コード例は、前述のステップの例です。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("WeatherData");
TableDescription tableDesc = table.describe();
    

Iterator<GlobalSecondaryIndexDescription> gsiIter = tableDesc.getGlobalSecondaryIndexes().iterator();
while (gsiIter.hasNext()) {
    GlobalSecondaryIndexDescription gsiDesc = gsiIter.next();
    System.out.println("Info for index "
         + gsiDesc.getIndexName() + ":");

    Iterator<KeySchemaElement> kseIter = gsiDesc.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = gsiDesc.getProjection();
    System.out.println("\tThe projection type is: "
        + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: "
            + projection.getNonKeyAttributes());
    }
}
```

## グローバルセカンダリインデックスのクエリ
<a name="GSIJavaDocumentAPI.QueryAnIndex"></a>

テーブルに `Query` を実行するのとほぼ同じ方法で、グローバルセカンダリインデックスに対する `Query` を使用することができます。インデックス名、インデックスパーティションキーとソートキー (存在する場合) のクエリ条件、および返す属性を指定する必要があります。この例では、インデックスは `PrecipIndex` で、パーティションキーが `Date` で、ソートキーが `Precipitation` です。このインデックスクエリは、降水量がゼロより大きい特定の日付のすべての気象データを返します。

AWS SDK for Java ドキュメント API を使用してグローバルセカンダリインデックスをクエリする手順を次に示します。

1. `DynamoDB` クラスのインスタンスを作成します。

1. 操作対象のインデックスを表すために、`Table` クラスのインスタンスを作成します。

1. クエリするインデックスの `Index` クラスのインスタンスを作成します。

1. `Index` オブジェクトの `query` メソッドを呼び出します。

属性名 `Date` は DynamoDB の予約語です。したがって、式の属性名を `KeyConditionExpression` のプレースホルダーとして使用する必要があります。

以下の Java コード例は、前述のステップの例です。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("WeatherData");
Index index = table.getIndex("PrecipIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("#d = :v_date and Precipitation = :v_precip")
    .withNameMap(new NameMap()
        .with("#d", "Date"))
    .withValueMap(new ValueMap()
        .withString(":v_date","2013-08-10")
        .withNumber(":v_precip",0));

ItemCollection<QueryOutcome> items = index.query(spec);
Iterator<Item> iter = items.iterator(); 
while (iter.hasNext()) {
    System.out.println(iter.next().toJSONPretty());
}
```

# 例: AWS SDK for Java ドキュメント API を使用したグローバルセカンダリインデックス
<a name="GSIJavaDocumentAPI.Example"></a>

次の Java コード例は、グローバルセカンダリインデックスの操作方法を示しています。この例では、`Issues` というテーブルを作成しています。これは、ソフトウェア開発のためのシンプルなバグ追跡システムで使用できる可能性があります。パーティションキーは `IssueId` で、ソートキーは `Title` です。このテーブルには、次の 3 つのグローバルセカンダリインデックスがあります。
+ `CreateDateIndex` – パーティションキーは `CreateDate` で、ソートキーは `IssueId` です。テーブルキーに加えて、属性 `Description` および `Status` はインデックスに射影されます。
+ `TitleIndex` – パーティションキーは `Title` で、ソートキーは `IssueId` です。テーブルキー以外の属性はインデックスに射影されません。
+ `DueDateIndex` – パーティションキーは `DueDate` で、ソートキーはありません。すべてのテーブル属性がインデックスに射影されます。

`Issues` テーブルが作成されると、プログラムはソフトウェアのバグレポートを表すデータをテーブルにロードします。次に、グローバルセカンダリインデックスを使用してデータのクエリを実行します。最後に、プログラムは `Issues` テーブルを削除します。

以下の例をテストするための詳細な手順については、「[Java コードの例](CodeSamples.Java.md)」を参照してください。

**Example**  

```
package com.amazonaws.codesamples.document;

import java.util.ArrayList;
import java.util.Iterator;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Index;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

public class DocumentAPIGlobalSecondaryIndexExample {

    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    public static String tableName = "Issues";

    public static void main(String[] args) throws Exception {

        createTable();
        loadData();

        queryIndex("CreateDateIndex");
        queryIndex("TitleIndex");
        queryIndex("DueDateIndex");

        deleteTable(tableName);

    }

    public static void createTable() {

        // Attribute definitions
        ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>();

        attributeDefinitions.add(new AttributeDefinition().withAttributeName("IssueId").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("Title").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("CreateDate").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("DueDate").withAttributeType("S"));

        // Key schema for table
        ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
        tableKeySchema.add(new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.HASH)); // Partition
                                                                                                           // key
        tableKeySchema.add(new KeySchemaElement().withAttributeName("Title").withKeyType(KeyType.RANGE)); // Sort
                                                                                                          // key

        // Initial provisioned throughput settings for the indexes
        ProvisionedThroughput ptIndex = new ProvisionedThroughput().withReadCapacityUnits(1L)
                .withWriteCapacityUnits(1L);

        // CreateDateIndex
        GlobalSecondaryIndex createDateIndex = new GlobalSecondaryIndex().withIndexName("CreateDateIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("CreateDate").withKeyType(KeyType.HASH), // Partition
                                                                                                                 // key
                        new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.RANGE)) // Sort
                                                                                                        // key
                .withProjection(
                        new Projection().withProjectionType("INCLUDE").withNonKeyAttributes("Description", "Status"));

        // TitleIndex
        GlobalSecondaryIndex titleIndex = new GlobalSecondaryIndex().withIndexName("TitleIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("Title").withKeyType(KeyType.HASH), // Partition
                                                                                                            // key
                        new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.RANGE)) // Sort
                                                                                                        // key
                .withProjection(new Projection().withProjectionType("KEYS_ONLY"));

        // DueDateIndex
        GlobalSecondaryIndex dueDateIndex = new GlobalSecondaryIndex().withIndexName("DueDateIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("DueDate").withKeyType(KeyType.HASH)) // Partition
                                                                                                              // key
                .withProjection(new Projection().withProjectionType("ALL"));

        CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
                .withProvisionedThroughput(
                        new ProvisionedThroughput().withReadCapacityUnits((long) 1).withWriteCapacityUnits((long) 1))
                .withAttributeDefinitions(attributeDefinitions).withKeySchema(tableKeySchema)
                .withGlobalSecondaryIndexes(createDateIndex, titleIndex, dueDateIndex);

        System.out.println("Creating table " + tableName + "...");
        dynamoDB.createTable(createTableRequest);

        // Wait for table to become active
        System.out.println("Waiting for " + tableName + " to become ACTIVE...");
        try {
            Table table = dynamoDB.getTable(tableName);
            table.waitForActive();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void queryIndex(String indexName) {

        Table table = dynamoDB.getTable(tableName);

        System.out.println("\n***********************************************************\n");
        System.out.print("Querying index " + indexName + "...");

        Index index = table.getIndex(indexName);

        ItemCollection<QueryOutcome> items = null;

        QuerySpec querySpec = new QuerySpec();

        if (indexName == "CreateDateIndex") {
            System.out.println("Issues filed on 2013-11-01");
            querySpec.withKeyConditionExpression("CreateDate = :v_date and begins_with(IssueId, :v_issue)")
                    .withValueMap(new ValueMap().withString(":v_date", "2013-11-01").withString(":v_issue", "A-"));
            items = index.query(querySpec);
        } else if (indexName == "TitleIndex") {
            System.out.println("Compilation errors");
            querySpec.withKeyConditionExpression("Title = :v_title and begins_with(IssueId, :v_issue)")
                    .withValueMap(
                            new ValueMap().withString(":v_title", "Compilation error").withString(":v_issue", "A-"));
            items = index.query(querySpec);
        } else if (indexName == "DueDateIndex") {
            System.out.println("Items that are due on 2013-11-30");
            querySpec.withKeyConditionExpression("DueDate = :v_date")
                    .withValueMap(new ValueMap().withString(":v_date", "2013-11-30"));
            items = index.query(querySpec);
        } else {
            System.out.println("\nNo valid index name provided");
            return;
        }

        Iterator<Item> iterator = items.iterator();

        System.out.println("Query: printing results...");

        while (iterator.hasNext()) {
            System.out.println(iterator.next().toJSONPretty());
        }

    }

    public static void deleteTable(String tableName) {

        System.out.println("Deleting table " + tableName + "...");

        Table table = dynamoDB.getTable(tableName);
        table.delete();

        // Wait for table to be deleted
        System.out.println("Waiting for " + tableName + " to be deleted...");
        try {
            table.waitForDelete();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void loadData() {

        System.out.println("Loading data into table " + tableName + "...");

        // IssueId, Title,
        // Description,
        // CreateDate, LastUpdateDate, DueDate,
        // Priority, Status

        putItem("A-101", "Compilation error", "Can't compile Project X - bad version number. What does this mean?",
                "2013-11-01", "2013-11-02", "2013-11-10", 1, "Assigned");

        putItem("A-102", "Can't read data file", "The main data file is missing, or the permissions are incorrect",
                "2013-11-01", "2013-11-04", "2013-11-30", 2, "In progress");

        putItem("A-103", "Test failure", "Functional test of Project X produces errors", "2013-11-01", "2013-11-02",
                "2013-11-10", 1, "In progress");

        putItem("A-104", "Compilation error", "Variable 'messageCount' was not initialized.", "2013-11-15",
                "2013-11-16", "2013-11-30", 3, "Assigned");

        putItem("A-105", "Network issue", "Can't ping IP address 127.0.0.1. Please fix this.", "2013-11-15",
                "2013-11-16", "2013-11-19", 5, "Assigned");

    }

    public static void putItem(

            String issueId, String title, String description, String createDate, String lastUpdateDate, String dueDate,
            Integer priority, String status) {

        Table table = dynamoDB.getTable(tableName);

        Item item = new Item().withPrimaryKey("IssueId", issueId).withString("Title", title)
                .withString("Description", description).withString("CreateDate", createDate)
                .withString("LastUpdateDate", lastUpdateDate).withString("DueDate", dueDate)
                .withNumber("Priority", priority).withString("Status", status);

        table.putItem(item);
    }

}
```

# グローバルセカンダリインデックスの操作: .NET
<a name="GSILowLevelDotNet"></a>

AWS SDK for .NET 低レベル API を使用して、1 つ以上のグローバルセカンダリインデックスを含む Amazon DynamoDB テーブルを作成し、テーブルのインデックスを記述し、インデックスを使用してクエリを実行できます。これらのオペレーションは、対応する DynamoDB オペレーションにマッピングされます。詳細については、「[Amazon DynamoDB API リファレンス](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)」を参照してください。

以下に、.NET 低レベル API を使用したテーブルオペレーションの一般的な手順を示します。

1. `AmazonDynamoDBClient` クラスのインスタンスを作成します。

1. 対応するリクエストオブジェクトを作成して、オペレーションについて必要なパラメータとオプションパラメータを入力します。

   たとえば、テーブルを作成するには `CreateTableRequest` オブジェクトを作成し、テーブルやインデックスにクエリを実行するには `QueryRequest` オブジェクトを作成します。

1. 前述のステップで作成したクライアントから提供された適切なメソッドを実行します。

**Topics**
+ [グローバルセカンダリインデックスを持つテーブルを作成します](#GSILowLevelDotNet.CreateTableWithIndex)
+ [グローバルセカンダリインデックスを持つテーブルの説明](#GSILowLevelDotNet.DescribeTableWithIndex)
+ [グローバルセカンダリインデックスのクエリ](#GSILowLevelDotNet.QueryAnIndex)
+ [例: AWS SDK for .NET 低レベル API を使用したグローバルセカンダリインデックス](GSILowLevelDotNet.Example.md)

## グローバルセカンダリインデックスを持つテーブルを作成します
<a name="GSILowLevelDotNet.CreateTableWithIndex"></a>

グローバルセカンダリインデックスは、テーブルの作成と同時に作成できます。これを行うには、`CreateTable` を使用し、1 つ以上のグローバルセカンダリインデックスの仕様を指定します。次の C\$1 コード例では、気象データに関する情報を保持するテーブルを作成しています。パーティションキーは `Location` で、ソートキーは `Date` です。グローバルセカンダリインデックス `PrecipIndex` は、さまざまな場所の降水データへの高速アクセスを可能にします。

.NET 低レベル API を使用して、グローバルセカンダリインデックスを含むテーブルを作成する手順を次に示します。

1. `AmazonDynamoDBClient` クラスのインスタンスを作成します。

1. リクエスト情報を指定する `CreateTableRequest` クラスのインスタンスを作成します。

   テーブル名、プライマリキー、およびプロビジョニングされたスループット値を指定する必要があります。グローバルセカンダリインデックスの場合は、インデックス名、プロビジョニングされたスループット設定、インデックスソートキーの属性定義、インデックスのキースキーマ、および属性射影を指定する必要があります。

1. リクエストオブジェクトをパラメータとして指定して、`CreateTable` メソッドを実行します。

以下の C\$1 サンプルコードは、前述のステップの例です。このコードは、グローバルセカンダリインデックス (`PrecipIndex`) を持つテーブル (`WeatherData`) を作成します。インデックスパーティションキーは `Date` で、ソートキーは `Precipitation` です。すべてのテーブル属性がインデックスに射影されます。ユーザーは、このインデックスに対するクエリを実行して、特定の日付の気象データを取得できます。必要に応じて、降水量によってデータを並べ替えることもできます。

`Precipitation` はテーブルのキー属性ではないため、必須ではありません。ただし、`Precipitation` のない `WeatherData` 項目は `PrecipIndex` に表示されません。

```
client = new AmazonDynamoDBClient();
string tableName = "WeatherData";

// Attribute definitions
var attributeDefinitions = new List<AttributeDefinition>()
{
    {new AttributeDefinition{
        AttributeName = "Location",
        AttributeType = "S"}},
    {new AttributeDefinition{
        AttributeName = "Date",
        AttributeType = "S"}},
    {new AttributeDefinition(){
        AttributeName = "Precipitation",
        AttributeType = "N"}
    }
};

// Table key schema
var tableKeySchema = new List<KeySchemaElement>()
{
    {new KeySchemaElement {
        AttributeName = "Location",
        KeyType = "HASH"}},  //Partition key
    {new KeySchemaElement {
        AttributeName = "Date",
        KeyType = "RANGE"}  //Sort key
    }
};

// PrecipIndex
var precipIndex = new GlobalSecondaryIndex
{
    IndexName = "PrecipIndex",
    ProvisionedThroughput = new ProvisionedThroughput
    {
        ReadCapacityUnits = (long)10,
        WriteCapacityUnits = (long)1
    },
    Projection = new Projection { ProjectionType = "ALL" }
};

var indexKeySchema = new List<KeySchemaElement> {
    {new KeySchemaElement { AttributeName = "Date", KeyType = "HASH"}},  //Partition key
    {new KeySchemaElement{AttributeName = "Precipitation",KeyType = "RANGE"}}  //Sort key
};

precipIndex.KeySchema = indexKeySchema;

CreateTableRequest createTableRequest = new CreateTableRequest
{
    TableName = tableName,
    ProvisionedThroughput = new ProvisionedThroughput
    {
        ReadCapacityUnits = (long)5,
        WriteCapacityUnits = (long)1
    },
    AttributeDefinitions = attributeDefinitions,
    KeySchema = tableKeySchema,
    GlobalSecondaryIndexes = { precipIndex }
};

CreateTableResponse response = client.CreateTable(createTableRequest);
Console.WriteLine(response.CreateTableResult.TableDescription.TableName);
Console.WriteLine(response.CreateTableResult.TableDescription.TableStatus);
```

DynamoDB がテーブルを作成し、テーブルのステータスを `ACTIVE` に設定するまで待機する必要があります。その後、テーブルへのデータ項目の入力を開始できます。

## グローバルセカンダリインデックスを持つテーブルの説明
<a name="GSILowLevelDotNet.DescribeTableWithIndex"></a>

テーブルでグローバルセカンダリインデックスに関する情報を取得するには、`DescribeTable` を使用します。インデックスごとに、名前、キースキーマ、および射影された属性にアクセスできます。

.NET 低レベル API を使用してテーブルのグローバルセカンダリインデックス情報にアクセスするには次の手順に従います。

1. `AmazonDynamoDBClient` クラスのインスタンスを作成します。

1. リクエストオブジェクトをパラメータとして指定して、`describeTable` メソッドを実行します。

   リクエスト情報を指定する `DescribeTableRequest` クラスのインスタンスを作成します。テーブル名を入力する必要があります。

以下の C\$1 サンプルコードは、前述のステップの例です。

**Example**  

```
client = new AmazonDynamoDBClient();
string tableName = "WeatherData";

DescribeTableResponse response = client.DescribeTable(new DescribeTableRequest { TableName = tableName});

List<GlobalSecondaryIndexDescription> globalSecondaryIndexes =
response.DescribeTableResult.Table.GlobalSecondaryIndexes;

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

foreach (GlobalSecondaryIndexDescription gsiDescription in globalSecondaryIndexes) {
     Console.WriteLine("Info for index " + gsiDescription.IndexName + ":");

     foreach (KeySchemaElement kse in gsiDescription.KeySchema) {
          Console.WriteLine("\t" + kse.AttributeName + ": key type is " + kse.KeyType);
     }

      Projection projection = gsiDescription.Projection;
      Console.WriteLine("\tThe projection type is: " + projection.ProjectionType);

      if (projection.ProjectionType.ToString().Equals("INCLUDE")) {
           Console.WriteLine("\t\tThe non-key projected attributes are: "
                + projection.NonKeyAttributes);
      }
}
```

## グローバルセカンダリインデックスのクエリ
<a name="GSILowLevelDotNet.QueryAnIndex"></a>

テーブルに `Query` を実行するのとほぼ同じ方法で、グローバルセカンダリインデックスに対する `Query` を使用することができます。インデックス名、インデックスパーティションキーとソートキー (存在する場合) のクエリ条件、および返す属性を指定する必要があります。この例では、インデックスは `PrecipIndex` で、パーティションキーが `Date` で、ソートキーが `Precipitation` です。このインデックスクエリは、降水量がゼロより大きい特定の日付のすべての気象データを返します。

.NET 低レベル API を使用して、グローバルセカンダリインデックスを含むテーブルにクエリを実行する手順を次に示します。

1. `AmazonDynamoDBClient` クラスのインスタンスを作成します。

1. リクエスト情報を指定する `QueryRequest` クラスのインスタンスを作成します。

1. リクエストオブジェクトをパラメータとして指定して、`query` メソッドを実行します。

属性名 `Date` は DynamoDB の予約語です。したがって、式の属性名を `KeyConditionExpression` のプレースホルダーとして使用する必要があります。

以下の C\$1 サンプルコードは、前述のステップの例です。

**Example**  

```
client = new AmazonDynamoDBClient();

QueryRequest queryRequest = new QueryRequest
{
    TableName = "WeatherData",
    IndexName = "PrecipIndex",
    KeyConditionExpression = "#dt = :v_date and Precipitation > :v_precip",
    ExpressionAttributeNames = new Dictionary<String, String> {
        {"#dt", "Date"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue> {
        {":v_date", new AttributeValue { S =  "2013-08-01" }},
        {":v_precip", new AttributeValue { N =  "0" }}
    },
    ScanIndexForward = true
};

var result = client.Query(queryRequest);

var items = result.Items;
foreach (var currentItem in items)
{
    foreach (string attr in currentItem.Keys)
    {
        Console.Write(attr + "---> ");
        if (attr == "Precipitation")
        {
            Console.WriteLine(currentItem[attr].N);
    }
    else
    {
        Console.WriteLine(currentItem[attr].S);
    }

         }
     Console.WriteLine();
}
```

# 例: AWS SDK for .NET 低レベル API を使用したグローバルセカンダリインデックス
<a name="GSILowLevelDotNet.Example"></a>

次の C\$1 コード例は、グローバルセカンダリインデックスの操作方法を示しています。この例では、`Issues` というテーブルを作成しています。これは、ソフトウェア開発のためのシンプルなバグ追跡システムで使用できる可能性があります。パーティションキーは `IssueId` で、ソートキーは `Title` です。このテーブルには、次の 3 つのグローバルセカンダリインデックスがあります。
+ `CreateDateIndex` – パーティションキーは `CreateDate` で、ソートキーは `IssueId` です。テーブルキーに加えて、属性 `Description` および `Status` はインデックスに射影されます。
+ `TitleIndex` – パーティションキーは `Title` で、ソートキーは `IssueId` です。テーブルキー以外の属性はインデックスに射影されません。
+ `DueDateIndex` – パーティションキーは `DueDate` で、ソートキーはありません。すべてのテーブル属性がインデックスに射影されます。

`Issues` テーブルが作成されると、プログラムはソフトウェアのバグレポートを表すデータをテーブルにロードします。次に、グローバルセカンダリインデックスを使用してデータのクエリを実行します。最後に、プログラムは `Issues` テーブルを削除します。

以下の例をテストするための詳細な手順については、「[.NET コード例](CodeSamples.DotNet.md)」を参照してください。

**Example**  

```
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelGlobalSecondaryIndexExample
    {
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        public static String tableName = "Issues";

        public static void Main(string[] args)
        {
            CreateTable();
            LoadData();

            QueryIndex("CreateDateIndex");
            QueryIndex("TitleIndex");
            QueryIndex("DueDateIndex");

            DeleteTable(tableName);

            Console.WriteLine("To continue, press enter");
            Console.Read();
        }

        private static void CreateTable()
        {
            // Attribute definitions
            var attributeDefinitions = new List<AttributeDefinition>()
        {
            {new AttributeDefinition {
                 AttributeName = "IssueId", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "Title", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "CreateDate", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "DueDate", AttributeType = "S"
             }}
        };

            // Key schema for table
            var tableKeySchema = new List<KeySchemaElement>() {
            {
                new KeySchemaElement {
                    AttributeName= "IssueId",
                    KeyType = "HASH" //Partition key
                }
            },
            {
                new KeySchemaElement {
                    AttributeName = "Title",
                    KeyType = "RANGE" //Sort key
                }
            }
        };

            // Initial provisioned throughput settings for the indexes
            var ptIndex = new ProvisionedThroughput
            {
                ReadCapacityUnits = 1L,
                WriteCapacityUnits = 1L
            };

            // CreateDateIndex
            var createDateIndex = new GlobalSecondaryIndex()
            {
                IndexName = "CreateDateIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "CreateDate", KeyType = "HASH" //Partition key
                },
                new KeySchemaElement {
                    AttributeName = "IssueId", KeyType = "RANGE" //Sort key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "INCLUDE",
                    NonKeyAttributes = {
                    "Description", "Status"
                }
                }
            };

            // TitleIndex
            var titleIndex = new GlobalSecondaryIndex()
            {
                IndexName = "TitleIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "Title", KeyType = "HASH" //Partition key
                },
                new KeySchemaElement {
                    AttributeName = "IssueId", KeyType = "RANGE" //Sort key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "KEYS_ONLY"
                }
            };

            // DueDateIndex
            var dueDateIndex = new GlobalSecondaryIndex()
            {
                IndexName = "DueDateIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "DueDate",
                    KeyType = "HASH" //Partition key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "ALL"
                }
            };



            var createTableRequest = new CreateTableRequest
            {
                TableName = tableName,
                ProvisionedThroughput = new ProvisionedThroughput
                {
                    ReadCapacityUnits = (long)1,
                    WriteCapacityUnits = (long)1
                },
                AttributeDefinitions = attributeDefinitions,
                KeySchema = tableKeySchema,
                GlobalSecondaryIndexes = {
                createDateIndex, titleIndex, dueDateIndex
            }
            };

            Console.WriteLine("Creating table " + tableName + "...");
            client.CreateTable(createTableRequest);

            WaitUntilTableReady(tableName);
        }

        private static void LoadData()
        {
            Console.WriteLine("Loading data into table " + tableName + "...");

            // IssueId, Title,
            // Description,
            // CreateDate, LastUpdateDate, DueDate,
            // Priority, Status

            putItem("A-101", "Compilation error",
                "Can't compile Project X - bad version number. What does this mean?",
                "2013-11-01", "2013-11-02", "2013-11-10",
                1, "Assigned");

            putItem("A-102", "Can't read data file",
                "The main data file is missing, or the permissions are incorrect",
                "2013-11-01", "2013-11-04", "2013-11-30",
                2, "In progress");

            putItem("A-103", "Test failure",
                "Functional test of Project X produces errors",
                "2013-11-01", "2013-11-02", "2013-11-10",
                1, "In progress");

            putItem("A-104", "Compilation error",
                "Variable 'messageCount' was not initialized.",
                "2013-11-15", "2013-11-16", "2013-11-30",
                3, "Assigned");

            putItem("A-105", "Network issue",
                "Can't ping IP address 127.0.0.1. Please fix this.",
                "2013-11-15", "2013-11-16", "2013-11-19",
                5, "Assigned");
        }

        private static void putItem(
            String issueId, String title,
            String description,
            String createDate, String lastUpdateDate, String dueDate,
            Int32 priority, String status)
        {
            Dictionary<String, AttributeValue> item = new Dictionary<string, AttributeValue>();

            item.Add("IssueId", new AttributeValue
            {
                S = issueId
            });
            item.Add("Title", new AttributeValue
            {
                S = title
            });
            item.Add("Description", new AttributeValue
            {
                S = description
            });
            item.Add("CreateDate", new AttributeValue
            {
                S = createDate
            });
            item.Add("LastUpdateDate", new AttributeValue
            {
                S = lastUpdateDate
            });
            item.Add("DueDate", new AttributeValue
            {
                S = dueDate
            });
            item.Add("Priority", new AttributeValue
            {
                N = priority.ToString()
            });
            item.Add("Status", new AttributeValue
            {
                S = status
            });

            try
            {
                client.PutItem(new PutItemRequest
                {
                    TableName = tableName,
                    Item = item
                });
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void QueryIndex(string indexName)
        {
            Console.WriteLine
                ("\n***********************************************************\n");
            Console.WriteLine("Querying index " + indexName + "...");

            QueryRequest queryRequest = new QueryRequest
            {
                TableName = tableName,
                IndexName = indexName,
                ScanIndexForward = true
            };


            String keyConditionExpression;
            Dictionary<string, AttributeValue> expressionAttributeValues = new Dictionary<string, AttributeValue>();

            if (indexName == "CreateDateIndex")
            {
                Console.WriteLine("Issues filed on 2013-11-01\n");

                keyConditionExpression = "CreateDate = :v_date and begins_with(IssueId, :v_issue)";
                expressionAttributeValues.Add(":v_date", new AttributeValue
                {
                    S = "2013-11-01"
                });
                expressionAttributeValues.Add(":v_issue", new AttributeValue
                {
                    S = "A-"
                });
            }
            else if (indexName == "TitleIndex")
            {
                Console.WriteLine("Compilation errors\n");

                keyConditionExpression = "Title = :v_title and begins_with(IssueId, :v_issue)";
                expressionAttributeValues.Add(":v_title", new AttributeValue
                {
                    S = "Compilation error"
                });
                expressionAttributeValues.Add(":v_issue", new AttributeValue
                {
                    S = "A-"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else if (indexName == "DueDateIndex")
            {
                Console.WriteLine("Items that are due on 2013-11-30\n");

                keyConditionExpression = "DueDate = :v_date";
                expressionAttributeValues.Add(":v_date", new AttributeValue
                {
                    S = "2013-11-30"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else
            {
                Console.WriteLine("\nNo valid index name provided");
                return;
            }

            queryRequest.KeyConditionExpression = keyConditionExpression;
            queryRequest.ExpressionAttributeValues = expressionAttributeValues;

            var result = client.Query(queryRequest);
            var items = result.Items;
            foreach (var currentItem in items)
            {
                foreach (string attr in currentItem.Keys)
                {
                    if (attr == "Priority")
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].N);
                    }
                    else
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].S);
                    }
                }
                Console.WriteLine();
            }
        }

        private static void DeleteTable(string tableName)
        {
            Console.WriteLine("Deleting table " + tableName + "...");
            client.DeleteTable(new DeleteTableRequest
            {
                TableName = tableName
            });
            WaitForTableToBeDeleted(tableName);
        }

        private static void WaitUntilTableReady(string tableName)
        {
            string status = null;
            // Let us wait until table is created. Call DescribeTable.
            do
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                    status = res.Table.TableStatus;
                }
                catch (ResourceNotFoundException)
                {
                    // DescribeTable is eventually consistent. So you might
                    // get resource not found. So we handle the potential exception.
                }
            } while (status != "ACTIVE");
        }

        private static void WaitForTableToBeDeleted(string tableName)
        {
            bool tablePresent = true;

            while (tablePresent)
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                }
                catch (ResourceNotFoundException)
                {
                    tablePresent = false;
                }
            }
        }
    }
}
```

# AWS CLI を使用した DynamoDBでのグローバルセカンダリインデックスの操作
<a name="GCICli"></a>

AWS CLI を使用して、1 つ以上のグローバルセカンダリインデックスを含む Amazon DynamoDB テーブルを作成し、テーブルのインデックスを記述し、インデックスを使用してクエリを実行できます。

**Topics**
+ [グローバルセカンダリインデックスを持つテーブルを作成します](#GCICli.CreateTableWithIndex)
+ [グローバルセカンダリインデックスを既存のテーブルに追加します](#GCICli.CreateIndexAfterTable)
+ [グローバルセカンダリインデックスを持つテーブルの説明](#GCICli.DescribeTableWithIndex)
+ [グローバルセカンダリインデックスのクエリ](#GCICli.QueryAnIndex)

## グローバルセカンダリインデックスを持つテーブルを作成します
<a name="GCICli.CreateTableWithIndex"></a>

グローバルセカンダリインデックスは、テーブルの作成と同時に作成できます。　 これを行うには、`create-table` パラメータを使用し、1 つ以上のグローバルセカンダリインデックスの仕様を指定します。次の例では、`GameTitleIndex` と呼ばれるグローバルセカンダリインデックスを含む `GameScores` という名前のテーブルを作成します。ベーステーブルには、パーティションキー `UserId` とソートキー `GameTitle` があり、特定のゲームの個々のユーザーのベストスコアを効率的に見つけることができます。一方、GSI にはパーティションキー `GameTitle` とソートキー `TopScore` があり、特定のゲームの全体的な最高スコアをすばやく見つけることができます。

```
aws dynamodb create-table \
    --table-name GameScores \
    --attribute-definitions AttributeName=UserId,AttributeType=S \
                            AttributeName=GameTitle,AttributeType=S \
                            AttributeName=TopScore,AttributeType=N  \
    --key-schema AttributeName=UserId,KeyType=HASH \
                 AttributeName=GameTitle,KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --global-secondary-indexes \
        "[
            {
                \"IndexName\": \"GameTitleIndex\",
                \"KeySchema\": [{\"AttributeName\":\"GameTitle\",\"KeyType\":\"HASH\"},
                                {\"AttributeName\":\"TopScore\",\"KeyType\":\"RANGE\"}],
                \"Projection\":{
                    \"ProjectionType\":\"INCLUDE\",
                    \"NonKeyAttributes\":[\"UserId\"]
                },
                \"ProvisionedThroughput\": {
                    \"ReadCapacityUnits\": 10,
                    \"WriteCapacityUnits\": 5
                }
            }
        ]"
```

DynamoDB がテーブルを作成し、テーブルのステータスを `ACTIVE` に設定するまで待機する必要があります。その後、テーブルへのデータ項目の入力を開始できます。[describe-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html) を使用して、テーブル作成のステータスを判断できます。

## グローバルセカンダリインデックスを既存のテーブルに追加します
<a name="GCICli.CreateIndexAfterTable"></a>

グローバルセカンダリインデックスは、テーブルの作成後に追加したり変更したりすることもできます。これを行うには、`update-table` パラメータを使用し、1 つ以上のグローバルセカンダリインデックスの仕様を指定します。次の例では、前の例と同じスキーマを使用していますが、テーブルが既に作成されており、後で GSI を追加する場合を想定しています。

```
aws dynamodb update-table \
    --table-name GameScores \
    --attribute-definitions AttributeName=TopScore,AttributeType=N  \
    --global-secondary-index-updates \
        "[
            {
                \"Create\": {
                    \"IndexName\": \"GameTitleIndex\",
                    \"KeySchema\": [{\"AttributeName\":\"GameTitle\",\"KeyType\":\"HASH\"},
                                    {\"AttributeName\":\"TopScore\",\"KeyType\":\"RANGE\"}],
                    \"Projection\":{
                        \"ProjectionType\":\"INCLUDE\",
                        \"NonKeyAttributes\":[\"UserId\"]
                    }
                }
            }
        ]"
```

## グローバルセカンダリインデックスを持つテーブルの説明
<a name="GCICli.DescribeTableWithIndex"></a>

テーブルのグローバルセカンダリインデックスに関する情報を取得するには、`describe-table` パラメータを使用します。インデックスごとに、名前、キースキーマ、および射影された属性にアクセスできます。

```
aws dynamodb describe-table --table-name GameScores
```

## グローバルセカンダリインデックスのクエリ
<a name="GCICli.QueryAnIndex"></a>

テーブルに `query` を実行するのとほぼ同じ方法で、グローバルセカンダリインデックスに対する `query` オペレーションを使用することができます。インデックス名、インデックスソートキーのクエリ条件、および返す属性を指定する必要があります。この例では、インデックスは `GameTitleIndex`、インデックスソートキーは `GameTitle` です。

返される属性は、インデックスに射影された属性だけです。このクエリを変更して非キー属性を選択することもできますが、これには比較的コストのかかるテーブルフェッチアクティビティが必要です。テーブルのフェッチの詳細については、「[属性の射影](GSI.md#GSI.Projections)」を参照してください。

```
aws dynamodb query --table-name GameScores\
    --index-name GameTitleIndex \
    --key-condition-expression "GameTitle = :v_game" \
    --expression-attribute-values '{":v_game":{"S":"Alien Adventure"} }'
```