使用DynamoDB进行数据建模,其中实体具有一对多和多对多的关系

6
我是一名新手,对NoSql的世界并不熟悉。我正在使用dynamodb构建一个无服务器应用程序。在关系型数据库中,当我有3个实体,如post、post_likes和post_tags时,我会有几个表,并使用连接来获取数据。但是,我想知道,在post与likes存在一对多关系,而与tags存在多对多关系的情况下,应该如何制定NoSql结构。 Post模型:
user_id <string>
attachment_url <string>
description <string>
public <boolean>

模型:

user_id <string>
post_id <string>
type <string>

标签模型:

name <string>

我有几个访问模式:

  1. 获取所有公共帖子
  2. 获取所有按单个标签和公共状态过滤的帖子
  3. 按用户ID获取所有帖子
  4. 按帖子ID获取单个帖子

每次都需要获取带有标签数据和包含附加在喜欢中的用户数据的喜欢数据的帖子。在关系型数据库中,我会创建一个post_tags表,并通过标签获取所有帖子。但是,在dynamodb中该怎么做呢?

我正在努力弄清楚我的表应该长什么样子,以及在这种情况下应将哪些字段设置为主键和排序键,例如post_iduser_idtag_namepublic字段?

我的最初想法是构建一个实体类似于这样的表:

Partition key | Sort key | data attributes 
tag_name      | post_id  | public | user_id | likes[] | other post attributes...

那么这个表格看起来会像这样:

enter image description here

我已设置了2个全局次要索引。 第一个全局次要索引:
分区键设置为public,排序键设置为post_id
第二个全局次要索引:
分区键设置为user_id,排序键设置为post_id
这样每个标签下的帖子,在表中都会有一个副本。我认为通过将标签作为第一个过滤器,可以有效地查询帖子,如果需要按标签查询它们。

enter image description here

但是,如果我只按照公共状态或用户 ID进行查询,我将会获得每个标签下的所有重复帖子。

enter image description here

enter image description here

或者我应该在表中有3个单独的实体,tagspostslikes,如果我通过标签获取一个帖子,我首先会查询所有标签下的post_ids,然后进行第二次查询来获取帖子及其likes id,最后进行第三次查询以获取likes数组。我不知道在这方面什么是最佳实践,因为我刚开始使用dynamodb

那么这个数据库结构应该如何呈现?


你已经尝试了哪些主键?我可能错了,但是你关于索引的问题暗示着你正在尝试在DynamoDb中创建类似SQL的索引。DynamoDB确实有“二级索引”的概念,但它与在SQL数据库中找到的索引没有任何关系。 - Seth Geoghegan
我还没有做任何事情。而且我在问题上可能没有表述清楚,我只是不确定为这种情况设置什么哈希和排序键,或者通常如何构建数据库结构。 - Leff
你的第四个访问模式是“获取单篇文章”。你是通过帖子ID、用户ID、两者还是其他方式获取单篇文章?你还提到建模“点赞”实体,但在任何访问模式中都没有描述如何使用它。你计划如何使用“点赞”信息?例如,帖子是否有一个点赞计数?您需要跟踪哪个用户点赞了哪个帖子吗?您可以详细说明应用程序如何使用此信息吗?您是否需要其他访问模式,例如“获取每个帖子的喜欢”或“获取用户喜欢的帖子”? - Seth Geoghegan
1
另外,您能详细说明标签的使用吗?您是想通过单个标签或任意数量的标签来获取帖子吗?在DynamoDB中解决标记问题可能很困难,最好在DDB之外解决。了解您计划如何使用标签可以帮助确定它是否适合DDB。例如,列出特定帖子上的标签很简单。获取所有带有单个标签的帖子也很简单。获取带有任意标签列表的所有帖子则更加困难。 - Seth Geoghegan
对于单个帖子,我考虑使用帖子ID来获取它,这就足够了。对于点赞,将使用与其关联的Likes数组来获取帖子,每个点赞都会有用户信息。至于标签,我考虑只使用一个标签,并获取所有具有该标签的帖子。 - Leff
1个回答

12
你已经很好地开始思考访问模式并定义实体(帖子、用户、点赞等)。正如你所知,深入了解访问模式对于在DynamoDB中存储数据至关重要。
在审查我的答案时,请记住这只是一种解决方案。 DynamoDB在定义数据模型时提供了很大的灵活性,这既是福音也是诅咒!这个答案并不意味着是建模这些访问模式的方法。相反,这是一种实现这些访问模式的方式。让我们开始吧!
我喜欢先列出我们需要建模的实体以及每个实体的主键。在本文中,我将使用复合主键,即由分区键(PK)和排序键(SK)组成的键。让我们从一个空表开始,并随着进展填写它。
         Partition Key             Sort Key
User
Post
Tag

用户

用户是您的应用程序的核心,因此我会从这里开始。

让我们首先定义一个用户模型,使我们可以通过ID识别用户。我将使用模式USER#<user_id>作为用户实体的PK和SK。

User entities

