对动态数据库分区的深入研究


数据库是大多数现代web应用程序的支柱,它们的性能在用户体验中起着重要作用。更快的响应时间——甚至是几分之一秒——可能是大多数用户选择其中一个选项的主要决定因素。因此,在设计数据库时考虑响应速度是非常重要的,以便提供尽可能好的性能。在本文中,我将讨论如何通过使用分区来优化数据库性能。

引入分区

电动数据库性能始于概念,止于概念分区。分区就像是存储和性能的单位。不要低估分区,这意味着你将无法设计出高效且可用的数据库。因此,了解幕后发生的事情是值得的。

最初,当你在动态数据库上创建一个表时,它会创建一个分区并将这个分区分配给这个表。此表上的任何操作(如插入、删除和更新)都将由存储此分区的节点处理。请记住,您不能完全控制创建的分区数量,但这可能会受到影响。

一个分区可以处理10GB的数据、3,000个读取容量单位(RCU)和1,000个写入容量单位(WCU),这表明表中存储的数据量与性能要求之间存在直接关系。当表中存储的数据超过10GB时,将添加一个新分区,或者区域协调单位超过3,000个,或者wcu大于1,000。然后,数据将分布在这些分区中。

那么,动态数据库是如何将数据分散到多个分区的呢?特定行所在的分区基于分区键。对于每个唯一的分区键值,该项被分配给一个特定的分区。

让我们用一个例子来演示。下表列出了考试和参加考试的学生。

在这个例子中,考试和学生之间是多对一的关系(为了简单起见,我们假设学生不会补考)。如果这个表只适用于某所学校的所有学生,那么数据集就相当小了。然而,如果是一个州或国家的所有学生,可能会有数百万行。这可能会使我们处于数据存储和性能限制的范围内,从而导致需要一个新的分区。

如果基于所需的RCU和WCP或数据集的大小,测功机数据库决定将其扩展到三个分区,下面是上述数据可能如何分布的虚拟表示:

正如我们上面看到的,每个考试标识都被分配到一个唯一的分区。根据数据集的大小,一个分区可能包含多个分区键值,但这里需要记住的重要一点是,一个分区键值只能分配给一个分区。许多学生可以参加一次考试。因此,学生标识成为查询该数据的最佳排序键值(因为它允许按学生标识对考试结果进行排序)。

通过添加更多分区,或者通过在分区之间移动数据,可以根据数据集的大小或性能要求进行无限扩展。然而,同样重要的是要记住,有严重的限制,必须加以考虑。

首先,分区的数量由动态数据库管理,添加分区是为了适应不断增加的数据集大小或不断提高的性能要求。虽然这对于增加分区数量是正确的,但是在容量或性能降低期间,分区不会自动减少。

这就引出了我们的下一个重点,即分配给RCU(读容量单位)和WCU(写容量单位)的值分布在多个分区上。例如,假设您需要向数据库分配30,000个区域协调单位。RCU单个分区最多可以支持3,000个分区。因此,为了满足这个请求,DynamoDB将自动创建10个分区。

如果您通过控制台增加您的RCU和WCU,AWS将为您提供每月的估计费用,如下所示:

以学生考试为例,每个考试的数据集都被分配到一个分区,正如您所记得的,这个分区可以容纳10GB的数据、3,000个区域协调单位和1,000个区域协调单位。然而每一次考试都会有数百万学生。因此,该数据集的大小可能远远超过10GB的容量限制(在为特定数据集选择分区键时必须记住这一点)。

将表格的RCU值或WCU值增加到超过3,000个区域协调单位和1,000个区域协调单位时,即使所需的区域协调单位和区域协调单位的数量减少,动态数据库也会创建额外的分区,但无法减少分区的数量。这可能导致这样一种情况,即每个分区最终只有很少的区域协调股和区域协调股。

由于过度节流可能导致性能问题,即使总的分配区域协调单位和区域协调单位适合预期负载,也可以创建一个公式来计算所需的分区数量,同时考虑性能。

基于我们要求的读取性能:

//Partitions for desired read performance  
Desired RCU / 3000 RCU

根据我们要求的写入性能,

//Partitions for desired write performance 
Desired WCU / 1000 WCU

为我们提供所需性能所需的分区数量,

//Total partitions for desired performance 
(Desired RCU / 3000 RCU) + (Desired WCU / 1000 WCU)

但这只是性能方面。我们还必须考虑存储方面。假设单个分区支持的最大容量为10GB,

//Total partitions for desired storage 
Desired capacity in GB / 10GB

以下公式可用于计算分区总数,以适应所需的性能方面和容量方面。

//Total partitions 
MAX(Total partitions for desired performance, Total partitions for desired capacity)

例如,考虑以下要求:

  • RCU容量:7,500
  • WCU容量:4,000
  • 存储容量:100GB

性能所需的分区数量可计算如下:

