关注者 - mongodb 数据库设计

Followers - mongodb database design

所以我正在使用 mongodb,但我不确定我是否拥有适合我正在尝试做的事情的正确/最佳数据库集合设计。

可以有很多项目,用户可以创建新的组,其中包含这些项目。任何用户都可以关注任何组!

我不仅将关注者和项目添加到组集合中,因为组中可能有 5 个项目,或者可能有 10000 个(关注者也一样),根据研究,我认为您不应该使用未绑定的数组(其中限制未知)由于文档由于其扩展大小而必须移动时的性能问题。 (在遇到性能问题之前是否有推荐的最大数组长度?)

我认为对于以下设计,真正的性能问题可能是当我想要获取用户针对特定项目关注的所有组时(基于 user_id 和 item_id), 因为那时我必须找到用户正在关注的所有组,并从中找到所有 item_groups 与 group_id $in 和项目 ID。 (但我实际上看不到任何其他方法)

Follower
.find({ user_id: "54c93d61596b62c316134d2e" })
.exec(function (err, following) {
  if (err) {throw err;};

  var groups = [];

  for(var i = 0; i<following.length; i++) {
    groups.push(following[i].group_id)
  }

  item_groups.find({
  'group_id': { $in: groups },
  'item_id': '54ca9a2a6508ff7c9ecd7810'
  })
  .exec(function (err, groups) {
    if (err) {throw err;};

    res.json(groups);

  });

})

是否有更好的数据库模式来处理此类设置?

更新:在下面的评论中添加了示例用例。

任何帮助/建议将不胜感激。

非常感谢, Mac

我读了你的comment/use-case。所以我更新了我的答案。

我建议按照这篇文章更改设计:MongoDB Many-To-Many

设计方法不同,您可能想要改造您的方法。我会试着给你一个开始的想法。 我假设 User 和 Follower 在这里基本上是相同的实体。 我认为您可能会发现有趣的一点是,在 MongoDB 中,您可以存储 array 字段,这就是我将用于 simplify/correct 您的 [=78] 设计的内容=].

我要省略的两个实体是:关注者和 ItemGroups

  • Followers:就是一个可以关注群组的用户。我会加一个 group ids 的数组,以包含用户关注的组列表。因此,我不会拥有一个实体 Follower,而只会拥有一个包含 Group ID 列表的数组字段的 User。
  • ItemGroups:我也想移除这个实体。相反,我会在组实体中使用一组项目 ID,并在项目实体中使用一组组 ID。

基本上就是这样。您将能够执行您在用例中描述的操作。该设计更简单、更准确,因为它反映了基于文档的数据库的设计决策。

备注:

  • 您可以在 MongoDB 中的数组字段上定义索引。例如,参见 Multikey Indexes
  • 但要小心在数组字段上使用索引。您需要了解您的用例才能决定它是否合理。看到这个 article。由于您只引用了 ObjectId,我认为您可以尝试一下,但在其他情况下,更改设计可能会更好。
  • 还要注意ID字段_id是一个MongoDB 用作主键的 ObjectID 的特定字段类型。要访问 ID,您可以参考它,例如如 user.id、group.id 等。您可以根据此 question.
  • 使用索引来确保唯一性

您的架构设计可能如下所示:

至于你的其他question/concerns

Is there a recommended maximum for array lengths before hitting performance issues anyway?

答案在 MongoDB 文档大小限制为 16 MB,现在您可以解决这个问题。但是 16 MB 被认为是足够的;如果您达到 16 MB,则必须改进您的设计。有关信息,请参阅 here,文档大小限制部分。

I think with the following design a real performance issue could be when I want to get all of the groups that a user is following for a specific item (based off of the user_id and item_id)...

我会这样做。注意使用 MongoDB.

时 "easier" 的发音
  1. 获取用户的商品
  2. 获取引用该项目的组

如果数组变得非常大并且您在其上使用索引,我会比较担心。这可能会总体上减慢对相应文档的写入操作。你的情况可能不是那么多,但不完全确定。

