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

異なる属性を持つさまざまなクエリタイプを必要とするアプリケーションは、これらの詳細なクエリを実行する際に、単一または複数のグローバルセカンダリインデックスを使用できます。

For example −ユーザー、ユーザーのログインステータス、およびログイン時間を追跡するシステム。前の例の増加により、データに対するクエリが遅くなります。

グローバルセカンダリインデックスは、テーブルからの属性の選択を整理することにより、クエリを高速化します。データの並べ替えに主キーを使用し、キーテーブル属性やテーブルと同一のキースキーマを必要としません。

すべてのグローバルセカンダリインデックスには、ソートキーのオプションを含むパーティションキーが含まれている必要があります。インデックスキースキーマはテーブルとは異なる場合があり、インデックスキー属性は任意の最上位の文字列、数値、またはバイナリテーブル属性を使用できます。

プロジェクションでは、他のテーブル属性を使用できますが、クエリは親テーブルから取得しません。

属性の予測

射影は、テーブルからセカンダリインデックスにコピーされた属性セットで構成されます。射影は常にテーブルパーティションキーとソートキーで発生します。クエリでは、プロジェクションにより、DynamoDBはプロジェクションの任意の属性にアクセスできます。それらは本質的に独自のテーブルとして存在します。

セカンダリインデックスの作成では、プロジェクションの属性を指定する必要があります。DynamoDBは、このタスクを実行する3つの方法を提供します-

  • KEYS_ONLY−すべてのインデックス項目は、テーブルパーティションとソートキーの値、およびインデックスキーの値で構成されます。これにより、最小のインデックスが作成されます。

  • INCLUDE −KEYS_ONLY属性と指定された非キー属性が含まれます。

  • ALL −すべてのソーステーブル属性が含まれ、可能な限り最大のインデックスが作成されます。

属性をグローバルセカンダリインデックスに投影する際のトレードオフに注意してください。これは、スループットとストレージコストに関連しています。

次の点を考慮してください-

  • 待ち時間が短く、いくつかの属性にのみアクセスする必要がある場合は、必要な属性のみを投影します。これにより、ストレージと書き込みのコストが削減されます。

  • アプリケーションが特定の非キー属性に頻繁にアクセスする場合は、スキャンの消費量と比較してストレージコストが低いため、それらを予測します。

  • 頻繁にアクセスされる属性の大規模なセットを投影できますが、これには高いストレージコストがかかります。

  • まれなテーブルクエリと頻繁な書き込み/更新にはKEYS_ONLYを使用します。これはサイズを制御しますが、それでもクエリで優れたパフォーマンスを提供します。

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

クエリを利用して、インデックス内の単一または複数のアイテムにアクセスできます。インデックスとテーブル名、必要な属性、および条件を指定する必要があります。結果を昇順または降順で返すオプションがあります。

スキャンを利用して、すべてのインデックスデータを取得することもできます。テーブル名とインデックス名が必要です。フィルタ式を使用して、特定のデータを取得します。

テーブルとインデックスのデータ同期

DynamoDBは、インデックスの親テーブルとの同期を自動的に実行します。アイテムに対する変更操作ごとに非同期更新が発生しますが、アプリケーションはインデックスに直接書き込みません。

DynamoDBのメンテナンスがインデックスに与える影響を理解する必要があります。インデックスの作成時に、キー属性とデータ型を指定します。つまり、書き込み時に、これらのデータ型はキースキーマデータ型と一致する必要があります。

アイテムの作成または削除時に、インデックスは結果整合性のある方法で更新されますが、データの更新はほんの一瞬で伝播します(何らかのタイプのシステム障害が発生しない限り)。アプリケーションのこの遅延を考慮する必要があります。

Throughput Considerations in Global Secondary Indexes−複数のグローバルセカンダリインデックスがスループットに影響を与えます。インデックスの作成には、テーブルとは別に存在する容量ユニットの仕様が必要であるため、操作ではテーブルユニットではなくインデックス容量ユニットが消費されます。

これにより、クエリまたは書き込みがプロビジョニングされたスループットを超えた場合にスロットルが発生する可能性があります。を使用してスループット設定を表示するDescribeTable

Read Capacity−グローバルセカンダリインデックスは結果整合性を提供します。クエリでは、DynamoDBはテーブルに使用されるものと同じプロビジョニング計算を実行しますが、アイテムサイズではなくインデックスエントリサイズを使用するという唯一の違いがあります。返されるクエリの制限は1MBのままです。これには、返されるすべてのアイテムの属性名のサイズと値が含まれます。

