如何通过日期(范围键)查询DynamoDB,而没有明显的哈希键?

52
我需要将iOS应用程序上的本地数据与DynamoDB表中的数据同步。 DynamoDB表约为2K行,仅具有哈希键(id),并具有以下属性:
  • id(uuid)
  • lastModifiedAt(时间戳)
  • name
  • latitude
  • longitude
我目前正在通过lastModifiedAt进行扫描和过滤,其中lastModifiedAt大于应用程序上次刷新日期,但我想这会变得很昂贵。
我能找到的最好的答案是使用lastModifiedAt作为范围添加全局二级索引,但是GSI没有明显的哈希键。
在需要使用GSI查询范围但没有明显哈希键时,最佳做法是什么? 或者,如果必须进行完整扫描,是否有任何降低成本的最佳实践?
3个回答

44
尽管全局二级索引似乎符合您的要求,但是将时间戳相关信息作为哈希键的一部分会很可能创建所谓的“热分区”,这是极其不可取的。
访问不平衡将发生在最近的项目将以比旧项目更频繁的方式检索。这不仅会影响您的性能,还会使您的解决方案变得不太划算。
从文档中查看一些详细信息:
例如,如果表具有非常少量的大量访问的分区键值,甚至可能只有一个非常频繁使用的分区键值,则请求流量集中在少量分区上-可能仅有一个分区。如果工作负载严重不平衡,意味着它过度集中在一个或少数几个分区上,则请求将无法达到总体预配吞吐量水平。为了充分利用DynamoDB吞吐量,请创建具有大量不同值的分区键,并且请求的值相对均匀地请求,尽可能随机。
根据所述,id 确实似乎是您的 Hash Key(又称 Partition Key)的不错选择,因为 GSI 键在分区方面的工作方式与之相同。另外,当您通过提供整个 Primary Key 来检索数据时,性能会得到高度优化,因此我们应该尽可能找到提供该解决方案的方法。
我建议创建单独的表来存储基于最近更新时间的主键。您可以根据最适合您用例的细粒度将数据分段到表中。例如,假设您想按天分段更新:
a. 您的每日更新可以存储在具有以下命名约定的表中:updates_DDMM b. updates_DDMM 表仅包含 id(其他表的哈希键)
现在假设最新的应用程序刷新日期是 2 天前(04/07/16),您需要获取最近的记录,则需要:

i. 扫描表格 updates_0504updates_0604,以获取所有哈希键。

ii. 最后,通过提交一个带有所有获取的哈希键的 BatchGetItem,从包含纬度/经度、名称等信息的主表中获取记录。

BatchGetItem 超级快速,可以像其他操作一样完成任务。

有人可能会认为创建额外的表格会增加整体解决方案的成本... 嗯,使用 GSI 本质上是复制您的表格(如果您正在投影所有字段),并为所有 ~2k 条记录添加额外的成本,无论它们是否最近更新过...

创建这样的表格似乎是违反直觉的,但实际上,当处理时间序列数据时,这是最佳实践(来自 AWS DynamoDB 文档):

[...] 应用程序可能会在表中所有项目上显示不均匀的访问模式,其中最新的客户数据更为相关,您的应用程序可能更频繁地访问最新的项目,并且随着时间的推移,这些项目被访问的次数越来越少,最终较旧的项目很少被访问。如果这是已知的访问模式,则在设计表模式时可以考虑它。您可以使用多个表来存储这些项目,而不是将所有项目存储在单个表中。例如,您可以创建用于存储每月或每周数据的表。对于存储来自最新月份或周的数据的表,其中数据访问率很高,请请求更高的吞吐量;对于存储较旧的数据的表,您可以降低吞吐量并节省资源。

通过将“热门”项目存储在具有较高吞吐量设置的一个表中,将“冷门”项目存储在具有较低吞吐量设置的另一个表中,您可以节省资源。您可以通过简单地删除表来删除旧项目。您还可以选择将这些表备份到其他存储选项,例如 Amazon Simple Storage Service(Amazon S3)。删除整个表比逐个删除项目要高效得多,因为您需要进行与放置操作相同数量的删除操作,从而将写入吞吐量翻倍。

Source: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html

我希望这有所帮助。敬礼。


8
这是一个很好的建议。谢谢你的周到考虑。这让我想到,考虑到获取基于时间数据的复杂性,也许使用 RDS 是更好的解决方案。 - James Skidmore
6
请注意,BatchGetItem的限制是100个项目。 - David Kobia
4
这是一个很好的评论,但是关于“热分区”的问题已经过时了,因为DynamoDB自适应容量可以适应不均匀的数据访问模式。请注意,还有针对时间序列数据的DynamoDB设计模式,其中PK是时间戳。 - kert
显而易见的是,DynamoDB 是为 OLTO 和 OLAP 设计的。https://www.alexdebrie.com/posts/dynamodb-single-table/#the-difficulty-of-analytics - user1912383

21

虽然D.Shawley的回答帮助我找到了正确的方向,但有两个对于GSI需要考虑的问题被忽略了:

  1. 哈希+范围需要保证唯一性,但是日期+时间戳(他推荐的方法)不一定是唯一的。
  2. 如果只使用以天为哈希值,则需要使用大量的查询来获取自上次刷新日期(可能是数月或数年前)以来每一天的结果。

因此,这是我采用的方法:

  • 创建一个全局二级索引(GSI),哈希键为YearMonth(例如,201508),范围键为id
  • 多次查询GSI,每次查询自上次刷新日期以来的一个月。此外,查询还要过滤条件为lastModifiedAt > [给定的时间戳]

1
请查看我的答案以获取其他考虑事项。谢谢。 - b-s-d
8
我和你的情况相同,并且采取了相同的解决方案。感谢你在这里发帖。一个提醒:GSI不需要是唯一的:http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForGSI.html - ustroetz

12

您可以使用时间戳的“day”部分作为哈希,并将完整时间戳用作范围。


2
由于哈希键需要一个 eq 条件,那么我是否需要为自上次应用程序刷新以来过去的每一天执行一次查询?(应用程序在本地存储了一个 lastRefreshedAt 时间戳)。看起来这比扫描更昂贵。 - James Skidmore
2
也许我可以将时间戳的“年-月”部分存储为哈希键?这将显着减少在某人在一年内首次打开应用程序时所需的查询数量,与使用“日”部分相比。此时,范围键似乎也变得无关紧要,因为通过哈希键查询将带入自lastRefreshedAt以来更新的所有项目。 - James Skidmore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接