本地二级索引 - Amazon DynamoDB
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

本地二级索引

某些应用程序只需使用基表的主键查询数据。但是,在某些情况下,备用排序键可能会很有用。要为您的应用程序提供排序键选择,您可以为 Amazon DynamoDB 表创建一个或多个本地二级索引,并针对这些索引发出 QueryScan 请求。

方案:使用本地二级索引

例如,请考虑在 Thread 中定义的 为 DynamoDB 中的代码示例创建表和加载数据 表。此表对应用程序(如 AWS 开发论坛)很有用。下表显示了此表中项目的组织方式,(并未显示所有属性。)


                包含论坛名称、主题、上次发布时间和回复数列表的线程表。

DynamoDB 连续存储具有相同分区键值的所有项目。在此示例中,给定特定的 ForumNameQuery 操作可以立即查找该论坛的所有话题。在具有相同分区键值的一组项目中,这些项目按排序键值排序。如果查询中还提供了排序键 (Subject),则 DynamoDB 可以缩小返回的结果—例如,返回“S3”论坛中所有以字母“a”开头的 Subject 线程。

某些请求可能需要更复杂的数据访问模式。例如:

  • 哪些论坛话题能获得最多的视角和回复?

  • 特定论坛中的哪些话题的消息数量最多?

  • 在特定的论坛中,在特定的时间段内发布了多少个话题?

要回答这些问题,Query 操作是不够的。相反,您必须对整个表执行 Scan 操作。对于包含数百万个项目的表,这会占用大量预置读取吞吐量,并需要很长时间才能完成。

但是,您可以在非键属性(如 RepliesLastPostDateTime)上指定一个或多个本地二级索引。

为给定分区键值维护一个替代排序键。local secondary index还包含其基表中部分或全部属性的副本。local secondary index您可以在创建表时指定投影到 local secondary index 中的属性。中的数据按与基表相同的分区键排列,但使用不同的排序键。local secondary index这可让您跨此不同维度高效地访问数据项目。为获得更大的查询或扫描灵活性,您可以为每个表创建最多 5 个 local secondary index。

假设应用程序需要查找过去三个月内在某个论坛中发布的所有话题。如果没有 local secondary index,应用程序则必须对整个 Scan 表执行 Thread 操作,并放弃在指定时间范围内的未发布。利用 local secondary index,Query 操作可以使用 LastPostDateTime 作为排序键并快速查找数据。

下图显示了一个名为 local secondary index 的 LastPostIndex。 请注意,分区键与 Thread 表的分区键相同,但排序键为 LastPostDateTime


                LastPostIndex 包含论坛名称、主题和上次发布时间的列表的表。

每个 local secondary index 必须满足以下条件:

  • 分区键与其基表的分区键相同。

  • 排序键只包含一个标量属性。

  • 基表的排序键将投影到索引中,并在其中充当非键属性。

在此示例中,分区键为 ForumName,local secondary index 的排序键为 LastPostDateTime。 此外,来自基表的排序键值(在本示例中为 Subject)将投影到索引中,但它不是索引键的一部分。如果应用程序需要基于 ForumNameLastPostDateTime 的列表,它可以针对 Query 发出 LastPostIndex 请求。 查询结果按 LastPostDateTime 排序,可以按升序或降序返回。查询还可以应用键条件,例如仅返回在特定时间范围内具有 LastPostDateTime 的项目。

每个 local secondary index 自动包含其基表中的分区键和排序键;您可以选择将非键属性投影到索引中。当您查询索引时,DynamoDB 便可高效地检索这些已投影的属性。当您查询 local secondary index 时,查询还可以检索 投影到索引的属性。DynamoDB 自动从基表中提取这些属性,但延迟更大,预置吞吐量成本更高。

对于任何 local secondary index,每个不同的分区键值最多可存储 10GB 个数据。此图包含基表中具有相同分区键值的所有项目,以及索引中的所有项目。有关更多信息,请参阅项目集合

属性投影

借助 LastPostIndex,应用程序可以使用 ForumNameLastPostDateTime 作为查询条件。但是,要检索任何其他属性,DynamoDB 必须对 Thread 表执行额外的读取操作。这些额外读取称为提取,它们可以增加查询所需的预置吞吐量总量。

