Amazon DynamoDB
开发人员指南 (API Version 2012-08-10)
AWS 服务或AWS文档中描述的功能,可能因地区/位置而异。点 击 Getting Started with Amazon AWS to see specific differences applicable to the China (Beijing) Region.

针对表的最佳实践

本节介绍处理表的最佳实践。

表内项目间的统一数据访问设计

表的预置吞吐量最佳使用情况取决于这些因素:

  • 主键选择。

  • 单个项目上的工作负载模式.

主键将唯一标识表中的每个项目。主键可以是简单主键 (分区键) 或复合主键 (分区键和排序键)。

在存储数据时,DynamoDB 会将表的项目划分至多个分区,数据的分布主要是根据分区键值来进行。因此,要全额获取您为表预置的请求吞吐量,请使工作负载在分区键值之间均匀分布。在分区键值间分配请求即会在分区间分配请求。

例如,如果表的少数几个分区键值的访问量非常大,或者有一个分区键值的使用量非常大,则请求流量将集中在少数几个分区上,甚至可能只在一个分区上。如果工作负载严重不均衡 (即不成比例地集中在一个或几个分区上),则请求将无法达到总体的预置吞吐量级别。为了充分利用 DynamoDB 吞吐量,应当在分区键具有大量非重复值并且对值的请求非常均匀时,以尽可能随机的方式创建表。

这并不意味着您必须访问所有分区键值来实现吞吐量级别,也不意味着访问分区键值的百分比需要非常高。但是请注意,随着您的工作负载访问更多的非重复分区键值,这些请求将分散到分区空间中,以更好地利用您分配的吞吐量级别。一般来说,随着表中被访问过的分区键值数与分区键值总数的比率不断增大,您将能够更高效地使用您的吞吐量。

选择分区键

下表比较了一些常见分区键架构的预置吞吐量效率:

分区键值 均一

用户 ID,应用程序中有许多用户。

状态代码,只有几个可用的状态代码。
项目创建日期,四舍五入至最近的时间段 (例如天、小时,分钟)
设备 ID,每个设备以相对类似的间隔访问数据
设备 ID,被跟踪的设备有很多,但到现在为止,其中某个设备比其他所有设备更加常用。

如果单个表只有非常少量的分区键值,请考虑在更多的非重复分区键值之间分布写入操作。也就是说,设计主键元素的结构,避免出现“热点” (请求频率非常高的) 分区键值而导致整体性能降低。

例如,最好设计具有复合主键的表。分区键代表项目创建日期,四舍五入至最近的一天。排序键表示项目标识符。在指定的一天,假设为 2014-07-09,所有新项目都将写入到该相同的分区键值下。

如果表可以完全放入单个分区中 (考虑到您的数据随时间的增长),并且您的应用程序读取和写入吞吐量要求不超过单个分区的读取和写入容量,则您的应用程序不会由于分区而遇到任何意外的限制。

但是,如果您预期会扩展到单个分区之外,则您应该设计应用程序架构,使应用程序能够更好地利用表的全部预置吞吐量。

跨多个分区键值随机化

提高此应用程序写入吞吐量的一种方法是在多个分区键值之间随机化写入操作。从一组固定数字 (例如,1200) 中选择随机数,并将其作为后缀链接到日期。这将生成分区键值,例如 2014-07-09.12014-07-09.2,以此类推,直到 2014-07-09.200。随机化分区键后,每天对表的写入将在所有分区键值之间均匀分布;这样可以实现更好的并行度以及更高的整体吞吐量。

要读取给定一天的所有项目,您需要获取每个后缀的所有项目。例如,您需要先针对分区键值 2014-07-09.1 发布 Query 请求,然后再针对 2014-07-09.2 发布另一个 Query,以此类推,直到 2014-07-09.200。最后,您的应用程序需要合并来自所有 Query 请求的结果。

使用计算值

随机化策略可以显著提升写入吞吐量;但是,它难于读取特定项目,因为您不知道在写入项目时使用了什么后缀值。为了更方便地读取单个项目,您可以使用一种不同的策略:不使用随机编号在分区之间分布项目,而是使用能够基于项目的某种固有属性来计算的编号。