这支持以下访问模式(为简单起见,示例采用伪代码):
  1. 通过ID获取用户
ddbClient.query(PK = USER#1, SK = USER#1)

我将使用新的PK/SK模式更新用户表

         Partition Key             Sort Key
User     USER#<user_id>           USER#<user_id>
Post
Tag

文章

我将通过关注用户和他们的文章之间的一对多关系来开始建模文章。

您可以通过UserId获取所有文章的访问模式,因此我将从将文章模型添加到用户分区开始。 我将通过定义PK为USER#<user_id>和SK为POST#<post_id>来实现此目的。

Users and Posts

这支持以下访问模式:
1. 获取用户和所有帖子
ddbClient.query(PK = USER#<user_id>)
  1. 获取用户文章
ddbClient.query(PK = USER#<user_id>, SK begins_with "POST#")

您可能会对奇怪的文章ID感到困惑。在获取文章时,您可能希望首先获取最新的文章。您还希望能够通过ID唯一标识文章。当您有这种要求时,可以使用KSUID作为您的唯一标识符。解释KSUID超出了您的问题范围,但请知道它们是唯一的并且可以按创建时间排序。由于DynamoDB按Sort Key对结果进行排序,因此查询用户文章的查询将自动按创建日期排序!
更新应用程序的PK / SK模式,现在我们有:
         Partition Key             Sort Key
User     USER#<user_id>           USER#<user_id>
Post     USER#<user_id>           POST#<post_id>
Tag

标签

在模型化帖子和标签之间的一对多关系时,我们有几个选项。您可以在帖子项目上包含一个list属性,其中简单地列出了该项目上的标签数。这种方法完全没问题。但是,从您的其他访问模式来看,我现在会采取不同的方法(稍后将明显为什么)。

我将使用PK为POST#<post_id>和SK为TAG#<tag_name>来建模标签。

Post Tags

由于主键是唯一的,以这种方式对标签进行建模将确保没有帖子被标记了两次相同的标签。此外,它允许我们在帖子上拥有无限数量的标签。

更新我们的Tag PK/SK表格,我们有:

         Partition Key             Sort Key
User     USER#<user_id>           USER#<user_id>
Post     USER#<user_id>           POST#<post_id>
Tag      POST#<post_id>           TAG#<tag_name>


此时,我们已经对用户、帖子和标签进行了建模。然而,我们只解决了你的四个访问模式中的一个。让我们看看如何使用二级索引来支持你的访问模式。
注意:你也可以用完全相同的方式对“赞”进行建模。
定义二级索引
二级索引允许你在数据中支持其他访问模式。让我们定义一个非常简单的二级索引,并看看它如何支持你的各种访问模式。
我将创建一个交换基本表中PK/SK模式的二级索引。这种模式被称为倒排索引,它看起来像这样:

Inverted Secondary Index

我们所做的只是交换了基本表的PK / SK模式,这使我们可以访问两个额外的访问模式:
  1. 按ID获取帖子
ddbClient.query(IndexName = InvertedIndex, PK = POST#<post_id>)
  1. 通过标签获取文章
ddbClient.query(IndexName = InvertedIndex, PK = TAG#<tag_name>)

获取所有公开/私有状态的帖子

您想通过公开/私有状态获取帖子,以及获取所有帖子。将所有帖子放在一个分区中是获取所有帖子的一种方法。我们可以将公开/私有状态放在排序键中,以区分公开和私有帖子。

为此,我将在帖子项上创建两个新属性:_typepublicPostId。这些字段将作为我称之为PostByStatus的辅助索引的PK/SK模式。

完成后,您的基本表格将如下所示:

new Post attributes

你的新二级索引将如下所示

Posts by public status

这个次要索引将使以下访问模式成为可能:

  1. 获取所有帖子
ddbClient.query(IndexName = PostByStatus, PK = POST)
  1. 获取所有私有文章
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PRIVATE#")
  1. 获取所有公共帖子
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PUBLIC#")

请记住,帖子ID是KSUID,因此它们将按照帖子创建日期自然排序。

关于热分区的说明

将所有帖子存储在单个分区中可能会导致热分区问题随着应用程序的扩展而出现。解决这个问题的一种方法是将Post项目分布在多个分区中。如何做取决于您的应用程序。

避免单个POST分区的一种策略可能涉及按创建日/周/月等对帖子进行分组。例如,您可以在PostByStatus二级索引中使用POSTS#<month>-<year>代替POST作为PK,如下所示:

Avoiding hot partitions

当获取帖子时,您的应用程序需要考虑这种模式(例如从当前月份开始向后倒退,直到获取足够的结果),但您将会在多个分区上平均负载。

总结

我希望这个练习能给你一些关于如何建模数据以支持特定访问模式的想法。在DynamoDB中进行数据建模需要时间来做好,并且可能需要多次迭代才能适用于您的特定应用程序。这可能是一个陡峭的学习曲线,但回报是一个解决方案,为您的应用程序带来规模和速度。


非常感谢您的回答和精彩的解释! - Leff

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