dynamodb 的数据建模,其中实体具有一对多和多对多关系

Data modelling for dynamodb where entity has one to many and many to many relationships

我是 NoSql 世界的新手。我正在使用 dynamodb 构建无服务器应用程序。在关系数据库中,当我有 3 个实体,如 post、post_likes 和 post_tags 时,我几乎没有 table 并使用连接来获取数据。但是,我想知道如何为 post 与喜欢有一对多关系,与标签有多对多关系的场景构建 NoSql 结构。

Post 型号:

user_id <string>
attachment_url <string>
description <string>
public <boolean>

喜欢模特:

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

标签型号:

name <string>

我的访问模式很少:

  1. Get all public posts
  2. Get all posts filtered by a single tag and public status
  3. Get all posts by user id
  4. Get a single post by post id

并且每次 post 应该获取标签数据,并且喜欢数据包括附加到喜欢的用户数据。 在关系数据库中,我将创建 post_tags table 并通过标签获取所有 post 。但是,我如何使用 dynamodb 做到这一点?

我正在努力弄清楚我的 table 应该是什么样子以及在 post_iduser_idtag_name 或 [ 中将什么设置为主键和排序键=17=] 这个案例的字段?

我最初的想法是构建一个 table 实体,如下所示:

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

那么这个 table 看起来像这样:

我设置了2个全局二级索引。 第一全球二级索引:

partition key set to public and sort key to post_id

第二个全球二级索引:

partition key set to user_id and sort key to post_id

这样,对于 post 的每个标签,我都会在 table 中复制 post。我想通过将 tag 作为第一个过滤器,这样我就可以有效地查询 posts 如果我需要通过以下方式查询它们一个 标签.

但是,如果我只通过 public 状态或 user_id 进行查询,我会为它们所属的每个标签获取 post 的所有副本。

或者我应该在 table、标签postslikes,如果我通过标签获取 post,我将首先执行一个查询以查找所有 post_ids标记,然后执行第二个查询以获取 posts 和他们的 likes id,然后执行第三个查询以获取 喜欢数组。 我不知道这方面的最佳实践是什么,因为我才刚刚开始使用 dynamodb.

那么这个数据库结构应该是什么样子的?

通过深入思考您的访问模式和定义您的实体(Posts、用户、喜欢等),您有了一个良好的开端。如您所知,透彻了解您的访问模式对于将数据存储在 DynamoDB 中至关重要

在查看我的回答时,请记住这只是 一个 解决方案。 DynamoDB 在定义数据模型时为您提供了极大的灵活性,这既是福也是祸!这个答案并不意味着 对这些访问模式进行建模的方式。相反,它是实现这些访问模式的 一种 方式。让我们开始吧!

我想首先列出我们需要建模的实体,以及每个实体的主键。在整个 post 中,我将使用复合主键,这些键由分区键 (PK) 排序键 (SK) 组成。让我们从空白 table 开始,边走边填。

         Partition Key             Sort Key
User
Post
Tag

用户

用户是您应用程序的核心,所以我将从这里开始。

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

这支持以下访问模式(为简单起见,使用伪代码示例):

  1. 通过 ID 获取用户
ddbClient.query(PK = USER#1, SK = USER#1)

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

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

Posts

我将通过关注用户和他们的 Post 之间的 one-to-many 关系来开始建模 Post。

您有一个通过 UserId 获取所有 Post 的访问模式,因此我将从将 Post 模型添加到用户分区开始。我将通过定义 USER#<user_id> 的 PK 和 POST#<post_id>.

的 SK 来完成此操作

这支持以下访问模式:

  1. 获取用户和所有 Posts
ddbClient.query(PK = USER#<user_id>)
  1. 获取用户 Posts
ddbClient.query(PK = USER#<user_id>, SK begins_with "POST#")

您可能想知道 odd-looking Post ID。获取 Post 时,您可能希望首先获取最新的 Post。您还希望能够通过 ID 唯一标识 Post。当您有这种需求时,您可以使用 KSUID 作为您的唯一标识符。解释 KSUID 有点超出您的问题范围,但要知道它们在创建时是独一无二的 and sortable。由于 DynamoDB 按排序键对结果进行排序,因此您对用户 post 的查询将自动按创建日期排序!

正在为您的应用程序更新 PK/SK 模式,我们现在有

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

标签

关于如何为 Post 和标签之间的 one-to-many 关系建模,我们有几个选项。您可以在 Post 项目上包含一个 list 属性,它只列出项目上的标签数。这种方法非常好。但是,看看您的其他访问模式,我现在将采用不同的方法(稍后就会明白为什么)。

我将使用 POST#<post_id> 的 PK 和 TAG#<tag_name>

的 SK 为标签建模

由于主键是唯一的,以这种方式建模标签将确保 Post 不会被同一个标签标记两次。此外,它允许我们在 Post.

上拥有无限数量的标签

正在更新我们的 PK/SK table 标签,我们有

         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>

此时我们已经为用户、Post 和标签建模。但是,我们只解决了四种访问模式中的一种。让我们看看我们如何使用二级索引来支持您的访问模式。

注意:您也可以用完全相同的方式建模 Likes

定义二级索引

二级索引允许您支持数据中的其他访问模式。让我们定义一个非常简单的二级索引,看看它如何支持您的各种访问模式。

我将创建一个二级索引来交换基础 table 中的 PK/SK 模式。这种模式称为 inverted index,看起来像这样:

我们在这里所做的就是交换您的基础 table 的 PK/SK 模式,这使我们能够访问两个额外的访问模式:

  1. 通过 ID 获取 Post
ddbClient.query(IndexName = InvertedIndex, PK = POST#<post_id>)
  1. 通过标签获取 Posts
ddbClient.query(IndexName = InvertedIndex, PK = TAG#<tag_name>)

通过 Public/Private 状态

获取全部 Post

您想按 public/private 状态获取 posts,以及获取 all Posts。获取 all Post 的一种方法是将它们放在一个分区中。我们可以把 public/private status 放在 sort key 中,把 public 和 private Post 分开。

为此,我将在 Post 项目上创建两个新属性:_typepublicPostId。这些字段将作为我调用的二级索引的 PK/SK 模式 PostByStatus.

这样做之后,你的基地 table 看起来像他的:

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

此二级索引将启用以下访问模式

  1. 获取所有 Posts
ddbClient.query(IndexName = PostByStatus, PK = POST)
  1. 获取所有私有 Posts
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PRIVATE#")
  1. 获取全部PublicPosts
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PUBLIC#")

请记住,post ID 是 KSUID,因此它们自然会按照 Post 创建日期在您的结果中排序。

关于热分区的一句话

将所有 Post 存储在单个分区中可能会导致 hot partition 随着应用程序的扩展。解决此问题的一种方法是将 Post 项目分布到多个分区。如何做到这一点完全取决于您并具体到您的应用程序。

避免单个 POST 分区的一种策略可能涉及通过创建 day/week/month/etc 对 Post 进行分组。例如,在 PostByStatus 二级索引中不使用 POST 作为主键,您可以使用 POSTS#<month>-<year>,如下所示:

您的应用程序在获取 Post 时需要考虑这种模式(例如,从当月开始并向后返回直到获取足够的结果),但您会将负载分散到多个分区。

总结

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