Dynamodb-global-secondary-indexes
DynamoDB-グローバルセカンダリインデックス
さまざまな属性を持つさまざまなクエリタイプを必要とするアプリケーションは、これらの詳細なクエリを実行する際に単一または複数のグローバルセカンダリインデックスを使用できます。
例-ユーザー、ログイン状態、ログイン時間を追跡するシステム。 前の例の成長により、そのデータに対するクエリが遅くなります。
グローバルセカンダリインデックスは、テーブルから選択した属性を整理することでクエリを高速化します。 データの並べ替えに主キーを使用し、キーテーブル属性やテーブルと同じキースキーマを必要としません。
すべてのグローバルセカンダリインデックスには、ソートキーのオプションを備えたパーティションキーが含まれている必要があります。 インデックスキースキーマはテーブルと異なる場合があり、インデックスキー属性はトップレベルの文字列、数値、またはバイナリテーブル属性を使用できます。
プロジェクションでは、他のテーブル属性を使用できますが、クエリは親テーブルから取得しません。
属性の投影
プロジェクションは、テーブルからセカンダリインデックスにコピーされた属性セットで構成されます。 プロジェクションは常にテーブルパーティションキーとソートキーで発生します。 クエリでは、プロジェクションにより、DynamoDBがプロジェクションの任意の属性にアクセスできます。基本的に、独自のテーブルとして存在します。
セカンダリインデックスの作成では、投影の属性を指定する必要があります。 DynamoDBは、このタスクを実行する3つの方法を提供します-
- KEYS_ONLY -すべてのインデックスアイテムは、テーブルパーティションとソートキー値、インデックスキー値で構成されます。 これにより、最小のインデックスが作成されます。
- INCLUDE -KEYS_ONLY属性と指定された非キー属性が含まれます。
- ALL -すべてのソーステーブル属性が含まれ、可能な限り最大のインデックスが作成されます。
属性をグローバルセカンダリインデックスに投影する際のトレードオフに注意してください。これは、スループットとストレージコストに関連しています。
次の点を考慮してください-
- いくつかの属性へのアクセスのみが必要で、待ち時間が短い場合は、必要な属性のみを投影してください。 これにより、ストレージと書き込みのコストが削減されます。
- アプリケーションが特定の非キー属性に頻繁にアクセスする場合は、スキャン消費と比較してストレージコストが少ないため、それらを投影します。
- 頻繁にアクセスされる属性の大きなセットを投影できますが、これには高いストレージコストがかかります。
- まれなテーブルクエリと頻繁な書き込み/更新にはKEYS_ONLYを使用します。 これはサイズを制御しますが、それでもクエリのパフォーマンスは良好です。
グローバルセカンダリインデックスのクエリとスキャン
クエリを利用して、インデックス内の単一または複数のアイテムにアクセスできます。 インデックスとテーブル名、目的の属性、および条件を指定する必要があります。結果を昇順または降順で返すオプションがあります。
スキャンを利用して、すべてのインデックスデータを取得することもできます。 テーブルとインデックス名が必要です。 フィルタ式を利用して、特定のデータを取得します。
テーブルとインデックスのデータ同期
DynamoDBは、親テーブルとのインデックスの同期を自動的に実行します。 アイテムの変更操作ごとに非同期更新が発生しますが、アプリケーションはインデックスに直接書き込みません。
DynamoDBのメンテナンスがインデックスに与える影響を理解する必要があります。 インデックスの作成時に、キー属性とデータ型を指定します。つまり、書き込み時には、これらのデータ型はキースキーマデータ型と一致する必要があります。
アイテムの作成または削除では、インデックスは最終的に一貫した方法で更新されますが、データの更新はほんの数秒で伝播します(何らかのタイプのシステム障害が発生しない限り)。 アプリケーションのこの遅延を考慮する必要があります。
グローバルセカンダリインデックスのスループットに関する考慮事項-複数のグローバルセカンダリインデックスはスループットに影響します。 インデックスの作成には、テーブルとは別に存在するキャパシティユニット仕様が必要です。その結果、テーブルユニットではなくインデックスキャパシティユニットを消費する操作が発生します。
これにより、クエリまたは書き込みがプロビジョニングされたスループットを超える場合にスロットルが発生する可能性があります。 DescribeTable を使用してスループット設定を表示します。
読み取り容量-グローバルセカンダリインデックスは、結果整合性を提供します。 クエリでは、DynamoDBはテーブルに使用されるものと同一のプロビジョニング計算を実行しますが、アイテムサイズではなくインデックスエントリサイズを使用するという唯一の違いがあります。 クエリリターンの制限は1MBのままです。これには、返されるすべてのアイテムの属性名のサイズと値が含まれます。
書き込み容量
書き込み操作が発生すると、影響を受けるインデックスは書き込み単位を消費します。 書き込みスループットコストは、テーブル書き込みで消費される書き込み容量ユニットとインデックス更新で消費されるユニットの合計です。 書き込み操作を成功させるには、十分な容量が必要です。そうしないと、スロットルが発生します。
書き込みコストも特定の要因に依存したままであり、そのいくつかは次のとおりです-
- インデックス付き属性を定義する新しいアイテムまたは未定義のインデックス付き属性を定義するアイテムの更新では、単一の書き込み操作を使用してアイテムをインデックスに追加します。
- インデックス付きキー属性値を変更する更新では、2つの書き込みを使用してアイテムを削除し、新しいアイテムを書き込みます。
- インデックス付き属性の削除をトリガーするテーブル書き込みは、単一の書き込みを使用して、インデックス内の古いアイテムの投影を消去します。
- 更新操作の前後にインデックスに存在しないアイテムは書き込みを使用しません。
- インデックスキースキーマの投影属性値のみを変更し、インデックスキー属性値を変更しない更新は、1回の書き込みを使用して、投影属性の値をインデックスに更新します。
これらすべての要因は、アイテムサイズが1KB以下であることを前提としています。
グローバルセカンダリインデックスストレージ
アイテムの書き込み時に、DynamoDBは属性が存在する必要があるインデックスに適切な属性セットを自動的にコピーします。 これは、テーブルアイテムのストレージと属性のストレージに課金することにより、アカウントに影響します。 使用されるスペースは、これらの量の合計から得られます-
- テーブルの主キーのバイトサイズ
- インデックスキー属性のバイトサイズ
- 投影された属性のバイトサイズ
- インデックスアイテムごとに100バイトのオーバーヘッド
平均アイテムサイズを推定し、グローバルセカンダリインデックスキー属性を使用してテーブルアイテムの数量を乗算することにより、ストレージのニーズを推定できます。
DynamoDBは、インデックスパーティションまたはソートキーとして定義された未定義の属性を持つテーブルアイテムのアイテムデータを書き込みません。
グローバルセカンダリインデックス
*CreateTable* 操作と *GlobalSecondaryIndexes* パラメーターを組み合わせて使用して、グローバルセカンダリインデックスを持つテーブルを作成します。 インデックスパーティションキーとして機能する属性を指定するか、インデックスソートキーに別の属性を使用する必要があります。 すべてのインデックスキー属性は、文字列、数値、またはバイナリスカラーでなければなりません。 また、 *ReadCapacityUnits* および *WriteCapacityUnits* で構成されるスループット設定を提供する必要があります。
*UpdateTable* を使用して、GlobalSecondaryIndexesパラメーターをもう一度使用して、既存のテーブルにグローバルセカンダリインデックスを追加します。
この操作では、次の入力を提供する必要があります-
- インデックス名
- キースキーマ
- 投影された属性 *スループット設定
グローバルセカンダリインデックスを追加すると、アイテムボリューム、予測される属性ボリューム、書き込み容量、および書き込みアクティビティのために、大きなテーブルではかなりの時間がかかる場合があります。* CloudWatch *メトリックを使用して、プロセスを監視します。
*DescribeTable* を使用して、グローバルセカンダリインデックスのステータス情報を取得します。 GlobalSecondaryIndexesの4つの *IndexStatus* の1つを返します-
- CREATING -インデックスの構築段階とその利用不能を示します。
- ACTIVE -使用するインデックスの準備ができていることを示します。
- UPDATING -スループット設定の更新ステータスを示します。
- DELETING -インデックスの削除ステータス、およびその永久的な使用不能を示します。
ロード/バックフィルステージ中にグローバルセカンダリインデックスプロビジョニングスループット設定を更新します(DynamoDBがインデックスに属性を書き込み、追加/削除/更新されたアイテムを追跡します)。 この操作を実行するには、 UpdateTable を使用します。
バックフィルの段階では、他のインデックスを追加/削除できないことに注意してください。
UpdateTableを使用して、グローバルセカンダリインデックスを削除します。 操作ごとに1つのインデックスのみを削除できますが、最大5つまでの複数の操作を同時に実行できます。 削除プロセスは親テーブルの読み取り/書き込みアクティビティには影響しませんが、操作が完了するまで他のインデックスを追加/削除することはできません。
Javaを使用してグローバルセカンダリインデックスを操作する
CreateTableを使用してインデックス付きのテーブルを作成します。 DynamoDBクラスインスタンス、リクエスト情報用の CreateTableRequest クラスインスタンスを作成し、リクエストオブジェクトをCreateTableメソッドに渡すだけです。
次のプログラムは短い例です-
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient (
new ProfileCredentialsProvider()));
//Attributes
ArrayList<AttributeDefinition> attributeDefinitions = new
ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("City")
.withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("Date")
.withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("Wind")
.withAttributeType("N"));
//Key schema of the table
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement()
.withAttributeName("City")
.withKeyType(KeyType.HASH)); //Partition key
tableKeySchema.add(new KeySchemaElement()
.withAttributeName("Date")
.withKeyType(KeyType.RANGE)); //Sort key
//Wind index
GlobalSecondaryIndex windIndex = new GlobalSecondaryIndex()
.withIndexName("WindIndex")
.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("Wind")
.withKeyType(KeyType.RANGE)); //Sort key
windIndex.setKeySchema(indexKeySchema);
CreateTableRequest createTableRequest = new CreateTableRequest()
.withTableName("ClimateInfo")
.withProvisionedThroughput(new ProvisionedThroughput()
.withReadCapacityUnits((long) 5)
.withWriteCapacityUnits((long) 1))
.withAttributeDefinitions(attributeDefinitions)
.withKeySchema(tableKeySchema)
.withGlobalSecondaryIndexes(windIndex);
Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
*DescribeTable* を使用してインデックス情報を取得します。 最初に、DynamoDBクラスのインスタンスを作成します。 次に、インデックスを対象とするTableクラスインスタンスを作成します。 最後に、テーブルをdescribeメソッドに渡します。
ここに短い例があります-
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient (
new ProfileCredentialsProvider()));
Table table = dynamoDB.getTable("ClimateInfo");
TableDescription tableDesc = table.describe();
Iterator<GlobalSecondaryIndexDescription> gsiIter =
tableDesc.getGlobalSecondaryIndexes().iterator();
while (gsiIter.hasNext()) {
GlobalSecondaryIndexDescription gsiDesc = gsiIter.next();
System.out.println("Index data " + gsiDesc.getIndexName() + ":");
Iterator<KeySchemaElement> kse7Iter = 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("\tProjection type: " + projection.getProjectionType());
if (projection.getProjectionType().toString().equals("INCLUDE")) {
System.out.println("\t\tNon-key projected attributes: "
+ projection.getNonKeyAttributes());
}
}
Queryを使用して、テーブルクエリと同様にインデックスクエリを実行します。 DynamoDBクラスインスタンス、ターゲットインデックスのTableクラスインスタンス、特定のインデックスのIndexクラスインスタンスを作成し、インデックスとクエリオブジェクトをクエリメソッドに渡すだけです。
よりよく理解するために、次のコードを見てください-
DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient (
new ProfileCredentialsProvider()));
Table table = dynamoDB.getTable("ClimateInfo");
Index index = table.getIndex("WindIndex");
QuerySpec spec = new QuerySpec()
.withKeyConditionExpression("#d = :v_date and Wind = :v_wind")
.withNameMap(new NameMap()
.with("#d", "Date"))
.withValueMap(new ValueMap()
.withString(":v_date","2016-05-15")
.withNumber(":v_wind",0));
ItemCollection<QueryOutcome> items = index.query(spec);
Iterator<Item> iter = items.iterator();
while (iter.hasNext()) {
System.out.println(iter.next().toJSONPretty());
}
次のプログラムは、より良い理解のための大きな例です-
注-次のプログラムは、以前に作成されたデータソースを想定している場合があります。 実行を試みる前に、サポートライブラリを取得し、必要なデータソース(必要な特性を備えたテーブル、または他の参照ソース)を作成します。
この例では、Eclipse IDE、AWS認証情報ファイル、およびEclipse AWS Javaプロジェクト内のAWS Toolkitも使用します。
import java.util.ArrayList;
import java.util.Iterator;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
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 GlobalSecondaryIndexSample {
static DynamoDB dynamoDB = new DynamoDB(new AmazonDynamoDBClient (
new ProfileCredentialsProvider()));
public static String tableName = "Bugs";
public static void main(String[] args) throws Exception {
createTable();
queryIndex("CreationDateIndex");
queryIndex("NameIndex");
queryIndex("DueDateIndex");
}
public static void createTable() {
//Attributes
ArrayList<AttributeDefinition> attributeDefinitions = new
ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("BugID")
.withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("Name")
.withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("CreationDate")
.withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
.withAttributeName("DueDate")
.withAttributeType("S"));
//Table Key schema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add (new KeySchemaElement()
.withAttributeName("BugID")
.withKeyType(KeyType.HASH)); //Partition key
tableKeySchema.add (new KeySchemaElement()
.withAttributeName("Name")
.withKeyType(KeyType.RANGE)); //Sort key
//Indexes' initial provisioned throughput
ProvisionedThroughput ptIndex = new ProvisionedThroughput()
.withReadCapacityUnits(1L)
.withWriteCapacityUnits(1L);
//CreationDateIndex
GlobalSecondaryIndex creationDateIndex = new GlobalSecondaryIndex()
.withIndexName("CreationDateIndex")
.withProvisionedThroughput(ptIndex)
.withKeySchema(new KeySchemaElement()
.withAttributeName("CreationDate")
.withKeyType(KeyType.HASH), //Partition key
new KeySchemaElement()
.withAttributeName("BugID")
.withKeyType(KeyType.RANGE)) //Sort key
.withProjection(new Projection()
.withProjectionType("INCLUDE")
.withNonKeyAttributes("Description", "Status"));
//NameIndex
GlobalSecondaryIndex nameIndex = new GlobalSecondaryIndex()
.withIndexName("NameIndex")
.withProvisionedThroughput(ptIndex)
.withKeySchema(new KeySchemaElement()
.withAttributeName("Name")
.withKeyType(KeyType.HASH), //Partition key
new KeySchemaElement()
.withAttributeName("BugID")
.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(creationDateIndex, nameIndex, dueDateIndex);
System.out.println("Creating " + tableName + "...");
dynamoDB.createTable(createTableRequest);
//Pause for active table state
System.out.println("Waiting for ACTIVE state of " + tableName);
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 == "CreationDateIndex") {
System.out.println("Issues filed on 2016-05-22");
querySpec.withKeyConditionExpression("CreationDate = :v_date and begins_with
(BugID, :v_bug)")
.withValueMap(new ValueMap()
.withString(":v_date","2016-05-22")
.withString(":v_bug","A-"));
items = index.query(querySpec);
} else if (indexName == "NameIndex") {
System.out.println("Compile error");
querySpec.withKeyConditionExpression("Name = :v_name and begins_with
(BugID, :v_bug)")
.withValueMap(new ValueMap()
.withString(":v_name","Compile error")
.withString(":v_bug","A-"));
items = index.query(querySpec);
} else if (indexName == "DueDateIndex") {
System.out.println("Items due on 2016-10-15");
querySpec.withKeyConditionExpression("DueDate = :v_date")
.withValueMap(new ValueMap()
.withString(":v_date","2016-10-15"));
items = index.query(querySpec);
} else {
System.out.println("\nInvalid index name");
return;
}
Iterator<Item> iterator = items.iterator();
System.out.println("Query: getting result...");
while (iterator.hasNext()) {
System.out.println(iterator.next().toJSONPretty());
}
}
}