继续使用我们的示例,假设每个项目有一个 OrderId。您的应用程序在向表中写入项目之前,可以根据订单 ID 计算分区键后缀。计算结果应为 1 到 200 之间的数字,这 200 个数字会在任何一组给出的名称 (或用户 ID) 之间完全平均分布。

简单的计算便已足够,例如订单 ID 中字符的 UTF-8 码位值的积,取模 200 + 1。分区键值则是将计算结果作为后缀附加到日期得到的值。通过此策略,写入操作将在分区键值之间均匀分布,从而在分区之间均匀分布。您可以方便地在特定项目上执行 GetItem 操作,因为在您需要检索特定 OrderId 值时,您可以计算出所需的分区键值。

要读取给定一天中的所有项目,您仍需要对各个 2014-07-09.N 键 (其中 N 为 1 到 200) 执行 Query 操作,您的应用程序需要合并所有结果。并且,这样可以避免一个“热点”分区键值占用所有工作负载。

了解分区行为

DynamoDB 为您自动管理表分区,必要时添加新分区并在分区之间平均分配预置的吞吐容量。

您可以估算 DynamoDB 最初将为您的表分配的分区数,然后将该估算值与您的规模和访问模式进行比较。您也可以估算为响应增加的存储或预置吞吐量要求,DynamoDB 将额外分配的分区数量。这些估算值可帮助您根据您的应用程序需求确定最佳表设计。

注意

以下有关分区大小和吞吐量的详细信息会随时发生变化。

分区的初始分配

在创建新表时,DynamoDB 会根据您指定的预置吞吐量设置分配表分区。

单一分区最多可支持 3000 个读取容量单位或 1000 个写入容量单位。在创建新表时,分区的初始数量可按以下方式表示:

Copy
( readCapacityUnits / 3,000 ) + ( writeCapacityUnits / 1,000 ) = initialPartitions (rounded up)

例如,假设您创建了具有 1000 个读取容量单位和 500 个写入容量单位的表。在这种情况下,分区的初始数量为:

Copy
( 1,000 / 3,000 ) + ( 500 / 1,000 ) = 0.8333 --> 1

因此,单个分区可以满足表的全部预置吞吐量需求。

但是,如果您创建了具有 1000 个读取容量单位和 1000 个写入容量单位的表,则单个分区无法支持指定的吞吐容量:

Copy
( 1,000 / 3,000 ) + ( 1,000 / 1,000 ) = 1.333 --> 2

在这种情况下,表需要两个分区,每个具有 500 个读取容量单位和 500 个写入容量单位。

分区的顺序分配

单个分区大约可保存 10GB 数据,可支持最多 3000 个读取容量单位或 1000 个写入容量单位。

必要时,DynamoDB 可通过拆分 现有分区,为您的表分配更多分区。假设表的一个分区 (P) 超过其存储限制 (10GB)。在这种情况下,DynamoDB 将如下拆分该分区:

  1. 分配两个新分区 (P1P2)。

  2. P 中的数据在 P1P2 之间均匀分配。

  3. 从表中取消分配 P

下图显示 DynamoDB 如何执行分区拆分。大方形表示分区,小方形表示表中的数据项。

在分区拆分期间,DynamoDB 将旧分区中的数据均匀分配给两个新分区 (其他分区中的数据不受影响)。然后,旧分区的预置的吞吐容量将均匀分布到两个新分区中 (请参阅每个分区的吞吐容量)。

请注意,DynamoDB 在后台自动执行分区拆分。表仍然完全可在您指定的吞吐量级别进行读取和写入活动。

分区拆分可用于应对以下情况:

  • 预置的吞吐量设置增加

  • 存储要求提高

预置的吞吐量设置增加

如果您增加表的预置吞吐量,而且目前表的分区模式无法满足您的新要求,DynamoDB 会将当前的分区数量增加一倍

例如,假设您创建了具有 5000 个读取容量单位和 2000 个写入容量单位的新表。使用来自分区的初始分配的信息,您可以确定这个新表需要四个分区:

Copy
( 5,000 / 3,000 ) + ( 2,000 / 1,000 ) = 3.6667 --> 4