假设您要使用“S3”中所有线程的列表填充网页,并为每个线程添加回复数 (按从最新回复开始的上次回复日期/时间排序)。要填充此列表,您需要以下属性:

  • Subject

  • Replies

  • LastPostDateTime

查询此数据并避免提取操作的最有效的方法是,将表中的 Replies 属性投影到 local secondary index 中,如下图所示。


                LastPostIndex 包含论坛名称、最后发布时间、主题和回复的列表的表。

投影 是从表复制到二级索引中的属性集。表的分区键和排序键始终投影到索引中;您可以投影其他属性以支持应用程序的查询要求。当您查询索引时,Amazon DynamoDB 可以访问投影中的任何属性,就像这些属性是在它们自己的表中一样。

创建 二级索引 时,您需要指定投影到索引中的属性。DynamoDB 提供了三个不同的选项来实现这一目的:

  • KEYS_ONLY – 索引中的每个项目仅包含表分区键和排序键值以及索引键值。KEYS_ONLY 选项生成尽可能小的 二级索引。

  • INCLUDE – 除 KEYS_ONLY 中描述的属性外,二级索引 还包括您指定的其他非键属性。

  • ALL – 二级索引 包含源表中的所有属性。由于所有表数据均在索引中得以复制,因此 ALL 投影生成尽可能大的 二级索引。

在上图中,非键属性 Replies 将投影到 LastPostIndex 中。 应用程序可以查询 LastPostIndex 而不是完整的 Thread 表,以便使用 SubjectRepliesLastPostDateTime 填充网页。 如果请求任何其他非键属性,DynamoDB 将需要从 Thread 表中提取这些属性。

从应用程序的角度来看,从基表提取其他属性是自动且透明的,因此无需重写任何应用程序逻辑。但是,此类提取会大幅降低使用 local secondary index 的性能优势。

您在选择要投影到local secondary index中的属性时,必须在预置吞吐量成本和存储成本之间做出权衡:

  • 如果只需要访问少量属性,同时尽可能降低延迟,就应考虑仅将键属性投影到local secondary index。索引越小,存储索引所需的成本越少,并且写入成本也会越少。如果您偶尔需要提取某些属性,则预置吞吐量的成本可能远远超过存储这些属性的长期成本。

  • 如果您的应用程序频繁访问某些非键属性,就应考虑将这些属性投影到local secondary index。local secondary index的额外存储成本会抵消频繁执行表扫描的成本。

  • 如果您需要频繁访问大多数非键属性,则可以将这些属性—甚至是整个基表—投影到 local secondary index。这为您提供了最大的灵活性和最低的预配置吞吐量消耗,因为无需提取。但是,如果您投影所有属性,您的存储成本将增加,甚至会翻倍。

  • 如果您的应用程序需要不经常查询表,但必须对表中的数据执行多次写入或更新,请考虑投影 KEYS_ONLY。这是最小的local secondary index,但仍可用于查询活动。

创建本地二级索引

要在表上创建一个或多个 local secondary index,请使用 LocalSecondaryIndexes 操作的 CreateTable 参数。表的本地二级索引是在创建表时创建的。删除表时,表上的任何本地二级索引也会被删除。

您必须指定一个非键属性以用作 local secondary index 的排序键。您选择的属性必须是标量 StringNumberBinary。 不允许使用其他标量类型、文档类型和集类型。有关数据类型的完整列表,请参阅数据类型

重要

对于具有本地二级索引的表,每个分区键值有 10GB 个大小限制。具有本地二级索引的表可以存储任意数量的项目,前提是任意一个分区键值的总大小不超过 10GB。有关更多信息,请参阅项目集合大小限制

您可以将任何数据类型的属性投影到local secondary index中。这包括标量、文档和集。有关数据类型的完整列表,请参阅数据类型

从 本地二级索引 读取数据

您可以使用 local secondary index 和 Query 操作从Scan中检索项目。和 GetItem 操作不能用于 GetBatchItem。local secondary index

查询本地二级索引

在 DynamoDB 表中,每个项目的组合分区键值和排序键值必须是唯一的。但是,在 local secondary index 中,排序键值对于给定分区键值不需要是唯一的。如果local secondary index中有多个项目具有相同的排序键值,则 Query 操作返回具有相同分区键值的所有项目。在响应中,匹配项目不会按任何特定顺序返回。

