DynamoDB邻接表主键

6
我正在使用DynamoDB来建模一个多对多的关系,需要将帖子和标签之间建立多对多的关联。每个帖子可以有多个标签,每个标签也可以有多个帖子。
我在id上添加了主键,并在type上添加了主排序键,然后又在iddata上添加了另一个全局索引。我又添加了一个全局索引,这次是在idtype上,但我认为这是多余的。
以下是我的进展情况。
id(Partition key)      type(Sort Key)       target       data
-------------          ----------           ------       ------
1                      post                 1            cool post
tag                    tag                  tag          n/a
1                      tag                  tag          orange

---------------------------------------------
---- inserting another tag will overwrite ---
---------------------------------------------

1                      tag                  tag          green

我正在参考这个很棒的讲座 https://www.youtube.com/watch?v=jzeKPKpucS0 和这些不太好的文档 https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html

我的问题在于,如果我尝试添加另一个带有id“1”和type“tag”的标记,它将覆盖现有标记,因为它会具有相同的复合键。我错过了什么?似乎建议是使主键和排序键成为idtype。我的类型应该更像“tag#orange”吗?在这种情况下,我可以在target上放置一个全局索引,并在类型上放置一个排序键。这样,我就可以通过查询目标=“tag”并以“tag”开头的类型获取所有带有特定标记的帖子。

只是想寻求一些关于使用Dynamo处理这种邻接列表数据的建议,因为它似乎非常有趣。谢谢!


我有点困惑。标签是有整数ID还是仅帖子有?您需要将标签保存为{"id": 1, "type": "tag", "data": "this is a tag"}还是{"id": "this is a tag", "type": "tag", "data": "this is a tag"} - Renato Byrro
标签也可以有整数ID,但为了清晰起见,我选择使用字符串。 - Mike
2个回答

20

邻接表的基本指南

您需要对建模方式进行一些修改。在邻接表中,有两种类型的项:

  1. 顶级(这些是您的 帖子标签
  2. 关联(表示哪些 标签 与每个 帖子 相关联,反之亦然)

要构建此邻接表,您必须遵循两个简单的准则(我认为这些准则在您的示例中缺失):

  • 每个顶级项(在您的情况下为 帖子标签)都必须使用主键来表示。此外,这些项在排序键和主键中应具有相同的值
  • 对于关联,使用主键表示源(或父项),使用排序键表示目标(或子项)。

从您的示例中可以看出,您将 帖子标签 的主键设置为仅为项目 ID,而实际上您还应该使用其类型;例如,Post-1Tag-3。在表示关联的项目中,我也没有看到您存储目标 ID

示例

假设您有:

  • 三个帖子:"hello world""foo bar""Whatever..."
  • 三个标签:"cool""awesome""great"
  • 帖子 "hello world" 有一个标签:"cool"
  • 帖子 "foo bar" 有两个标签:"cool""great"
  • 帖子 "Whatever..." 没有任何标签

您需要在 Dynamo 中以此方式进行建模:

PRIMARY-KEY   | SORT-KEY    | SOURCE DATA  | TARGET DATA
--------------|-------------|--------------|-------------
Post-1        | Post-1      | hello world  |
Post-2        | Post-2      | foo bar      |
Post-3        | Post-3      | Whatever...  |
Tag-1         | Tag-1       | cool         |
Tag-2         | Tag-2       | awesome      |
Tag-3         | Tag-3       | great        |
Post-1        | Tag-1       | hello world  | cool
Post-2        | Tag-1       | foo bar      | cool
Post-2        | Tag-3       | foo bar      | great
Tag-1         | Post-1      | cool         | hello world
Tag-1         | Post-2      | cool         | foo bar
Tag-3         | Post-2      | great        | foo bar

如何查询这个邻接表

1) 需要获取特定的项,例如 Post-1:

查询 primary-key == "Post-1" & sort-key == "Post-1" - 返回: 只有 Post-1

2) 需要获取与Post-2相关的所有标签:

按照 primary-key == "Post-2" & sort-key BEGINS_WITH "Tag-" 进行查询 - 返回:Tag-1Tag-3 的关联。

查看文档,了解以 begin_with 关键条件表达式开始的信息.

3) 需要获取与特定标签(例如Tag-1)关联的所有帖子:

按照 primary_key == "Tag-1" & sort-key BEGINS_WITH "Post-" 进行查询 - 返回: Post-1Post-2 关联。

请注意,如果更改给定帖子的内容,则还需要更改所有关联项中的值。

您也可以不要在关联项中存储帖子和标签内容,这可以节省存储空间。但是,在此情况下,您需要两个查询来执行上面的示例查询2和3:一个用于检索关联,另一个用于检索每个源项数据。由于查询比存储数据更昂贵,因此我更喜欢复制存储。但这真的取决于您的应用程序是读密集还是写密集。如果是读密集,将内容复制到关联中可以减少读取查询的数量。如果是写密集,不重复内容会节省更新关联时的写入查询。

希望这有所帮助!;)


在你的例子中,如果你添加了帖子-标签关联,那么你还必须添加标签-帖子关联。难道我们不应该使用GSI吗?例如,分区键是TAG-1,排序键是POST-1... - Anshuman Singh
2
是的,您可以使用 GSI 进行操作。但是,它会以相同的方式复制数据。为了使用 GSI,我建议利用稀疏索引。只向关联项添加属性,这样顶级项就不会在 GSI 中复制。 - Renato Byrro
1
显然,您还需要调整代码以适当地从表或GSI查询。如果我们需要添加一个关联项,我认为额外添加两个并将所有内容放在单个表中的优点不会带来太多麻烦,因为这样可以简化一些事情。 - Renato Byrro
而且,如果帖子和标签是更复杂的对象,包含了与关联无关的更多数据,你是否仍然会为帖子和标签分别创建单独的表格?让这个表格只包含与关联相关的数据? - andy mccullough
我认为没有必要将它们分开放在不同的表中,除非你有不同的访问模式。例如:如果对帖子的访问是可变和不可预测的,按需表可能有助于减少限流;如果对标签的访问更加可预测,保留吞吐量将降低成本。即使是这种情况,我也会犹豫是否要将它们拆分开来。 - Renato Byrro

2
我认为你没有遗漏任何东西。这个想法是,ID对于物品的类型是唯一的。通常,你会为ID生成一个长UUID,而不是使用连续的数字。另一种选择是使用你创建该项的日期时间,可能加上一个随机数,以避免在创建项时发生冲突。
我之前提供的答案可能会有所帮助:DynamoDB M-M邻接列表设计模式。
不要移除排序键 - 这不会帮助使你的项更加唯一。

我正在参考这个图示 https://youtu.be/jzeKPKpucS0?t=2286,看起来我需要将体育标签的ID设置为1,以便知道它所附加的帖子是什么。 - Mike
看你的回答,似乎建议类型应该是“tag:sports”,以保持唯一性。我在我的问题中提出了这个建议,但不确定。 - Mike

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