你已经很好地开始思考访问模式并定义实体(帖子、用户、点赞等)。正如你所知,深入了解访问模式对于在DynamoDB中存储数据至关重要。
在审查我的答案时,请记住这只是一种解决方案。 DynamoDB在定义数据模型时提供了很大的灵活性,这既是福音也是诅咒!这个答案并不意味着是建模这些访问模式的方法。相反,这是一种实现这些访问模式的方式。让我们开始吧!
我喜欢先列出我们需要建模的实体以及每个实体的主键。在本文中,我将使用复合主键,即由分区键(PK)和排序键(SK)组成的键。让我们从一个空表开始,并随着进展填写它。
Partition Key Sort Key
User
Post
Tag
用户
用户是您的应用程序的核心,因此我会从这里开始。
让我们首先定义一个用户模型,使我们可以通过ID识别用户。我将使用模式USER#<user_id>
作为用户实体的PK和SK。
![User entities](https://istack.dev59.com/pHjyL.webp)
这支持以下访问模式(为简单起见,示例采用伪代码):
- 通过ID获取用户
ddbClient.query(PK = USER
我将使用新的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](https://istack.dev59.com/r7DoV.webp)
这支持以下访问模式:
1. 获取用户和所有帖子
ddbClient.query(PK = USER
- 获取用户文章
ddbClient.query(PK = USER
您可能会对奇怪的文章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](https://istack.dev59.com/65HMd.webp)
由于主键是唯一的,以这种方式对标签进行建模将确保没有帖子被标记了两次相同的标签。此外,它允许我们在帖子上拥有无限数量的标签。
更新我们的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](https://istack.dev59.com/2j8CG.webp)
我们所做的只是交换了基本表的PK / SK模式,这使我们可以访问两个额外的访问模式:
- 按ID获取帖子
ddbClient.query(IndexName = InvertedIndex, PK = POST
- 通过标签获取文章
ddbClient.query(IndexName = InvertedIndex, PK = TAG
获取所有公开/私有状态的帖子
您想通过公开/私有状态获取帖子,以及获取所有帖子。将所有帖子放在一个分区中是获取所有帖子的一种方法。我们可以将公开/私有状态放在排序键中,以区分公开和私有帖子。
为此,我将在帖子项上创建两个新属性:_type
和publicPostId
。这些字段将作为我称之为PostByStatus
的辅助索引的PK/SK模式。
完成后,您的基本表格将如下所示:
![new Post attributes](https://istack.dev59.com/ORnsi.webp)
你的新二级索引将如下所示
![Posts by public status](https://istack.dev59.com/FO2uj.webp)
这个次要索引将使以下访问模式成为可能:
- 获取所有帖子
ddbClient.query(IndexName = PostByStatus, PK = POST)
- 获取所有私有文章
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PRIVATE#")
- 获取所有公共帖子
ddbClient.query(IndexName = PostByStatus, PK = POST, SK begins_with "PUBLIC#")
请记住,帖子ID是KSUID,因此它们将按照帖子创建日期自然排序。
关于热分区的说明
将所有帖子存储在单个分区中可能会导致热分区问题随着应用程序的扩展而出现。解决这个问题的一种方法是将Post项目分布在多个分区中。如何做取决于您的应用程序。
避免单个POST
分区的一种策略可能涉及按创建日/周/月等对帖子进行分组。例如,您可以在PostByStatus
二级索引中使用POSTS#<month>-<year>
代替POST
作为PK,如下所示:
![Avoiding hot partitions](https://istack.dev59.com/y9nHG.webp)
当获取帖子时,您的应用程序需要考虑这种模式(例如从当前月份开始向后倒退,直到获取足够的结果),但您将会在多个分区上平均负载。
总结
我希望这个练习能给你一些关于如何建模数据以支持特定访问模式的想法。在DynamoDB中进行数据建模需要时间来做好,并且可能需要多次迭代才能适用于您的特定应用程序。这可能是一个陡峭的学习曲线,但回报是一个解决方案,为您的应用程序带来规模和速度。