嵌入重复的“ friendship”子文档以在MongoDB中建模相互的友谊边缘

时间:2019-02-15 22:16:54

标签: mongodb graph-theory data-modeling social-networking

我正在尝试在MongoDB中建立双向友谊。 “双向”是指,与Facebook一样,但与Twitter不同,如果您是Sam的朋友,那么Sam也必须与您成为朋友。在MongoDB中,通常推荐的解决方案(example)似乎是这样的:

  • 创建一个User集合(又称节点以使用适当的图论术语)和一个Friendship集合(又称边)
  • 每个User文档都包含一个嵌入式friends数组,该数组的每个元素都包含每个朋友的ObjectID。每个元素还可以缓存有关每个朋友的只读信息(例如姓名,照片URL),这样可以避免跨文档查询常见用例,例如“显示我的朋友列表”。
  • 添加友谊包括插入一个新的Friendship文档,然后$push —将Friendship的对象ID放入两个用户的friends数组中,并进行读取仅缓存有关朋友的信息(例如姓名),以避免在显示朋友列表时进行多文档查询。

我正在考虑一种不同的设计,在该设计中,将在双向关系的两个节点中存储(复制)边缘数据,而不是单独的Friendship集合。像这样:

  {
    _id: new ObjectID("111111111111111111111111"),
    name: "Joe", 
    pictureUrl: "https://foo.com/joe.jpg",
    invites: [
      ... // similar schema to friends array below
    ],
    friends: [
      {
        friendshipId: new ObjectID("123456789012345678901234"),
        lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"),
        user1: {
          userId: new ObjectID("111111111111111111111111"),
          name: "Joe",  // cached, read-only data to avoid multi-doc reads
          pictureUrl: "https://foo.com/joe.jpg",
        },
        user2: {
          userId: new ObjectID("222222222222222222222222"),
          name: "Bill",  // cached, read-only data to avoid multi-doc reads
          pictureUrl: "https://foo.com/bill.jpg",
        },
      }
    ]
  },
  {
    _id: new ObjectID("222222222222222222222222"),
    name: "Bill",
    pictureUrl: "https://foo.com/bill.jpg",
    invites: [
      ... // similar schema to friends array below
    ],
    friends: [
      {
        friendshipId: new ObjectID("123456789012345678901234"),
        lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"), // shared data about the edge
        user1: {  // data specific to each friend
          userId: new ObjectID("111111111111111111111111"),
          name: "Joe",  // cached, read-only data to avoid multi-doc reads
          pictureUrl: "https://foo.com/joe.jpg",
        },
        user2: { // data specific to each friend
          userId: new ObjectID("222222222222222222222222"),
          name: "Bill",  // cached, read-only data to avoid multi-doc reads
          pictureUrl: "https://foo.com/bill.jpg",
        },
      }
    ]
  }

这是我打算处理以下问题的方式:

  • 读取-常见操作的所有读取仅发生在单个User文档中。有关朋友的高级信息(例如,姓名,图片网址)被缓存在friends数组中。
  • 邀请朋友-将新文档添加到嵌入两个用户的invites数组中(上面未显示),其结构和功能与上面显示的friends集合相似
  • 已接受邀请-使用updateMany,向两个用户的friends数组中添加新的相同嵌入式文档,并从两个用户的$pull数组中添加invites的元素。最初,我将使用多文档事务进行这些更新,但是由于添加友谊并不是时间紧迫的,因此可以进行调整以使用最终的一致性。
  • 撤销了友谊-将updateMany与过滤器结合使用,以{'friends.friendshipId': new ObjectID("123456789012345678901234")}$pull两个用户的friends数组中的友谊子文档。像上面一样,这可以在开始时使用多文档交易,如果需要扩展则可以最终使用一致性。
  • 更新到缓存的数据-如果用户更改了friends中缓存的信息(例如名称或图片URL),这是一项不常见的操作,可能会缓慢进行并且一次只能处理一个文档,因此最终一致性很好。

关于您的建议,我有两个基本问题:

  • 上述方法有哪些问题和陷阱?我知道一些显而易见的事情:额外的存储空间,更新速度较慢,需要添加排队和重试逻辑以支持最终的一致性以及边缘数据在其两个副本之间不同步的风险。我认为我可以解决这些问题。但是我还会遇到其他非显而易见的问题吗?

  • 与其在边缘的每个节点上都具有user1user2字段,不如使用2元素数组来代替?为什么或者为什么不?这是我的意思的示例:

    friends: [
      {
        friendshipId: new ObjectID("123456789012345678901234"),
        lastMeeting: new Date("2019-02-07T20:35:55.256+00:00"),
        users: [
          {
            userId: new ObjectID("111111111111111111111111"),
            name: "Joe",  // cached, read-only data to avoid multi-doc reads
            pictureUrl: "https://foo.com/joe.jpg",
          },
          {
            userId: new ObjectID("222222222222222222222222"),
            name: "Bill",  // cached, read-only data to avoid multi-doc reads
            pictureUrl: "https://foo.com/bill.jpg",
          },
        ],
      }
    ]
    

顺便说一句,我知道与MongoDB相比,图形数据库甚至关系数据库在关系建模方面更胜一筹。但是由于种种原因,我现在已经选择了MongoDB,因此请仅将答案限制在MongoDB解决方案上,而不要让我使用图形或关系数据库。谢谢!

0 个答案:

没有答案