(7500/3000) + (4000/1000) = 2.5 + 4 = 6.5

我们将把它四舍五入到最接近的整数:7。

容量所需的分区数量为:

100/10 = 10

因此,所需的分区总数为:

MAX(7, 10) = 10

一个关键因素是RCU和WCU的总数在分区总数中平均分配。因此,如果您在所有分区上并行读写,您将只能获得一个表的总分配RCU和WCU量。这只能通过一个好的分区密钥模型来存档,这意味着密钥均匀地分布在所有的密钥空间中。

选择一个好的分区键

在选择一个好的关键字时,没有统一的答案——这完全取决于数据集的性质。对于一个小容量的表,键的选择并不重要(3000 RCU和1000 RCU的单个分区是可以实现的,即使是一个设计糟糕的键结构)。然而,随着数据集的增长,关键选择变得越来越重要。

创建表时必须指定分区键。如果您正在使用控制台,您将看到类似于以下内容的内容:

或者,如果您使用命令行界面,您必须运行类似以下内容:

aws dynamodb create-table \
    --table-name us_election_2016 \
    --attribute-definitions \
    AttributeName=candidate_id,AttributeType=S \
    AttributeName=voter_id,AttributeType=S \
    AttributeName=state,AttributeType=S \
    --key-schema AttributeName=candidate_id,KeyType=HASH AttributeName=voter_id,KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

选择好的分区键的第一个标准是选择具有尽可能多的不同值的属性。例如,当有许多员工可用时,您可以选择一个员工标识。应该做什么被选中的是一个部门标识,这里只有几个可用的部门。

下一个标准是选择一个对所有键值访问一致的属性。例如,在投票记录系统中,如果您希望每个候选人获得相似的票数,那么选择一个候选人标识将是理想的。如果一两个候选人获得90%的可用选票,那么这就变得不太理想了。

好的分区键候选的另一个标准是属性应该具有跨时间的临时读写模式。如果用一个现有的属性很难实现这些,那么就值得考虑一个语法或混合值。

让我们看一个例子,用2016年美国大选来突出我们刚才讨论的一切。具体来说,我们希望存储所有候选人的所有投票记录。

每个政党都将有许多候选人竞争该党的选举提名。你可能有两到十个候选人。问题是候选人之间的票数不会均匀分配——将会有一两个候选人获得多数票。

为了这个例子,让我们假设我们期望收到价值10,000 WCU的投票。假设,在第一个实例中,我们创建了一个表,并天真地选择候选标识作为分区键,日期/时间作为范围键。

在这个例子中,动态数据库将创建10个分区(根据我们以前的公式,需要10个分区来支持10,000个WCU)。如果我们也假设我们有10个候选分区,动态数据库会将这些分区键分布在10个分区上,如下所示:

这种模式有很大的缺陷。首先,我们将候选人的绩效限制在远低于10,000 WCU的水平。正如我们上面讨论的,现实世界的候选人投票将严重偏向一两个受欢迎的候选人。因此,分配给最不受欢迎的候选人的表现只是浪费WCU。

即使我们假设候选人之间的投票权重是一致的,他们的投票者可能位于不同的时区,也可能在不同的时间投票。因此,与其他候选人相比,某些候选人在特定时间可能会出现投票高峰。即使使用精心设计的分区键,您也会遇到这样的基于时间的问题。

让我们考虑一个在全国选举中只有两个候选人的情况。为了支持性能容量,分配了100,000个wcu,而动态数据库将创建100个分区来支持这一点。但是,如果选择候选标识作为分区键,每个候选数据将被限制在一个分区中,即使有98个未使用的分区。因此,您将很快达到存储限制,导致应用程序失败并停止记录更多投票。

这个问题通过引入密钥共享计划得以解决。这意味着对于每个候选项(即每个分区),分区键的前缀值为1-10或1-1000(根据数据集的大小加深)。这给了我们更广泛的分区键。这意味着DynamoDB将在多个分区之间平均分配这些数据。它看起来有点像这样:

现在,我们可以看看直方图以前密钥共享:

对应的分区键看起来像什么(请注意,在这个例子中,我只插入了两个候选的数据):

这是直方图在...之后密钥共享:

我们可以看到,有了密钥共享计划,负载如何在分区间更均匀地分布。节流是最小的。相应的分区键如下所示:

结论

在动态数据库上设计数据模型时,还有许多其他因素需要考虑,例如Local Secondary IndexesGlobal Secondary Indexes。有关这些索引的更多信息,请查看AWS documentation了解它们如何影响数据库性能。

在选择数据库结构时,数据库建模非常重要,对于一个性能最佳的应用程序来说,这是必不可少的。尽管DynamoDB是一个完全受管理且高度可扩展的数据库解决方案,但在设计一个可靠的应用程序时,这一切都取决于您。不管数据库有多强大,设计不佳的数据库模型都会导致应用程序性能不佳。