我正在试图找出像app这样的约会网站最好的架构。用户有一个列表(可能很多),他们可以查看其他用户列表以“喜欢”和“不喜欢”它们。
目前,我只是将其他人员列入likedBy
和dislikedBy
数组。当用户“喜欢”列表时,它会将其列表ID放入“喜欢”列表数组中。但是,我现在想跟踪用户喜欢列表的时间戳。这将用于用户的“历史列表”或用于数据分析。
我需要做两个单独的查询:
find all active listings that this user has not liked or disliked before
以及用户“喜欢”/“不喜欢”选择的历史记录
find all the listings user X has liked in chronological order
我目前的架构是:
listings
_id: 'sdf3f'
likedBy: ['12ac', 'as3vd', 'sadf3']
dislikedBy: ['asdf', 'sdsdf', 'asdfas']
active: bool
我可以这样做吗?
listings
_id: 'sdf3f'
likedBy: [{'12ac', date: Date}, {'ds3d', date: Date}]
dislikedBy: [{'s12ac', date: Date}, {'6fs3d', date: Date}]
active: bool
我还想为choices
创建一个新的集合。
choices
Id
userId // id of current user making the choice
userlistId // listing of the user making the choice
listingChoseId // the listing they chose yes/no
type
date
在执行find all active listings that this user has not liked or disliked before
时,我不确定在其他集合中使用这些选项会对性能产生影响。
非常感谢任何见解!
答案 0 :(得分:15)
嗯,您显然认为将这些内容嵌入“列表”文档中是一个好主意,因此您在此处提供的案例的其他使用模式可以正常工作。考虑到这一点,没有理由把它扔掉。
为了澄清,你似乎想要的结构是这样的:
{
"_id": "sdf3f",
"likedBy": [
{ "userId": "12ac", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "as3vd", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "sadf3", "date": ISODate("2014-04-09T07:30:47.091Z") }
],
"dislikedBy": [
{ "userId": "asdf", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "sdsdf", "date": ISODate("2014-04-09T07:30:47.091Z") },
{ "userId": "asdfas", "date": ISODate("2014-04-09T07:30:47.091Z") }
],
"active": true
}
除了有一个捕获之外,这一切都很好。因为您在两个数组字段中具有此内容,所以您将无法在这两个字段上创建索引。这是一个限制,其中只有一个数组类型的字段(或多键)可以包含在复合索引中。
因此,为了解决第一个查询无法使用索引的明显问题,您可以这样构建:
{
"_id": "sdf3f",
"votes": [
{
"userId": "12ac",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "as3vd",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "sadf3",
"type": "like",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "asdf",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "sdsdf",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
},
{
"userId": "asdfas",
"type": "dislike",
"date": ISODate("2014-04-09T07:30:47.091Z")
}
],
"active": true
}
这允许覆盖此表单的索引:
db.post.ensureIndex({
"active": 1,
"votes.userId": 1,
"votes.date": 1,
"votes.type": 1
})
实际上你可能想要一些索引来满足你的使用模式,但现在可以使用索引了。
覆盖第一种情况,你有这种形式的查询:
db.post.find({ "active": true, "votes.userId": { "$ne": "12ac" } })
考虑到你显然不会为每个用户提供喜欢和不喜欢的选项,这是有道理的。按照该索引的顺序,至少可以使用active来过滤,因为您的否定条件需要扫描其他所有内容。任何结构都无法解决这个问题。
对于另一种情况,您可能希望userId在日期之前位于索引中并作为第一个元素。然后你的查询很简单:
db.post.find({ "votes.userId": "12ac" })
.sort({ "votes.userId": 1, "votes.date": 1 })
但是你可能想知道你突然失去了一些东西,因为得到“喜欢”和“不喜欢”的数量就像测试阵列的大小一样容易,但现在它有点不同了。这不是使用聚合无法解决的问题:
db.post.aggregate([
{ "$unwind": "$votes" },
{ "$group": {
"_id": {
"_id": "$_id",
"active": "$active"
},
"likes": { "$sum": { "$cond": [
{ "$eq": [ "$votes.type", "like" ] },
1,
0
]}},
"dislikes": { "$sum": { "$cond": [
{ "$eq": [ "$votes.type", "dislike" ] },
1,
0
]}}
])
因此,无论您的实际使用形式如何,您都可以存储文档的任何重要部分以保留在分组_id
中,然后以简单的方式评估“喜欢”和“不喜欢”的数量。
您也可能不会将条目从喜欢变为不喜欢也可以在单个原子更新中完成。
你可以做更多的事情,但出于给定的原因,我更喜欢这种结构。