四个分区中每个都能够支持每秒 1250 次读取 (5000 个读取容量单位/4 个分区) 和每秒 500 次写入 (2000 个写入容量单位/4 个分区)。

现在,假设您将表的读取容量单位从 5000 增加到 8000。现有的四个分区无法支持这一要求。为此 (请参阅分区的顺序分配),DynamoDB 将分区数量加倍 (4 * 2 = 8)。生成的每个分区都能够支持每秒 1000 次读取 (8000 个读取容量单位/8 个分区) 和每秒 250 次写入 (2000 个写入容量单位/8 个分区)。

下图显示表中的四个原始分区和 DynamoDB 将分区数量增加一倍后生成的分区模式。大方形表示分区,小方形表示表中的数据项。

存储要求提高

如果现有分区已填满数据,DynamoDB 将拆分该分区。结果将生成两个分区,旧分区中的数据在新分区之间平均分配。

预置的吞吐量设置增加中介绍的表格有八个分区,因此最大容量大约为 80 GB,如下所示:

Copy
8 partitions * 10GB = 80 GB

如果其中一个分区即将完全填满,DynamoDB 的应对措施是拆分该分区,因而总共产生九个分区,总容量达到 90 GB,如下所示:

Copy
9 partitions * 10GB = 90 GB

下图显示一个完全填满的原始分区和 DynamoDB 拆分该分区后生成的分区模式。大方形表示分区,小方形表示表中的数据项。

每个分区的吞吐容量

如果您估算表中的分区数量,便可以确定每个分区吞吐容量的大约值。假设您想要创建一个具有 5000 个读取容量单位和 2000 个写入容量单位的表。DynamoDB 将为新表分配四个分区:

Copy
( 5,000 / 3,000 ) + ( 2,000 / 1,000 ) = 3.6667 --> 4

您可以如下确定每个分区的读取和写入容量:

Copy
5,000 read capacity units / 4 partitions = 1,250 read capacity units per partition 2,000 write capacity units / 4 partitions = 500 write capacity units per partition

现在,假设这四个分区中有一个即将完全填满。DynamoDB 会拆分该分区,从而有五个分区分配给该表。然后,如下分配每个分区的读取和写入容量:

Copy
1,250 read capacity units / 2 partitions = 625 read capacity units per child partition 500 write capacity units / 2 partitions = 250 write capacity units per child partition

结果:

  • 三个分区 (共五个分区) 各自具有 1250 个读取容量单位和 500 个写入容量单位。

  • 其他二个分区各自具有 625 个读取容量单位和 250 个写入容量单位。

请注意,随着表中的分区数量逐渐增加,每个分区可用的读取和写入容量单位越来越少。 (但是,表的总预置吞吐量保持不变。)

谨慎使用突增容量

DynamoDB 可以较为灵活地预置每个分区的吞吐量。当您没有完全利用某个分区的吞吐量时,DynamoDB 会保留一部分未使用的容量,以便应对以后的吞吐量使用突增的情况。DynamoDB 当前最长可保留五分钟 (300 秒) 未使用的读取和写入容量。在读取或写入活动偶尔突增期间,这些额外的容量单位可能会快速消耗,速度之快甚至超过您为表定义的每秒预置吞吐量。但是,请不要将您的应用程序设计为总是依赖于可用的突增容量,DynamoDB 会在未经事先通知的情况下将突增容量用于后台维护和其他任务。

注意

未来,突增容量的这些具体细节可能会发生变化。

在数据上传期间分散写入活动

很多时候,您需要将数据从其他数据源加载到 DynamoDB。通常情况下,DynamoDB 会将表的数据划分到多个服务器。这样可以提升向表中上传数据的性能 (如果您同时向所分配的所有服务器上传数据)。例如,假设您要将用户消息上传到某个 DynamoDB 表。您需要设计一个使用复合主键 (分区键和排序键分别为 UserID 和 MessageID) 的表。从源文件上传数据时,您可能倾向于读取某个特定用户的所有消息项目,并将这些项目上传到 DynamoDB,如下表的序列所示。

UserID MessageID

U1

1

U1 2
U1 ...
U1 ...直到 100

U2

1

U2 2
U2 ...
U2 ...直到 200