您可以使用最终一致性读取或强一致性读取来查询local secondary index。要指定所需的一致性类型,请使用 ConsistentRead 操作的 Query 参数。从 local secondary index 执行强一致性读取始终返回最新更新的值。如果查询需要从基表中提取其他属性,这些属性将与索引一致。

考虑从特定论坛的讨论话题请求数据的 Query 返回的以下数据。

{ "TableName": "Thread", "IndexName": "LastPostIndex", "ConsistentRead": false, "ProjectionExpression": "Subject, LastPostDateTime, Replies, Tags", "KeyConditionExpression": "ForumName = :v_forum and LastPostDateTime between :v_start and :v_end", "ExpressionAttributeValues": { ":v_start": {"S": "2015-08-31T00:00:00.000Z"}, ":v_end": {"S": "2015-11-31T00:00:00.000Z"}, ":v_forum": {"S": "EC2"} } }

在此查询中:

  • DynamoDB 访问 LastPostIndex,使用 ForumName 分区键查找“EC2”的索引项目。具有此键的所有索引项目都彼此相邻存储,以实现快速检索。

  • 在此论坛中,DynamoDB 使用索引查找与指定的 LastPostDateTime 条件匹配的键。

  • 因为 Replies 属性投影到索引中,所以 DynamoDB 可以检索此属性,而无需消耗任何额外的预置吞吐量。

  • 属性不会投影到索引中,因此 Tags 必须访问 DynamoDB 表并提取此属性。Thread

  • 结果返回,按 LastPostDateTime 排序。 索引条目先按分区键值排序,然后按排序键值排序,Query 按照存储顺序返回它们。(您可以使用 ScanIndexForward 参数以降序返回结果。)

由于 Tags 属性不会投影到 local secondary index 中,因此 DynamoDB 必须消耗额外的读取容量单位才能从基表中提取此属性。如果您需要经常运行此查询,则应将 Tags 投影到 LastPostIndex 以避免从基表中提取。但是,如果您仅偶尔需要访问 Tags,则将 Tags 投影到索引中的额外存储成本可能不太值得。

扫描本地二级索引

您可以使用 Scan 从 local secondary index 中检索所有数据。您必须在请求中提供基表名称和索引名称。通过 Scan,DynamoDB 可读取索引中的全部数据并将其返回到应用程序。您还可以请求仅返回部分数据并放弃其余数据。为此,请使用 FilterExpression API 的 Scan 参数。有关更多信息,请参阅Scan 的筛选表达式

项目写入和本地二级索引

DynamoDB 自动将所有本地二级索引与其各自的基表保持同步。应用程序绝不会直接向索引中写入内容。但是,您有必要了解 DynamoDB 如何维护这些索引。

在创建 local secondary index 时,您可以指定一个属性以用作索引的排序键。您还可以为该属性指定数据类型。这意味着,每当您向基表中写入项目时,如果该项目定义了索引键属性,则其类型必须与索引键架构的数据类型匹配。对于 LastPostIndex,索引中的 LastPostDateTime 排序键定义为 String 数据类型。如果您尝试向 Thread 表添加项目并为 LastPostDateTime 指定其他数据类型(例如 Number),则由于数据类型不匹配,DynamoDB 将返回 ValidationException

在基表中的项目与 local secondary index 中的项目之间,无需一对一关系。实际上,此行为对于许多应用程序来说都是有益的。

与索引较少的表相比,具有多个本地二级索引的表会产生更高的写入活动成本。有关更多信息,请参阅本地二级索引的预置吞吐量注意事项

重要

对于具有本地二级索引的表,每个分区键值有 10GB 个大小限制。具有本地二级索引的表可以存储任意数量的项目,只要任何一个分区键值的总大小不超过 10GB。有关更多信息,请参阅项目集合大小限制

本地二级索引的预置吞吐量注意事项

在 DynamoDB 中创建表时,您可以为表的预期工作负载预置读取和写入容量单位。该工作负载包括表的本地二级索引上的读取和写入活动。

要查看预置吞吐容量的当前费率,请参阅 定价Amazon DynamoDB。

读取容量单位

当您查询local secondary index时,占用的读取容量单位数取决于数据的访问方式。