不幸的是,NoSQL 数据库在这种情况下不符合条件。您的数据模型似乎完全相关。根据 MongoDB 文档,我们只能做 these and can perform only these.

第 14 张幻灯片有 some practices. MongoDB advises to us using Followers collection to get which user follows which group and vice versa with good performance. You may find the closest case to your situation on this page 个。但我认为,如果您想在不同的页面上获得每个结果,幻灯片就可以使用。例如;您是 Twitter 用户,当您单击 followers 按钮时,您会看到所有关注者。然后单击关注者姓名,您将看到该关注者的消息以及您可以看到的任何内容。我们可以看到所有这些工作 一步一步 不需要关系查询

I believe that you should not use unbound arrays (where the limit is unknown) due to performance issues when the document has to be moved because of its expanding size. (Is there a recommended maximum for array lengths before hitting performance issues anyway?)

是的,你是对的。 http://askasya.com/post/largeembeddedarrays。 但是,如果您的数组中有大约一百个项目,则没有问题。 如果您有 固定大小 一些数据,您可以将它们作为数组嵌入到您的关系集合中。您可以快速查询索引的嵌入式文档字段。

以我的拙见,您应该在测试中创建数十万个test data and check performances of using embedded documents and arrays eligible to your case. Don't forget creating indexes appropriate your queries. You may try to using document references。测试后,如果您喜欢结果的表现,请继续..

您曾尝试查找特定用户关注的 group_id 条记录,然后您已尝试查找包含这些 group_id 的特定项目。 Item_GroupsFollowers 集合是否可能具有 多对多 关系? 如果是这样,NoSQL 数据库不支持多对多关系。

是否有机会将数据库更改为 MySQL?

如果是这样,您应该检查 this

briefly MongoDB pros against to MySQL;
- Better writing performance

briefly MongoDB cons against to MySQL;
- Worse reading performance

如果您在 Node.js 上工作,您可以查看 https://www.npmjs.com/package/mysql and https://github.com/felixge/node-mysql/

祝你好运...

您在创建高性能 NoSQL 架构设计方面走在了正确的轨道上,而且我认为您提出的关于如何正确布局的问题是正确的。

以下是我对您申请的理解:

看起来群组可以同时拥有多个关注者(将用户映射到群组)和多个项目,但项目不一定在多个群组中(尽管有可能)。从您给出的用例示例来看,这听起来像是检索项目所在的所有组,并且组中的所有项目将是一些常见的读取操作。

在您当前的架构设计中,您已经实现了一个模型,将用户映射到群组作为关注者,并将项目映射到群组作为 item_groups。在您提到更复杂查询的问题之前,这一切正常:

I think with the following design a real performance issue could be when I want to get all of the groups that a user is following for a specific item (based off of the user_id and item_id)

我认为有几件事可以帮助您解决这种情况:

  • 利用 MongoDB 对关注者对象的强大 indexing capabilities. In particular, I think you should consider creating compound indexes 覆盖您的群组和用户,以及对项目和群组的 Item_Groups。您还需要确保这种关系是唯一的,因为用户只能关注一个组一次,并且一个项目只能添加到组一次。这最好在您的架构中定义的一些预保存挂钩中实现,或者使用插件来检查有效性。

FollowerSchema.index({ group: 1, user: 1 }, { unique: true }); Item_GroupsSchema.index({ group: 1, item: 1 }, { unique: true });

在这些字段上使用索引会在写入集合时产生一些开销,但听起来从集合中读取将是一种更常见的交互,所以这是值得的(我建议阅读更多index performance).

  • 由于用户可能不会关注数千个群组,我认为在用户模型中包含用户关注的群组数组是值得的。当您想要在用户当前关注的组中查找某个项目的所有实例时,这将帮助您完成复杂的查询,因为您将在此处获得组列表。您仍然会在使用 $in: groups 的地方拥有实现,但它会少一个对集合的查询。

  • 正如我之前提到的,似乎项目不一定在那么多组中(就像用户不一定关注数千个组一样)。如果情况通常是一个项目可能在几百个组中,我会考虑只为它添加到的每个组向项目模型添加一个数组。这会在读取项目所在的所有组时提高您的性能,您提到的查询将是一个常见的查询。注意:您仍将使用 Item_Groups 模型通过查询(现已编入索引)group_id.

  • 来检索组中的所有项目