这种情况下存在的问题是,您未在 DynamoDB 的分区键值之间分散您的写入请求。您一次只采用一个分区键值并上传其所有项目,然后再转至下一个分区键值并对其执行相同操作。DynamoDB 会在后台将表中的数据划分到多个服务器。为了充分利用为您的表预置的所有吞吐容量,您需要在分区键值之间分散工作负载。这时,将上传工作量不均匀地分散给所有具有相同分区键值的项目,可能会导致您无法充分利用 DynamoDB 为您的表预置的所有资源。您可以先从每个分区键值中上传一个项目,以分散上传工作量。然后,针对下一组排序键值重复这一模式,直到按下表中的示例上传顺序上传完所有项目数据为止:

UserID MessageID

U1

1

U2 1
U3 1
... ....

U1

2

U2 2
U3 2
... ...

此序列中的每次上传都使用不同的分区键值,以便能够同时使用更多 DynamoDB 服务器,从而提高吞吐量性能。

了解时间序列数据的访问模式

您创建每一个表都要为其指定吞吐量要求。DynamoDB 会分配资源来持续以低延迟方式处理您的吞吐量要求。在设计您的应用程序和表时,您应当考虑应用程序的访问模式,以最有效的方式使用表的资源。

假设您设计了一个表来在跟踪客户在您网站上的行为 (例如单击 URL)。您需要设计具有复合主键 (分区键和排序键分别为 Customer ID 和 Date/Time) 的表。在此应用程序中,客户数据会随着时间的推移而无限增长;但是,该表所有项目的访问模式可能在该应用程序中并不均等。靠近当前日期的客户数据会有更大的相关性。并且应用程序对靠近当前日期的项目访问得更加频繁,但是随着时间的推移对这些项目的访问会有所减少,最终较早的项目会很少被访问到。如果这是一个已知的访问模式,那么在设计表的架构时您应当将此因素考虑在内。这样您就可以使用多个表来存储项目,而不是将所有这些项目都存储在一个表中。例如,您可以创建存储每月或每周数据的表。对于存储最近一个月或一周数据的表,由于其访问率很高,您可以请求较高的吞吐量;而对于存储较早的数据的表,您可以将吞吐量调低以节省资源。

您可以将“热”项目存储在一个具有较高吞吐量设置的表中,将“冷”项目存储在另一个具有较低吞吐量设置的表中,从而节省资源。您只需删除相应的表,即可移除较早的项目。您可以选择将这些表备份到其他存储选项,例如 Amazon Simple Storage Service (Amazon S3)。删除整个表的效率要显著高于逐个移除项目。后者会从根本上使写入吞吐量加倍,因为您每做一个删除操作都要有一个放置操作。

缓存常用项目

表中可能有些项目比其他项目更常用。例如,来看一下在创建表并加载示例数据中介绍的 ProductCatalog 表,假设此表包含数百万种不同产品。某些产品可能是客户经常使用的,所以这些项目的访问频率一直比其他项目更高。因此,ProductCatalog 上读取活动的分布会高度偏向这些常用项目。

一种解决方案是将这些读取缓存在应用程序层。缓存是很多高吞吐量应用程序中使用的技术,将热点项目的读取活动转移到缓存而不是数据库。应用程序可以将最常用的项目缓存在内存中,或使用 ElastiCache 这样的产品实现。

继续 ProductCatalog 示例,当客户从该表请求某一项目时,应用程序将首先在缓存中查找,查看缓存中是否存在该项目的副本。如果是,则表示缓存命中;否则表示缓存未命中。当缓存未命中时,应用程序需要从 DynamoDB 读取项目并在缓存中存储该项目的副本。随着时间的推移,缓存中充满最常用项目,缓存未命中率降低;应用程序就不需要为这些项目访问 DynamoDB 了。

缓存解决方案可以缓解常用项目的偏向读取活动。此外,因为缓存会减少针对表的读取活动数量,所以有助于降低使用 DynamoDB 的总体成本。

在调整预置吞吐量时考虑使用均一工作负载

随着表中的数据量增长,或者随着您预置额外的读取和写入容量,DynamoDB 会自动跨多个分区分布数据。如果您的应用程序不需要这么多的吞吐量,您只需使用 UpdateTable 操作来减少吞吐量,并且只为您预置的吞吐量支付费用。