与表查询一样,索引查询可以使用最终一致性读取或强一致性读取,具体取决于 ConsistentRead 的值。 一个强一致性读取会占用一个读取容量单位;最终一致性读取只会占用其中的一半。因此,通过选择最终一致性读取,您可以降低读取容量单位费用。

对于仅请求索引键和投影属性的索引查询,DynamoDB 计算预置读取活动的方式与对表查询执行的方式相同。唯一不同的是,本次计算基于索引条目的大小,而不是基表中项目的大小。读取容量单位的数量是返回的所有项目的所有投影属性大小之和;然后,结果向上取整到下一个 4 KB 边界。有关 DynamoDB 如何计算预配置吞吐量使用情况的更多信息,请参阅管理 DynamoDB 预置容量表的设置

对于读取未投影到 local secondary index 的属性的索引查询,DynamoDB 需要从基表提取这些属性,以及从索引读取投影的属性。当您在 Select 操作的 ProjectionExpressionQuery 参数中包含任何非投影属性时,就会发生这些提取。提取会导致查询响应中出现额外的延迟,并且还会产生较高的预置吞吐量成本:除了来自前面所述的 local secondary index 的读取之外,您还需要为提取的每个基表项目支付读取容量单位的费用。此费用用于从表中读取整个项目,而不仅仅是请求的属性。

Query 操作返回的结果大小上限为 1 MB。这包括所有属性名称的大小和所返回的所有项目的值。但是,如果针对 local secondary index 的查询导致 DynamoDB 从基表提取项目属性,则结果中数据的最大大小可能较小。在这种情况下,结果大小是以下项的总和:

  • 索引中匹配项目的大小(向上取整到下一个 4 KB)。

  • 基表中每个匹配项目的大小,其中每个项目单独舍入到下一个 4 KB。

使用此公式,Query 操作返回的结果的最大大小仍为 1 MB。

例如,请考虑每个项目的大小均为 300 字节的表。该表上有一个 local secondary index,但每个项目的 200 个字节仅投影到索引中。现在,假设您对此索引进行 Query,查询要求表提取每个项目,并且查询返回 4 个项目。DynamoDB 对以下内容进行求和:

  • 索引中匹配项目的大小:200 字节 × 4 项目 = 800 字节;然后,此项将向上取整为 4 KB。

  • 基表中每个匹配项目的大小:(300 字节,四舍五入为 4 KB)× 4 项目 = 16 KB。

因此,结果中的数据的总大小为 20 KB。

写入容量单位

在添加、更新或删除表中的项目时,更新本地二级索引会占用表的预置写入容量单位。写入的总预置吞吐量成本是通过写入表所占用的写入容量单位与通过更新本地二级索引所占用的写入容量单位之和。

向local secondary index写入项目的成本取决于多个因素:

  • 如果您向定义了索引属性的表中写入新项目,或更新现有的项目来定义之前未定义的索引属性,只需一个写入操作即可将项目放置到索引中。

  • 如果对表的更新更改了索引键属性的值(从 A 更改为 B),则需要执行两次写入:一次用于删除索引中之前的项目,另一次用于将新项目放置到索引中。 

  • 如果索引中已有某一项目,而对表执行的写入操作删除了索引属性,就需要执行一次写入操作删除索引中旧的项目投影。

  • 如果更新项目前后索引中没有此项目,此索引就不会额外产生写入成本。

所有这些因素都假定索引中每个项目的大小小于或等于 1 KB 这一项目大小(用于计算写入容量单位)。如果索引条目大于这一大小,就会占用额外的写入容量单位。您可以考虑查询需要返回的属性并将这些属性仅投影到索引,从而最大程度地降低写入成本。

本地二级索引的存储注意事项

当应用程序向表中写入项目时,DynamoDB 会自动将正确的属性子集复制到应包含这些属性的任何本地二级索引。您的 AWS 账户需要支付在基表中存储项目以及在表的任何本地二级索引中存储属性的费用。

索引项目所占用的空间大小就是以下内容之和:

  • 基表的主键 (分区键和排序键) 的大小 (按字节计算)

  • 索引键属性的大小(按字节计算)

  • 投影的属性(如果有)的大小(按字节计算)

  • 每个索引项目 100 bytes 的开销

要估计local secondary index的存储要求,您可以估计索引中项目的平均大小,然后乘以基表中项目的数量。

