我正在尝试在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),这是一项不常见的操作,可能会缓慢进行并且一次只能处理一个文档,因此最终一致性很好。关于您的建议,我有两个基本问题:
上述方法有哪些问题和陷阱?我知道一些显而易见的事情:额外的存储空间,更新速度较慢,需要添加排队和重试逻辑以支持最终的一致性以及边缘数据在其两个副本之间不同步的风险。我认为我可以解决这些问题。但是我还会遇到其他非显而易见的问题吗?
与其在边缘的每个节点上都具有user1
和user2
字段,不如使用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解决方案上,而不要让我使用图形或关系数据库。谢谢!