我同意其他答案的一般观点,即这是一个 边界线 关系问题。

MongoDB 数据模型的关键是写入繁重,但这对于这个用例来说可能很棘手,主要是因为如果你想让 link 用户项目(对一个有大量用户关注的组的更改会导致大量写入,您需要一些工作人员来执行此操作)。

让我们调查一下读取密集型模型是否不适用于此处,或者我们是否在进行过早优化。

重读方法

您主要关注以下用例:

a real performance issue could be when I want to get all of the groups that a user is following for a specific item [...] because then I have to find all of the groups the user is following, and from that find all of the item_groups with the group_id $in and the item id.

让我们来剖析一下:

  • 获取用户关注的所有群组

    这是一个简单的查询:db.followers.find({userId : userId})。我们将需要 userId 上的索引,这将使该操作的运行时间为 O(log n),或者即使对于大 n 也非常快。

  • 从中找到具有 group_id $in 和项目 ID

    的所有 item_groups

    现在这是比较棘手的部分。让我们暂时假设项目不太可能属于大量组。那么复合索引 { itemId, groupId } 效果最好,因为我们可以通过第一个标准大幅减少候选集——如果一个项目仅在 800 个组中共享并且用户正在关注 220 个组,mongodb 只需要找到它们的交集,这比较容易,因为两个集合都很小。

不过,我们需要比这更深入:

您的数据结构可能属于复杂网络。复杂网络有多种形式,但假设您的关注者图表是 nearly scale-free 是有意义的,这也是最坏的情况。在无标度网络中,极少数节点(名人、超级碗、维基百科)吸引了大量 'attention'(即有很多连接),而更多节点难以获得相同数量关注度合并.

小节点无需担心,上面的查询,包括到数据库的往返都在 2 毫秒范围内在我的开发机器上,在具有数千万个连接和 > 5GB 数据的数据集上。现在数据集不是很大,但无论您选择什么技术,都将受 RAM 限制,因为在任何情况下索引都必须在 RAM 中(网络中的数据局部性和可分离性通常很差),并且设置的交集大小是小的定义。换句话说:这个制度是由硬件瓶颈主导的。

超级节点呢?

因为那是猜测,而且我对网络模型很感兴趣,所以我 took the liberty of implementing a dramatically simplified network tool 根据您的数据模型进行了一些测量。 (抱歉,它是用 C# 编写的,但是用我最流利的语言生成结构良好的网络已经够难了...)。

查询超级节点时,我得到的结果在 7 毫秒顶部 范围内(这是在 1.3GB 数据库中的 12M 条目上, 最大的组具有其中有 133,000 个项目,一个用户关注 143 个组。)

此代码中的假设是用户关注的组数并不大,但这在这里似乎是合理的。如果不是,我会选择大量写入的方法。

随意使用代码。不幸的是,如果你想用超过几 GB 的数据来尝试这个,它需要一些优化,因为它根本没有优化并且在这里和那里做了一些非常低效的计算(尤其是 beta 加权随机洗牌可以改进).

换句话说:我不会担心读取密集型方法的性能 yet 问题通常不是以至于用户数量增长,但用户以意想不到的方式使用系统。

重写方法

另一种方法可能是颠倒 linking:

的顺序
UserItemLinker
{
 userId,
 itemId,
 groupIds[]  // for faster retrieval of the linker. It's unlikely that this grows large
}

这可能是最具可扩展性的数据模型,但我不会选择它,除非我们谈论的是分片是关键要求的海量数据。这里的关键区别在于,我们现在可以通过将 userId 用作分片键的一部分来有效地划分数据。这有助于在多数据中心场景中并行查询、高效分片并改善数据局部性。

这可以使用更精细的测试平台版本进行测试,但我还没有找到时间,坦率地说,我认为这对大多数应用程序来说都太过分了。