将 AWS Appsync 与 DynamoDB 结合使用时,您是否应该通过在同一表上存储"redundant copies"相关数据来对关系进行建模(非规范化)?



我最近阅读了ElasticSearch文档中的这一部分(或者更准确地说是指南)。它说,你应该尝试以预期的方式使用非关系数据库,这意味着你应该避免不同表之间的连接,因为它们的设计并不能很好地处理这些连接。这也提醒了我在DynamoDB文档中的一节中指出,大多数设计良好的DynamoDB后端只需要一个表。

让我们举一个食谱数据库为例,其中每个食谱都使用了几种配料。每种食材都可以用在许多不同的食谱中。

选项1:对我来说,在AppSync和DynamoDB中对此进行建模的明显方法是从ingredients表开始,该表中每个成分有一个项目,存储所有成分数据,ingredient id作为分区键。然后,我有另一个recipes表,该表具有分区键recipe id和一个存储阵列中所有ingredient idingredients字段。在AppSync中,我可以通过recipe id执行GetItem请求,然后用ingredients表上的BatchGetItem解析ingredients字段来查询配方。假设一个食谱平均包含10种成分,那么这意味着有11个GetItem请求发送到DynamoDB表。

选项2:我认为这是一种"类似联接"的操作,显然不是使用非关系数据库的理想方式。因此,我也可以做以下操作:对recipes表上的所有成分数据进行"冗余复制",不仅将ingredient id保存在那里,还将ingredients表中的所有其他数据保存在那里。这可能会大幅增加磁盘空间的使用量,但显然磁盘空间很便宜,只执行1个GetItem请求(而不是11个)可以提高性能。正如稍后在ElasticSearch指南中所讨论的,这也需要一些额外的工作来确保更新成分数据时的并发性。因此,当一个成分被更新时,我可能不得不使用DynamoDB流来更新recipes表中的所有数据。这将需要昂贵的扫描来查找使用更新成分的所有配方,并需要BatchWrite来更新所有这些项目。(不过,成分更新可能很少见,因此读取性能的提高可能是值得的。)

我很想听听你对的看法

  • 您会选择哪个选项,为什么
  • 第二种"更非关系的方式"似乎很痛苦,我担心随着更多级别/关系的出现(例如,如果用户可以用食谱创建菜单),当我不得不多次保存同一数据的"冗余副本"时,由此产生的复杂性可能会很快失控。我对关系数据库了解不多,但当每个数据都有其唯一的位置时,这些事情似乎要简单得多(我想这就是"规范化"的含义)
  • 方案1中的getRecipe是否真的比方案2贵11倍(性能和成本方面)?或者我误解了什么
  • 在关系数据库(例如MySQL)中,选项1会比在DynamoDB中更便宜吗?如果我理解正确的话,即使它是一个联接,它也只是11个("NoSQL预期方式")GetItem操作。这还会比1个SQL查询快吗
  • 如果我有一个非常相关的数据结构,像DynamoDB这样的非关系数据库会是一个糟糕的选择吗?或者AppSync/GraphQL是一种仍然使其成为可行选择的方法(通过允许选项1,这真的很容易构建)?我读到一些观点认为,在查询NoSQL数据库时,经常围绕缺失的联接功能工作,并且必须在应用程序端这样做,这是它不适合的主要原因。但AppSync可能是解决这个问题的一种方法。其他意见(包括DynamoDB文档)提到性能问题是您应该始终只查询一个表的主要原因

我知道这已经很晚了,但可能会对以后的人有所帮助。

从实体关系图开始,因为这将有助于确定您的选择。即使在NoSQL中,也有标准的关系建模方法。

接下来,定义您的访问模式。完成所有CRUDL操作,并确保对于每个操作,都可以访问该操作的特定数据。例如,在选项1中,配料存储在字段中的数组中:考虑一种访问模式,您可能需要删除配方中的配料。要做到这一点,您需要知道数组中项目的索引。因此,您必须获取整个数组,找到项目的索引,然后发出另一个调用来更新数组,同时考虑可能的竞争条件。

在应用程序中执行此操作虽然可能,但效率并不高。你也可以在你的解析器中对此进行编码,但相信我,尝试使用速度模板语言这样做并不值得头疼

TL;DR是对整个应用程序的实体关系图进行建模,并仔细考虑所有的访问模式。如果关系是一对多,则可以取消数据的规范化,使用复合排序键或使用辅助索引。如果多对多,你开始进入邻接列表和其他高级策略。Alex DeBrie在这里和这里都有一些很棒的资源。

最新更新