書き込み容量

書き込み操作が発生すると、影響を受けるインデックスは書き込みユニットを消費します。書き込みスループットコストは、テーブルの書き込みで消費される書き込み容量の単位と、インデックスの更新で消費される単位の合計です。書き込み操作を成功させるには、十分な容量が必要です。そうしないと、スロットルが発生します。

書き込みコストも特定の要因に依存し続けますが、その一部は次のとおりです。

  • インデックス付き属性を定義する新しいアイテムまたは未定義のインデックス付き属性を定義するアイテムの更新は、単一の書き込み操作を使用してアイテムをインデックスに追加します。

  • インデックス付きキー属性値を変更する更新では、2回の書き込みを使用してアイテムを削除し、新しいアイテムを書き込みます。

  • インデックス付き属性の削除をトリガーするテーブル書き込みは、1回の書き込みを使用して、インデックス内の古いアイテムの投影を消去します。

  • 更新操作の前後にインデックスに存在しないアイテムは、書き込みを使用しません。

  • インデックスキー属性値ではなく、インデックスキースキーマの投影属性値のみを変更する更新では、1回の書き込みを使用して、インデックスへの投影属性の値を更新します。

これらの要素はすべて、アイテムサイズが1KB以下であることを前提としています。

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

アイテムの書き込み時に、DynamoDBは、属性が存在する必要のあるすべてのインデックスに正しい属性のセットを自動的にコピーします。これは、テーブルアイテムストレージと属性ストレージの料金を請求することでアカウントに影響を与えます。使用されるスペースは、これらの量の合計から生じます-

  • テーブルの主キーのバイトサイズ
  • インデックスキー属性のバイトサイズ
  • 投影された属性のバイトサイズ
  • インデックスアイテムごとに100バイトのオーバーヘッド

平均アイテムサイズを見積もり、グローバルセカンダリインデックスキー属性を持つテーブルアイテムの数量を掛けることで、ストレージのニーズを見積もることができます。

DynamoDBは、インデックスパーティションまたはソートキーとして定義された未定義の属性を持つテーブルアイテムのアイテムデータを書き込みません。

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

を使用して、グローバルセカンダリインデックスを含むテーブルを作成します。 CreateTable とペアになっている操作 GlobalSecondaryIndexesパラメータ。インデックスパーティションキーとして機能する属性を指定するか、インデックスソートキーに別の属性を使用する必要があります。すべてのインデックスキー属性は、文字列、数値、またはバイナリスカラーである必要があります。また、以下で構成されるスループット設定を提供する必要があります。ReadCapacityUnits そして WriteCapacityUnits

使用する UpdateTable GlobalSecondaryIndexesパラメーターをもう一度使用して、既存のテーブルにグローバルセカンダリインデックスを追加します。

この操作では、次の入力を提供する必要があります-

  • インデックス名
  • キースキーマ
  • 投影された属性
  • スループット設定

グローバルセカンダリインデックスを追加すると、アイテムの量、予測される属性の量、書き込み容量、および書き込みアクティビティが原因で、大きなテーブルではかなりの時間がかかる場合があります。使用するCloudWatch プロセスを監視するためのメトリック。

使用する DescribeTableグローバルセカンダリインデックスのステータス情報を取得します。4つのうちの1つを返しますIndexStatus GlobalSecondaryIndexesの場合-

  • 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()); 
   } 
}

クエリを使用して、テーブルクエリと同様にインデックスクエリを実行します。DynamoDBクラスインスタンス、ターゲットインデックスのテーブルクラスインスタンス、特定のインデックスのインデックスクラスインスタンスを作成し、インデックスとクエリオブジェクトをクエリメソッドに渡すだけです。

よりよく理解するために次のコードを見てください-

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()); 
}

次のプログラムは、理解を深めるためのより大きな例です。

Note−次のプログラムは、以前に作成されたデータソースを想定している場合があります。実行を試みる前に、サポートライブラリを取得し、必要なデータソース(必要な特性を持つテーブル、またはその他の参照ソース)を作成します。

この例では、Eclipse IDE、AWS認証情報ファイル、およびEclipse AWSJavaプロジェクト内のAWSToolkitも使用しています。

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()); 
      } 
   } 
}