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