如果表包含的某个项目未定义特定属性,但该属性定义为索引排序键,则 DynamoDB 不会将该项目的任何数据写入到索引。

项目集合

注意

此部分仅适用于具有本地二级索引的表。

在 DynamoDB 中,项目集合 是任意项目组,这些项目在表及其所有本地二级索引中具有相同分区键值。在本部分中使用的示例中,Thread 表的分区键为 ForumNameLastPostIndex 的分区键也为 ForumName。 具有相同 ForumName 的所有表和索引项目都是相同项目集合的一部分。例如,在 Thread 表和 LastPostIndex local secondary index 中,有一个用于论坛 EC2 的项目集合和一个用于论坛 RDS 的不同项目集合。

下图显示了论坛 S3 的项目集合。

在此图中,项目集合包含 ThreadLastPostIndex 中所有项目,其中 ForumName 分区键值为“S3”。如果表上有其他本地二级索引,则 ForumName 等于“S3”的索引中的任何项目都将是项目集合的一部分。

您可以使用 DynamoDB 中的以下任意操作返回有关项目集合的信息:

  • BatchWriteItem

  • DeleteItem

  • PutItem

  • UpdateItem

其中每个操作都支持 ReturnItemCollectionMetrics 参数。当您将此参数设置为 SIZE 时,您可以查看有关索引中每个项目集合的大小信息。

下面是对 UpdateItem 表执行 Thread 操作输出的示例(ReturnItemCollectionMetrics 设置为 SIZE)。 已更新的项目具有 ForumName 值“EC2”,因此输出包含有关该项目集合的信息。

{ ItemCollectionMetrics: { ItemCollectionKey: { ForumName: "EC2" }, SizeEstimateRangeGB: [0.0, 1.0] } }

对象显示此项目集合的大小介于 0 和 1 GB 之间。SizeEstimateRangeGB 定期更新此大小估计值,因此下次修改项目时,数字可能不同。DynamoDB

项目集合大小限制

任何项目集合的最大大小均为 10GB。此限制不适用于没有本地二级索引的表。只有具有一个或多个本地二级索引的表会受到影响。

如果某个项目集合超出 10GB 限制,DynamoDB 将返回 ItemCollectionSizeLimitExceededException,并且您将无法在项目集合中添加更多项目或增加项目集合中项目的大小。(仍允许执行缩减项目集合大小的读取和写入操作。) 您仍可以将项目添加到其他项目集合。

要减小项目集合的大小,您可以执行以下操作之一:

  • 删除任何具有相关分区键值的不必要的项目。当您从基表中删除这些项目时,DynamoDB 还会删除具有相同分区键值的任何索引条目。

  • 通过删除属性或通过减小属性的大小来更新项目。如果将这些属性投影到任何本地二级索引,则 DynamoDB 还会减小相应索引条目的大小。

  • 创建具有相同分区键和排序键的新表,然后将项目从旧表移到新表。如果表具有不常访问的历史数据,这可能是好方法。您还可以考虑将此历史数据存档到 Amazon Simple Storage Service (Amazon S3)。

当项目集合的总大小下降到 10GB 以下时,您可以再次添加具有相同分区键值的项目。

作为最佳实践,我们建议您分析您的应用程序以监控项目集合大小。执行此操作的一种方法是,每次使用 ReturnItemCollectionMetricsSIZEBatchWriteItemDeleteItem 时将 PutItem 参数设置为 UpdateItem。 您的应用程序应检查输出中的 ReturnItemCollectionMetrics 对象,并在项目集合超过用户定义的限制(例如,表)时记录错误消息。设置小于 10GB 的限制会提供一个提前警告系统,以便您知道项目集合即将达到对该集合执行某些操作的限制。

项目集合和分区

每个项目集合的表和索引数据存储在一个分区中。请参考 Thread 表示例,具有相同 ForumName 属性的所有基表和索引项目都将存储在同一分区中。“S3”项目集合将存储在一个分区中、在另一个分区中“EC2”以及第三个分区中“RDS”。

您应该设计应用程序,以便表数据跨不同的分区键值均匀分布。对于具有本地二级索引的表,您的应用程序不应在单个分区上的单个项目集合中创建读取和写入活动的“热点”。