DynamoDB – 全局二级索引
DynamoDB – 全局二级索引
需要具有不同属性的各种查询类型的应用程序可以使用单个或多个全局二级索引来执行这些详细查询。
例如– 一个系统跟踪用户、他们的登录状态和他们的登录时间。上一个示例的增长减慢了对其数据的查询。
全局二级索引通过组织从表中选择的属性来加速查询。它们在对数据进行排序时使用主键,并且不需要与表相同的键表属性或键模式。
所有全局二级索引都必须包含一个分区键,并且可以选择排序键。索引键模式可以与表不同,索引键属性可以使用任何顶级字符串、数字或二进制表属性。
在投影中,您可以使用其他表属性,但是,查询不会从父表中检索。
属性投影
投影由从表复制到二级索引的属性集组成。投影总是与表分区键和排序键一起发生。在查询中,投影允许 DynamoDB 访问投影的任何属性;它们本质上是作为自己的表存在的。
在二级索引创建中,您必须为投影指定属性。DynamoDB 提供了三种执行此任务的方法 –
-
KEYS_ONLY – 所有索引项都由表分区和排序键值以及索引键值组成。这将创建最小的索引。
-
INCLUDE – 它包括 KEYS_ONLY 属性和指定的非关键属性。
-
ALL – 它包括所有源表属性,创建最大可能的索引。
请注意将属性投影到全局二级索引中的权衡,这与吞吐量和存储成本有关。
考虑以下几点 –
-
如果您只需要访问少数属性,并且延迟很低,则只投影您需要的那些。这降低了存储和写入成本。
-
如果应用程序频繁访问某些非关键属性,请对其进行投影,因为与扫描消耗相比,存储成本相形见绌。
-
您可以投影经常访问的大量属性,但是,这会带来很高的存储成本。
-
将 KEYS_ONLY 用于不频繁的表查询和频繁的写入/更新。这控制了大小,但仍然提供了良好的查询性能。
全局二级索引查询和扫描
您可以使用查询来访问索引中的单个或多个项目。您必须指定索引和表名、所需的属性和条件;可以选择按升序或降序返回结果。
您还可以利用扫描来获取所有索引数据。它需要表和索引名称。您可以使用过滤器表达式来检索特定数据。
表和索引数据同步
DynamoDB 自动对索引与其父表执行同步。对项目的每次修改操作都会导致异步更新,但是,应用程序不会直接写入索引。
您需要了解 DynamoDB 维护对索引的影响。在创建索引时,您指定键属性和数据类型,这意味着在写入时,这些数据类型必须匹配键模式数据类型。
在项目创建或删除时,索引以最终一致的方式更新,但是,数据更新会在几分之一秒内传播(除非发生某种类型的系统故障)。您必须考虑申请中的这种延迟。
全局二级索引中的吞吐量注意事项– 多个全局二级索引影响吞吐量。索引创建需要容量单位规范,该规范与表分开存在,导致操作消耗索引容量单位而不是表单位。
如果查询或写入超过预配置的吞吐量,这可能会导致限制。使用DescribeTable查看吞吐量设置。
读取容量– 全局二级索引提供最终的一致性。在查询中,DynamoDB 执行的供应计算与用于表的计算相同,唯一的区别是使用索引条目大小而不是项目大小。查询返回的限制仍然是 1MB,其中包括每个返回项目的属性名称大小和值。
写入容量
当写操作发生时,受影响的索引消耗写单元。写入吞吐量成本是表写入消耗的写入容量单位和索引更新消耗的单位的总和。成功的写入操作需要足够的容量,否则会导致限制。
写入成本还取决于某些因素,其中一些如下 –
-
定义索引属性的新项目或定义未定义索引属性的项目更新使用单个写入操作将项目添加到索引。
-
更新更改索引键属性值使用两次写入来删除一个项目并写入一个新项目。
-
表写入触发删除索引属性使用单个写入来擦除索引中的旧项目投影。
-
更新操作前后索引中不存在的项目不使用写入。
-
更新仅更改索引键架构中的投影属性值,而不更改索引键属性值,使用一次写入将投影属性的值更新到索引中。
所有这些因素都假定项目大小小于或等于 1KB。
全局二级索引存储
在项目写入时,DynamoDB 会自动将正确的属性集复制到属性必须存在的任何索引。这会通过向您的帐户收取表格项目存储和属性存储费用来影响您的帐户。使用的空间来自这些数量的总和 –
- 表主键的字节大小
- 索引键属性的字节大小
- 投影属性的字节大小
- 每个索引项 100 字节的开销
您可以通过估计平均项目大小并乘以具有全局二级索引键属性的表项目的数量来估计存储需求。
DynamoDB 不会为具有定义为索引分区或排序键的未定义属性的表项目写入项目数据。
全球二级指数原油
使用与GlobalSecondaryIndexes参数配对的CreateTable操作创建具有全局二级索引的表。您必须指定一个属性作为索引分区键,或者使用另一个属性作为索引排序键。所有索引键属性必须是字符串、数字或二进制标量。您还必须提供吞吐量设置,包括ReadCapacityUnits和WriteCapacityUnits。
使用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 类实例,并将索引和查询对象传递给查询方法。
查看以下代码以更好地理解 –
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()); } } }