对于旨在使用均匀工作负载的应用程序,DynamoDB 分区分配活动不明显。临时的不均衡工作负载通常可以通过突增限额来吸收,如 谨慎使用突增容量 中所述。但是,如果您的应用程序必须经常满足不均衡的工作负载,那么您应该记住使用 DynamoDB 的分区行为来设计您的表 (请参阅了解分区行为),并谨慎增加和减少该表的预置吞吐量。

如果您降低表的预置吞吐量,DynamoDB 不会减少分区数。假设您创建的表具有的吞吐量远超过您的应用程序实际需求,然后在一段时间后减少了预置吞吐量。在这种情况下,相比您最初使用较低吞吐量来创建该表的情况,每个分区的预置吞吐量会更低。

例如,请考虑一种情况,您需要将 2000 万个项目批量加载到 DynamoDB 表中。假定每个项目大小为 1 KB,这会生成 20 GB 数据。这个批量加载任务总共需要 2000 万个写入容量单位。要在 30 分钟内执行此数据加载,您需要将表的预置写入吞吐量设置为 11000 个写入容量单位。

分区的最大写入吞吐量为 1000 个写入容量单位 (请参阅了解分区行为);因此,DynamoDB 将创建 11 个分区,每个分区具有 1000 个预置写入容量单位。

在批量数据加载之后,您的稳定运行写入吞吐量需求可能会低得多,例如,假设您的应用程序只需要每秒 200 次写入。如果您将表的预置吞吐量减少到此级别,那么这 11 个分区中的每个分区将预置为大约每秒 20 个写入容量单位。每个分区的这一预置吞吐量级别与 DynamoDB 的突增行为相结合,应该足以满足应用程序需求。

但是,如果应用程序需要持续保持每个分区每秒超过 20 次的写入吞吐量,您应该:(a) 设计一个架构,其中每个分区键值每秒需要的写入次数更少,或者 (b) 设计批量数据加载,使其运行速度更低,从而减少初始吞吐量需求。例如,假设可以接受运行时间超过 3 个小时而不是 30 分钟的批量导入。在这种情况下,只需要预置每秒 1900 个写入容量单位,而不是 11000 个。因此,DynamoDB 只会为表创建两个分区。

大规模测试您的应用程序

许多表在一开始只有少量数据,但是随着应用程序执行写入活动而增大。这种增长可能是循序渐进的,不会超出您为表定义的预置吞吐量设置。随着您的表增大,DynamoDB 会通过将数据分布到更多分区中来自动扩展您的表。出现这种情况时,分配给生成的每个分区的预置吞吐量低于为原始分区分配的预置吞吐量。

假设您的应用程序跨所有分区键值访问表的数据,但使用的是不均衡的方式 (少数分区键值的访问频率远高于其他键值)。在表中没有太多数据时,您的应用程序性能可能不错。但是,随着表增大,分区就会越来越多,每个分区的吞吐量越来越少。您可能会发现,您的应用程序在尝试使用过去适用的非均衡访问模式时会受到限制。

要在表增大时避免出现“热点”键问题,请确保对应用程序设计开展大规模测试。请考虑在大规模运行时的存储与吞吐量的比率,以及 DynamoDB 如何将分区分配到表。 (有关更多信息,请参阅 了解分区行为。)

如果无法生成大量的测试数据,您可以创建具有非常高的预置吞吐量设置的表。这会创建带有大量分区的表,然后您可以使用 UpdateTable 来减少设置,但保持您在大规模运行应用程序时确定的相同存储与吞吐量的比率。现在,您的表具有预期在大规模增长之后的每分区吞吐量比率。使用真实的工作负载在此表上测试您的应用程序。

存储时间序列数据的表可能会无限制地增长,并且在一段时间后,应用程序性能会变慢。对于时间序列数据,应用程序在表中读取和写入最近项目的频率通常要远高于较旧的项目。如果您从实时表中删除较旧的时间序列数据,并将这些数据存档到其他位置,您就可以保持每个分区较高的吞吐量比率。

有关适用于时间序列数据的最佳实践,请参阅了解时间序列数据的访问模式