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

Lef*_*eff 6 nosql amazon-dynamodb

我是 NoSql 世界的新手。我正在使用 dynamodb 构建一个无服务器应用程序。在关系数据库中,当我有 3 个实体(例如 post、post_likes 和 post_tags)时,我将只有很少的表并使用联接来获取数据。但是,我想知道应该如何为帖子与点赞具有一对多关系以及与标签具有多对多关系的场景创建 NoSql 结构。

帖子型号:

user_id <string>
attachment_url <string>
description <string>
public <boolean>
Run Code Online (Sandbox Code Playgroud)

喜欢型号:

user_id <string>
post_id <string>
type <string>
Run Code Online (Sandbox Code Playgroud)

标签型号:

name <string>
Run Code Online (Sandbox Code Playgroud)

我的访问模式很少:

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

每次发布帖子时都应使用标签数据和点赞数据(包括附加到点赞的用户数据)来获取。在关系数据库中,我将创建 post_tags 表并按标签获取所有帖子。但是,我该如何使用 dynamodb 来做到这一点?

我正在努力弄清楚我的表应该是什么样子,以及在这种情况下将什么设置为 、 或 字段post_id之间的user_id主键和排序键?tag_namepublic

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

Partition key | Sort key | data attributes 
tag_name      | post_id  | public | user_id | likes[] | other post attributes...
Run Code Online (Sandbox Code Playgroud)

那么这张表看起来会是这样的:

在此输入图像描述

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

分区键设置为 public,排序键设置为 post_id

第二全球二级指数:

分区键设置为 user_id,排序键设置为 post_id

这样,对于帖子所具有的每个标签,我都会在表中拥有该帖子的副本。我认为通过将标签作为第一个过滤器,这样如果我需要通过标签查询帖子,我就可以有效地查询帖子

在此输入图像描述

但是,如果我仅通过公共状态或user_id进行查询,我将获得它们所属的每个标签的所有重复帖子。

在此输入图像描述

在此输入图像描述

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

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

Set*_*gan 12

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

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

我喜欢首先列出我们需要建模的实体以及每个实体的主键。在这篇文章中,我将使用复合主键,它们是由分区键 (PK)和排序键 (SK)组成的键。让我们从一张空白表格开始,然后填写它。

         Partition Key             Sort Key
User
Post
Tag

Run Code Online (Sandbox Code Playgroud)

用户

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

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

用户实体

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

  1. 通过ID获取用户
ddbClient.query(PK = USER#1, SK = USER#1)
Run Code Online (Sandbox Code Playgroud)

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

         Partition Key             Sort Key
User     USER#<user_id>           USER#<user_id>
Post
Tag
Run Code Online (Sandbox Code Playgroud)

帖子

我将通过关注用户及其帖子之间的一对多关系来开始对帖子进行建模。

您有一个按 UserId 获取所有帖子的访问模式,因此我首先将 Post 模型添加到 User 分区。我将通过定义 的 PKUSER#<user_id>和 SK来完成此操作POST#<post_id>

用户和帖子

这支持以下访问模式:

  1. 获取用户和所有帖子
ddbClient.query(PK = USER#<user_id>)
Run Code Online (Sandbox Code Playgroud)
  1. 获取用户帖子
ddbClient.query(PK = USER#<user_id>, SK begins_with "POST#")
Run Code Online (Sandbox Code Playgroud)

您可能对那些看起来奇怪的帖子 ID 感到好奇。获取帖子时,您可能希望首先获取最新的帖子。您还希望能够通过 ID 唯一地标识帖子。当您有此类需求时,您可以使用KSUID作为您的唯一标识符。解释 KSUID 有点超出了您的问题范围,但要知道它们在创建时是唯一的且可排序的。由于 DynamoDB 按排序键对结果进行排序,因此您对用户帖子的查询将自动按创建日期排序!

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

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

Run Code Online (Sandbox Code Playgroud)

标签

关于如何对帖子和标签之间的一对多关系进行建模,我们有几种选择。您可以在帖子项目上包含一个list属性,该属性仅列出项目上的标签数量。这个方法完全没问题。但是,看看您的其他访问模式,我现在将采取不同的方法(稍后会明白原因)。

我将对 PKPOST#<post_id>和 SK 的标签进行建模TAG#<tag_name>

帖子标签

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

更新标签的 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>

Run Code Online (Sandbox Code Playgroud)

至此,我们已经对用户、帖子和标签进行了建模。但是,我们仅解决了四种访问模式中的一种。让我们看看如何使用二级索引来支持您的访问模式。

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

定义二级索引

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

我将创建一个二级索引来交换基表中的 PK/SK 模式。这种模式称为倒排索引,如下所示:

倒排二级索引

我们在这里所做的只是交换基表的 PK/SK 模式,这使我们能够访问两种额外的访问模式:

  1. 通过 ID 获取帖子
ddbClient.query(IndexName = InvertedIndex, PK = POST#<post_id>)
Run Code Online (Sandbox Code Playgroud)
  1. 按标签获取帖子
ddbClient.query(IndexName = InvertedIndex, PK = TAG#<tag_name>)
Run Code Online (Sandbox Code Playgroud)

按公共/私人状态获取所有帖子

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

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

完成此操作后,您的基表将如下所示:

新帖子属性

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

按公开状态分类的帖子

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

  1. 获取所有帖子
ddbClient.query(IndexName = PostByStatus, PK = POST)
Run Code Online (Sandbox Code Playgroud)
  1. 获取所有私人帖子
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PRIVATE#")
Run Code Online (Sandbox Code Playgroud)
  1. 获取所有公开帖子
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PUBLIC#")
Run Code Online (Sandbox Code Playgroud)

请记住,帖子 ID 是 KSUID,因此它们自然会按帖子发布日期在结果中排​​序。

关于热分区的一句话

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

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

避免热分区

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

包起来

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