DynamoDB - 全域性二級索引



需要使用不同屬性的不同查詢型別的應用程式可以使用單個或多個全域性二級索引來執行這些詳細查詢。

例如 - 一個系統跟蹤使用者、他們的登入狀態和他們的登入時間。前面示例的增長減慢了對其資料的查詢速度。

全域性二級索引透過組織表中選擇的屬性來加速查詢。它們使用主鍵對資料進行排序,並且不需要與表相同的鍵表屬性或鍵模式。

所有全域性二級索引都必須包含一個分割槽鍵,可以選擇一個排序鍵。索引鍵模式可以與表不同,索引鍵屬性可以使用任何頂級字串、數字或二進位制表屬性。

在投影中,您可以使用其他表屬性,但是,查詢不會從父表中檢索資料。

屬性投影

投影包含從表複製到二級索引的一組屬性。投影始終與表分割槽鍵和排序鍵一起出現。在查詢中,投影允許 DynamoDB 訪問投影的任何屬性;它們本質上作為自己的表存在。

在建立二級索引時,必須指定投影的屬性。DynamoDB 提供三種執行此任務的方法:

  • KEYS_ONLY - 所有索引項都包含表分割槽和排序鍵值以及索引鍵值。這將建立最小的索引。

  • INCLUDE - 它包含 KEYS_ONLY 屬性和指定的非鍵屬性。

  • ALL - 它包含所有源表屬性,建立最大的索引。

請注意將屬性投影到全域性二級索引中的權衡,這與吞吐量和儲存成本有關。

請考慮以下幾點:

  • 如果您只需要訪問少量屬性且延遲較低,則只需投影您需要的屬性。這降低了儲存和寫入成本。

  • 如果應用程式經常訪問某些非鍵屬性,請對其進行投影,因為儲存成本與掃描消耗相比微不足道。

  • 您可以投影經常訪問的大量屬性集,但是,這會產生高昂的儲存成本。

  • 對於不頻繁的表查詢和頻繁的寫入/更新,請使用 KEYS_ONLY。這可以控制大小,但仍可在查詢中提供良好的效能。

全域性二級索引查詢和掃描

您可以使用查詢訪問索引中的單個或多個專案。您必須指定索引和表名、所需屬性和條件;可以選擇按升序或降序返回結果。

您還可以使用掃描來獲取所有索引資料。它需要表名和索引名。您可以使用過濾器表示式來檢索特定資料。

表和索引資料同步

DynamoDB 自動執行索引與其父表之間的同步。對專案的每個修改操作都會導致非同步更新,但是,應用程式不會直接寫入索引。

您需要了解 DynamoDB 維護對索引的影響。在建立索引時,您會指定鍵屬性和資料型別,這意味著在寫入時,這些資料型別必須與鍵模式資料型別匹配。

在建立或刪除專案時,索引會以最終一致的方式更新,但是,對資料的更新會在幾分之一秒內傳播(除非發生某種型別的系統故障)。您必須在應用程式中考慮此延遲。

全域性二級索引中的吞吐量考慮 - 多個全域性二級索引會影響吞吐量。索引建立需要容量單元規範,這些規範獨立於表存在,導致操作消耗索引容量單元而不是表單元。

如果查詢或寫入超過預配的吞吐量,這可能會導致限制。使用DescribeTable檢視吞吐量設定。

讀取容量 - 全域性二級索引提供最終一致性。在查詢中,DynamoDB 執行的預配計算與用於表的計算相同,唯一的區別是使用索引條目大小而不是專案大小。查詢返回的限制仍然為 1MB,其中包括每個返回專案中的屬性名稱大小和值。

寫入容量

當寫入操作發生時,受影響的索引會消耗寫入單元。寫入吞吐量成本是表寫入中消耗的寫入容量單元和索引更新中消耗的單元的總和。成功的寫入操作需要足夠的容量,否則會導致限制。

寫入成本也取決於某些因素,其中一些如下:

  • 定義索引屬性的新專案或定義未定義索引屬性的專案更新使用單個寫入操作將專案新增到索引。

  • 更改索引鍵屬性值的更新使用兩次寫入來刪除一個專案並寫入一個新專案。

  • 在更新操作之前和之後索引中不存在的表寫入使用單個寫入來擦除索引中的舊專案投影。

  • 在更新操作之前和之後索引中都不存在的專案不使用寫入。

  • 僅更改索引鍵模式中投影屬性值且不更改索引鍵屬性值的更新使用一次寫入將投影屬性的值更新到索引中。

所有這些因素都假設專案大小小於或等於 1KB。

全域性二級索引儲存

在專案寫入時,DynamoDB 會自動將正確的屬性集複製到必須存在這些屬性的任何索引中。這會影響您的帳戶,因為它會向您收取表專案儲存和屬性儲存的費用。使用的空間來自這些數量的總和:

  • 表主鍵的位元組大小
  • 索引鍵屬性的位元組大小
  • 投影屬性的位元組大小
  • 每個索引項 100 位元組的開銷

您可以透過估算平均專案大小並將其乘以具有全域性二級索引鍵屬性的表專案的數量來估算儲存需求。

對於定義為索引分割槽或排序鍵的屬性未定義的表專案,DynamoDB 不會寫入專案資料。

全域性二級索引 CRUD

透過使用與GlobalSecondaryIndexes引數配對的CreateTable操作來建立具有全域性二級索引的表。您必須指定一個屬性作為索引分割槽鍵,或使用另一個屬性作為索引排序鍵。所有索引鍵屬性必須是字串、數字或二進位制標量。您還必須提供吞吐量設定,包括ReadCapacityUnitsWriteCapacityUnits

使用UpdateTable使用GlobalSecondaryIndexes引數再次向現有表新增全域性二級索引。

在此操作中,您必須提供以下輸入:

  • 索引名稱
  • 鍵模式
  • 投影屬性
  • 吞吐量設定

新增全域性二級索引可能會由於專案數量、投影屬性數量、寫入容量和寫入活動而導致大型表花費大量時間。使用CloudWatch指標監控此過程。

使用DescribeTable獲取全域性二級索引的狀態資訊。它為 GlobalSecondaryIndexes 返回四個IndexStatus之一:

  • CREATING - 它表示索引的構建階段及其不可用性。

  • ACTIVE - 它表示索引已準備好使用。

  • UPDATING - 它表示吞吐量設定的更新狀態。

  • DELETING - 它表示索引的刪除狀態及其永久不可用性。

在載入/回填階段(DynamoDB 將屬性寫入索引並跟蹤新增/刪除/更新的專案)期間更新全域性二級索引的預配吞吐量設定。使用UpdateTable執行此操作。

您應該記住,您不能在回填階段新增/刪除其他索引。

使用 UpdateTable 刪除全域性二級索引。它只允許每個操作刪除一個索引,但是,您可以同時執行多個操作,最多五個。刪除過程不會影響父表的讀/寫活動,但是您必須等到操作完成才能新增/刪除其他索引。

使用 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 類例項,並將索引和查詢物件傳遞給 query 方法。

請檢視以下程式碼以更好地理解:

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 工具包。

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()); 
      } 
   } 
}
廣告
© . All rights reserved.