我有一个Posts
和Users
的集合,用户可以对每个帖子进行upvote / downvote。将此存储在mongodb数据库中以确保用户不能多次为给定文档投票的最佳方法是什么?
我提出的最简单的nosql-ish解决方案是存储一个user_id数组,这些user_id在每个Post
文档中投票(甚至是(user_id, vote)
的数组vote
,其中{{1}}为+1或 - 1这样用户就可以改变他们的投票了。从表现的角度来看,这是一个好主意,因为每个帖子都有数千张选票吗?
Reddit等真正受欢迎的网站怎么样?顶级帖子可以有数十万票?
答案 0 :(得分:7)
Reddit等真正受欢迎的网站怎么样?顶级帖子可以有数十万票?
他们呢?在关系数据库中使用您的想法,您有一个用于用户id指针的整数,一个用于post指针的整数,以及一个用于投票的字节。每次投票总共9个字节。
当然,有一些索引开销。每次投票总计15个字节。 600万票将占用90兆字节的磁盘空间。
Reddit会在一段时间后锁定帖子,因此无法对其进行编辑或投票。所以Reddit不必永远存储个人投票。只需投票总数。
答案 1 :(得分:7)
MongoDB文档目前限制为最大16MB,因此假设Gilbert的计算准确无误,您将无法在user_id
文档中存储所有6百万Post
个。
但是,您可以考虑将投票存储在User
文档中(即特定用户投票的post_id
)。用户在600万个不同帖子上投票的可能性要小得多,因此这样您就不会很快达到大小限制。
另一种处理此问题的方法:如果您希望对特定帖子进行多次投票,您可能希望将Post
文档之外的投票存储在单独的集合中,并执行其他查询,类似于许多SQL方式中的to-many JOIN表:
user_votes { user_id: ObjectId(...), post_id: ObjectId(...), vote:-1 }
并在(user_id,post_id)上创建复合索引。
答案 2 :(得分:2)
好的,我知道问了这个问题已经有一段时间了,但是我对上述答案或在类似问题上发布的任何其他答案都不满意。所以最后我想出了另一个答案。
首先,如上所述,MongoDB文档当前限制为最大16MB。因此,实现此目的的逻辑方式将是,具有2个不同的集合,一个用于upvotes,一个用于downvotes,它们将存储postid和已投票或已投票的用户数组。这将有助于我们分离馆藏,并有更多的空间可以使用。为了清楚起见,这里是我使用的架构:
投票:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const UpvoteModel = new Schema({
questionid: {
type: Schema.Types.ObjectId,
ref: 'questionsModel'
}
,
votes: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}]
})
module.exports = Upvote = mongoose.model('upvotes', UpvoteModel)
赞成投票:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const DownvoteModel = new Schema({
questionid: {
type: Schema.Types.ObjectId,
ref: 'questionsModel'
}
,
votes: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}]
})
module.exports = Downvote = mongoose.model('downvotes', DownvoteModel)
帖子收集(在这种情况下为问题):
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const QuestionsSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
title: {
type: String,
required: true
},
description: {
type: String
},
voteCount: {
type: Number
},
votes: [{
user: { type: Schema.Types.ObjectId }
}],
views: [{
user: { type: Schema.Types.ObjectId }
}],
answers: [{
user: { type: Schema.Types.ObjectId },
answer: {
type: String
},
date: {
type: Date,
default: Date.now
}
}],
date: {
type: Date,
default: Date.now
}
})
module.exports = Question = mongoose.model('questionsModel', QuestionsSchema)
每次创建帖子/问题时,帖子/问题ID都会存储在upvote和downvote表中,如果用户单击了upvote或downvote,则需要将该用户添加到该特定表中,然后在发布/问题表。
发布/提问路线:
router.post('/upvote', (req, res) => {
Upvote.findOne({ questionid: req.body.params.questionid })
.then(oneVote => {
if (oneVote.votes.filter(user => req.body.params.userid).length === 1) {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: -1 } })
.then(() => {
Upvote.updateOne({
questionid: req.body.params.questionid,
},
{
$pull: {
votes: { user: ObjectId(req.body.params.userid) }
}
})
.then(() => console.log('decrement by -1'))
}
)
.catch(err => console.log(err))
}
else if (oneVote.votes.filter(user => req.body.params.userid).length === 0) {
Upvote.findOneAndUpdate({
questionid: req.body.params.questionid,
'votes.user': { $ne: ObjectId(req.body.params.userid) }
},
{
$push: {
votes: { user: ObjectId(req.body.params.userid) }
}
},
{ useFindAndModify: false }
)
.then(oldupvote => {
Downvote.findOne({ questionid: req.body.params.questionid })
.then(downvote => {
if (downvote.votes.filter(user => req.body.params.userid).length > 0) {
Downvote.updateOne({
questionid: req.body.params.questionid,
},
{
$pull: {
votes: { user: ObjectId(req.body.params.userid) }
}
})
.then(() => {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: 2 } })
.then(() => console.log('increment by 2')
)
.catch(err => console.log(err))
})
.catch(err => console.log(err))
}
else {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: 1 } })
.then(() => console.log('increment by 1')
)
.catch(err => console.log(err))
}
})
.catch(err => console.log(err))
})
}
})
.catch(err => console.log(err))
})
下注路线:
router.post('/downvote', (req, res) => {
Downvote.findOne({ questionid: req.body.params.questionid })
.then(oneVote => {
if (oneVote.votes.filter(user => req.body.params.userid).length === 1) {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: 1 } })
.then(() => {
Downvote.updateOne({
questionid: req.body.params.questionid,
},
{
$pull: {
votes: { user: ObjectId(req.body.params.userid) }
}
})
.then(() => console.log('increment by 1'))
.catch(err => console.log(err))
}
)
.catch(err => console.log(err))
}
else if (oneVote.votes.filter(user => req.body.params.userid).length === 0) {
Downvote.findOneAndUpdate({
questionid: req.body.params.questionid,
'votes.user': { $ne: ObjectId(req.body.params.userid) }
},
{
$push: {
votes: { user: ObjectId(req.body.params.userid) }
}
},
{ useFindAndModify: false }
)
.then(oldownvote => {
Upvote.findOne({ questionid: req.body.params.questionid })
.then(upvote => {
if (upvote.votes.filter(user => req.body.params.userid).length > 0) {
Upvote.updateOne({
questionid: req.body.params.questionid,
},
{
$pull: {
votes: { user: ObjectId(req.body.params.userid) }
}
})
.then(() => {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: -2 } })
.then(() => console.log('decrement by -2')
)
})
.catch(err => console.log(err))
}
else {
Question.updateOne({ _id: req.body.params.questionid },
{ $inc: { voteCount: -1 } })
.then(() => console.log('decrement by -1')
)
.catch(err => console.log(err))
}
})
.catch(err => console.log(err))
})
// .then(() => {
// Upvote.findOne({ questionid: req.body.params.questionid })
// .then(updatedupvote => console.log(updatedupvote))
// })
.catch(err => console.log(err))
}
})
.catch(err => console.log(err))
})
如果有人正在寻找类似的答案,此答案将发布以供将来参考。