动机说明:当使用云存储,如DynamoDB时,我们必须意识到存储模型的影响,因为它将直接影响性能、可扩展性和财务成本。与使用本地数据库不同,您不仅支付存储的数据,还支付对数据执行的操作。例如,删除记录是一项写操作,因此如果您没有有效的清理计划(尤其是在处理时间序列数据的情况下),您将付出代价。您的数据模型在处理小数据量时不会出现问题,但在需要扩展时肯定会破坏您的计划。话虽如此,像创建(或不创建)索引、为键定义适当的属性、创建表分割等决策将产生整体巨大的差异。选择DynamoDB(或更通用地说,选择键值存储)作为任何其他架构决策都伴随着一个权衡,您需要清楚地了解存储模型的某些概念,才能有效地使用该工具。选择正确的键确实很重要,但这只是冰山一角。例如,如果您忽略了您正在处理时间序列数据的事实,那么无论您定义什么主键或索引,您的预配吞吐量都不会得到优化,因为它分布在整个表格(及其分区)上,而不仅仅是经常访问的数据,这意味着未使用的数据直接影响吞吐量,因为它是同一张表的一部分。这导致了当您知道您的预配吞吐量应该足够满足您的需求时,“意外”抛出“ProvisionedThroughputExceededException”异常的情况,但正在不均匀地访问表格分区已达到其极限(更多详细信息在这里)。
以下帖子有更多细节,但我想给你一些动力来阅读并理解,尽管你现在可以找到一个更简单的解决方案,但在不久的将来,这可能意味着从头开始解决问题(“墙”可能会表现为高昂的财务成本、性能和可扩展性限制,或三者的组合)。
问:我能以唯一的通知ID作为哈希键,用户ID作为范围键吗?这样做是否只允许我通过范围键进行查找,即不提供哈希键?
答:DynamoDB是一个键值存储,这意味着最有效的查询使用整个键(哈希或哈希范围)。使用Scan
操作实际上执行查询只是因为您没有您的键,这绝对是您的数据模型在满足您的要求方面存在不足的迹象。有几件事情需要考虑,并且有许多选项可以避免这个问题(下面有更多详细信息)。
在继续之前,我建议您阅读这篇快速文章,以清楚地了解哈希键和哈希+范围键之间的区别:
DynamoDB:何时使用什么PK类型?
您的情况是典型的时间序列数据场景,在这种情况下,随着时间的推移,您的记录将变得过时。有两个主要因素需要注意:
如果您将所有通知放在一个表中,并且最近的通知被更频繁地访问,那么您的预配置吞吐量将无法有效使用。
您应该将最常访问的项目分组到一个表中,以便可以为所需的访问适当地调整预配置吞吐量。此外,请确保您正确定义哈希键,以使数据在多个分区中分布均匀。
- 以最高效(努力、性能和成本方面)的方式删除过时数据
文档建议将数据分割成不同的表,以便在记录变得过时时可以删除或备份整个表(有关更多详细信息,请参见下面的文档)。
以下是文档中解释与时间序列数据相关的最佳实践的部分:
了解时间序列数据的访问模式
对于每个创建的表,您需要指定吞吐量要求。 DynamoDB 分配并保留资源来处理您的吞吐量要求,并实现持续低延迟。在设计应用程序和表时,您应考虑应用程序的访问模式,以最有效地利用表的资源。
假设您设计了一个用于跟踪客户在您网站上行为的表,例如他们单击的 URL。您可以使用 Customer ID 作为哈希属性和日期/时间作为范围属性来设计表。在此应用程序中,客户数据会随着时间的推移而不断增长;但是,应用程序可能会显示出对表中所有项目的不均匀访问模式,其中最新的客户数据更相关,您的应用程序可能更频繁地访问最新的项目,并且随着时间的推移这些项目将被较少访问,最终较旧的项目很少被访问。如果这是已知的访问模式,则可以在设计表架构时考虑它。您可以使用多个表存储这些项目,而不是在单个表中存储所有项目。例如,您可以创建用于存储每月或每周数据的表。对于存储来自最近一个月或一周数据的表,其中数据访问率很高,请请求更高的吞吐量,并为存储旧数据的表降低吞吐量并节省资源。
您可以通过在一个具有较高吞吐量设置的表中存储“热”项目和在另一个具有较低吞吐量设置的表中存储“冷”项目来节省资源。您可以通过简单地删除表来删除旧项目。您还可以选择将这些表备份到其他存储选项(例如 Amazon Simple Storage Service(Amazon S3))。删除整个表比逐个删除项要高效得多,因为这样做会使写入吞吐量增加一倍,需要进行与放置操作同样多的删除操作。
来源:http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html#GuidelinesForTables.TimeSeriesDataAccessPatterns
例如,您可以按月对表进行分段:Notifications_April, Notifications_May, etc
Q: 我希望能够查询给定用户的最近 X 条通知。
A: 我建议使用Query
操作,并仅使用Hash Key
(UserId
)进行查询,Range Key
用于按时间戳
(日期和时间)对通知进行排序。
Hash Key: UserId
Range Key: Timestamp
注意: 更好的解决方案是在Hash Key
中不仅包含UserId
,而且还包含另一个可以在查询前计算的连接信息,以确保您的Hash Key
授予您对数据的访问权限。例如,如果特定用户的通知比其他用户更受欢迎,则可以开始使用热分区...在Hash Key
中添加其他信息将减轻这种风险。
问: 我想获取特定用户未读通知的数量。
答: 创建一个稀疏索引作为全局二级索引(GSI)
,将UserId
作为Hash Key
,将Unread
作为Range Key
。
例如:
Index Name: Notifications_April_Unread
Hash Key: UserId
Range Key : Unuread
当您通过哈希键(UserId)查询此索引时,您将自动获得所有未读通知,无需扫描与此情况不相关的通知。请记住,表中的原始主键会自动投影到索引中,因此如果您需要获取有关通知的更多信息,您可以始终借助这些属性在原始表上执行GetItem
或BatchGetItem
操作。
注意:您可以探索使用不同的属性而不是“未读”标志的想法,重要的是要记住,稀疏索引可以帮助您解决此用例(下面有更多详细信息)。
详细说明:
我会建立一个稀疏索引以确保您可以查询减少的数据集来进行计数。在您的案例中,您可以使用“未读”属性标记通知是否已读,并使用该属性创建稀疏索引。当用户阅读通知时,您只需从通知中删除该属性,以使其不再显示在索引中。以下是文档中清楚适用于您场景的一些指南:
利用稀疏索引的优点
对于表中的任何一项,在索引范围键属性值存在于该项时,DynamoDB将只写入相应的索引条目。如果在每个表项中都没有出现范围键属性,则称该索引为稀疏索引。
为了跟踪未完成订单,您可以在CustomerId(哈希)和IsOpen(范围)上创建索引。只有在表中定义IsOpen的订单才会出现在索引中。然后,您的应用程序可以通过查询该索引快速有效地查找仍处于打开状态的订单。例如,如果您有数千个订单,但只有少量订单是打开状态,应用程序可以查询该索引并返回每个打开订单的OrderId。与扫描整个CustomerOrders表相比,您的应用程序将执行显着较少的读取操作。
而不是将任意值写入IsOpen属性,您可以使用另一个属性,该属性将在索引中产生有用的排序顺序。为此,您可以创建OrderOpenDate属性,并将其设置为下订单的日期(并在订单被履行后删除该属性),并使用CustomerId(哈希)和OrderOpenDate(范围)架构创建OpenOrders索引。这样,当您查询索引时,将以更有用的排序顺序返回项目。
这样的查询非常高效,因为索引中的项目数量要少得多,而不是表中的项目数量。此外,您从索引中投影的表属性越少,则消耗的读取容量单位就越少。
参考文献:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForGSI.html#GuidelinesForGSI.SparseIndexes
以下是一些编程创建和删除表所需的操作的参考:
创建表
http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateTable.html
删除表
http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteTable.html