【问题标题】:Many to Many with thousands of reference items具有数千个参考项目的多对多
【发布时间】:2018-05-06 06:36:59
【问题描述】:

我目前有一个 SQL Server 数据库,其中包含一个包含 400,000 部电影的表。我有另一个包含数千个用户的表。

CREATE TABLE [movie].[Header]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [SourceId] [int] NOT NULL,
    [ReleaseDate] [Date] NOT NULL,
    [Title] [nvarchar](500) NOT NULL
)

CREATE TABLE [account].[Registration]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Username] [varchar](50) NOT NULL,
    [PasswordHash] [varchar](1000) NOT NULL,
    [Email] [varchar](100) NOT NULL,
    [CreatedAt] [datetime] NOT NULL,
    [UpdatedAt] [datetime] NOT NULL
)

CREATE TABLE [movie].[Likes] 
(
    [Id] [uniqueidentifier] NOT NULL,
    [HeaderId] [int] NOT NULL,
    [UserId] [int] NOT NULL,
    [CreatedAt] [datetime] NOT NULL
)

CREATE TABLE [movie].[Dislikes]
(
    [Id] [uniqueidentifier] NOT NULL,
    [HeaderId] [int] NOT NULL,
    [UserId] [int] NOT NULL,
    [CreatedAt] [datetime] NOT NULL
)

从未来两周开始,每位用户都会看到 100 部电影。然后他们可以执行喜欢、不喜欢、推荐等操作。

我正在将整个应用程序迁移到无服务器架构中。我通过 Lambda + API Gateway 在 AWS 中运行 API,现在我正在考虑将 DynamoDB 用于数据库。我不认为我有什么超级疯狂的东西会阻止我将数据存储在 Dynamo 中,而且它们的定价/消费模型似乎比 SQL Server(目前托管在 Azure 中)便宜得多。

我遇到的一件事是了解我将如何对用户在电影中执行动作进行建模。如果他们“喜欢”一部电影,它就会进入一个喜欢列表,他们可以回去访问。在那里,我向他们展示了整个移动记录(实际上包含更多数据,例如演员/工作人员/评级等。我只是截断了电缆以简化它)。如果我将每个“Like”作为一个项目存储在 Dynamo 中,并将整个电影作为一个属性存储,我认为用户文档会变得非常大。

我还需要在两周后继续向用户展示他们没有执行任何操作的电影。他们对我执行了操作的电影需要从查询中删除。今天我只是加入电影表和用户操作表,从用户操作表中已经存在的查询中删除电影。我将如何在 NoSql 中以相同的最终结果对其进行建模?

我可以将喜欢/不喜欢合并到具有动作类型属性(表示喜欢/不喜欢等)的单个文档中,以及已执行该动作的电影数组。仍然不确定我将如何过滤 [Header] 查询,以便用户文档中的电影不会回来。

我想我会将电影哈希键设置为分片的发布日期,因为平均每个发布日期大约有 10 部电影。这给出了一个很好的分布。我想我会使用用户 ID 具有哈希键的文档,该文档包含用户执行过操作的所有电影;不知道这是否是正确的道路。

我从未处理过 NoSql,所以我想请教一下。我不确定如何最好地设计本质上是一对多的东西,但每个用户的电影可能有数万部。

【问题讨论】:

  • Dynamodb 真正是关于了解您的数据访问模式。比如,什么是最常用的查询,什么是批量写入或读取的,而不是随着时间的推移轻轻分布...例如,用户获得尚未喜欢/不喜欢的电影列表的频率是否比获得已经喜欢的动作列表的频率高得多。平均而言,每个查询要返回多少个项目? DynamoDB 主要是关于进行良好的权衡。顺便说一句,“文档”不是 dynamoDB 术语 :)
  • 简而言之,您能否详细说明什么对您来说更重要/更重。您的取舍可能是什么
  • 最常用的查询是显示用户尚未喜欢/不喜欢的电影列表的查询,以及电影的所有演员和工作人员(可以是 60 人)以及电影详细信息(评级、流派、描述等)。第二个查询是用户喜欢/不喜欢的所有电影。目前,它是对喜欢和另一个不喜欢的单独查询。不过,这似乎是我可以轻松组合的东西。喜欢/不喜欢查询还向用户展示了所有演员/工作人员和电影的详细信息。
  • 目前我们有 400,000 部电影。将其乘以演员/工作人员记录,可以尝试复制大量数据。如果我应该保留一张电影表,那么我应该保留一张用户表,其中包含代表所有喜欢/不喜欢的属性。在某些情况下,我们已经看到用户在一天之内喜欢/不喜欢 1,000 部电影,因此在应用内会产生分页需求。

标签: nosql amazon-dynamodb


【解决方案1】:

所以,根据您的 cmets,我将提出一个建议。这并不意味着它是一个正确的答案,我也可能是错的或漏掉了一点

首先,请反复阅读Best Practices 的每个部分。有些模式您可能从未想过,但使用 NoSQL 方法仍然可以实现。它非常有帮助和教育意义(考虑到你说你是 NoSQL 的新手)。与您的案例有相似之处,您可以根据最佳实践创建自己的答案。

我可以建议的是:

NoSQL 在查询“不存在”方面非常糟糕。 NoSQL 的一个大技巧是它确切地知道在哪里可以找到您要查找的数据,而不是在哪里不可以找到。因此,很难找到尚未对电影执行任何操作的用户。如果你可以使用像 Redis 这样的辅助数据库,你可以很容易地完成它。使用 Redis 数据结构,您可以查询尚未喜欢/不喜欢的用户,并从 DynamoDB 获取其余的电影数据。但是,暂时将辅助数据库 Redis 放在一边,只使用 DynamoDB 方法。

一种方法是当每部电影到达数据库(新电影)时,您可以将它们添加到每个用户,动作类型为not-actioned-yet。现在,对于所有用户,您都可以非常轻松且非常快速地查询这些内容。 (现在它知道数据在哪里;))但这是不对的,因为如果有 10.000 个用户,那么您为每部电影制作 10.000 次写入。

另一种方法可以假设您在一个表中有一个项目,该表包含用户最后一次“获取尚未执行的列表”查询的日期。现在,一段时间后,用户返回相同的查询,现在您需要读取该日期并获取该日期之后添加到您的数据库中的所有电影。使用日期时间作为排序键,您可以查询从该日期开始的电影。可以说,在用户上次查询后添加了 10 部电影(这些肯定是用户尚未采取行动)。现在您将这 10 部电影作为项目 not-actioned-yet 添加到表中。在此之后,您将拥有所有用户尚未操作的电影。 'not-actioned-yet' 的类型也类似于 'like, disliked'。从现在开始,您可以轻松查询它们。

示例表结构:

您可以使用sparse indexestime series table approach 将新电影(在接下来的两周内)与其他电影区分开来。这样您就可以有效地查询或扫描它们。在这里使用稀疏索引

电影表

| Id (Hash Key|Primary Key) | StartingDateUnix(GSI SK) | IsIn2Weeks (GSI) |
|:-------------------------:|-------------------------:|:----------------:|
| MovieId1                  |        1234567           |     1     
| MovieId2                  |        1234568           |     1    
| MovieId3                  |        001123            |     null     

要在 unix 1234567 之后获取电影,您必须使用大于 unix 时间的排序键查询 GSI。

用户操作表

| UserId (Hash Key) | ActionType_ForMovie(Sort Key) | CreatedAt (LSI) |
|:-----------------:|:-----------------------------:|:---------------:|
| UserId1           |       no-action::MovieId1     |      1234567    |
| UserId1           |       no-action::MovieId2     |      1234568    |   
| UserId1           |       like::MovieId3          |      1234569    | 
| UserId1           |       like::MovieId4          |      1234561    |     
| UserId1           |       dislike::MovieId5       |      1234562    |   

使用排序键,您可以查询尚未执行的所有喜欢不喜欢...并且您可以按日期对它们进行排序。也可以分页。

我在这个问题上花了一些时间,因为这对我来说也是一个很好的挑战,我希望得到反馈。希望它在某种程度上有所帮助

【讨论】:

  • 感谢您的建议。如果我要走这条路,听起来我需要为每个用户的每部电影(400,000)添加一条记录。只有 10 个用户,这将是 400 万个项目。我知道规模对于发电机来说不是一个大问题。不确定与此相关的成本,因为我必须为每件作为阅读的物品付费。当用户查看他们之前的操作列表时,我会查询所有喜欢/不喜欢的东西,这可能是很多项目并且需要非常高的大规模 RCU
  • 我曾想过有一个表,ID为哈希,开始日期为SK来存储所有电影。每部电影都会有一个数字集,其中包含每个已采取行动的用户的用户 ID。这样我就可以过滤掉该集合中不存在@Userid 的电影。然后是第二个表,其中 userid 为 hash,action-type 为 sort,一组喜欢和一组不喜欢。每个都包含他们操作过的电影 ID。缺点是我必须在执行操作时更新两个不同表上的两条记录。会有错误导致事情不同步的风险。
  • 另一种选择是将所有电影存储在 dynamodb 中,并存储所有用户操作。将该数据转储到数据湖中,并通过 dynamodb 之外的数据处理生成用户尚未执行的电影集,并将结果推送回 dynamodb 作为用户可用的下一组电影。在用户处理了 30-50% 的当前队列后,该过程可能会运行几秒钟,以确保他们的未处理电影列表永远不会用完。
  • 我现在不明白一些事情。回答您的第一条评论。您不会为每个用户添加一个条目。当该用户来到您的电影列表时,您可以添加它们。这归结为只有活跃用户。它不会是 400 万件,我不这么认为。其次,当用户查询他们以前的喜欢时,您也不会查询很多项目。你只是分页。它是每个应用程序所做的。您只阅读最近的 25 个项目并等待用户询问第 2 页,即另外 25 个项目。您可以使用 'Begins_with' 关键字查询 dynamodb 排序键,这将为您提供 'Begins_with(like::)' 并且只有喜欢。
  • 回答您的第二条评论。当所有哈希键都是唯一的时,拥有排序键有什么意义?你不能查询什么可以吗?我已经练习过这种“在同一个条目中列出”的方法,但在很多情况下它是不好的。每次操作都会读取/更新行,如果大小大于 4kb (很快就会),您会消耗越来越多的容量读取/更新一个条目。 DynamoDB 建议将数据分散在我建议的排序键之类的索引上,而不是将它们捆绑到同一个项目中。
猜你喜欢
  • 1970-01-01
  • 2017-08-26
  • 1970-01-01
  • 2018-03-19
  • 1970-01-01
  • 1970-01-01
  • 2021-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多