在mongodb中存储upvotes / downvotes

时间:2012-08-27 18:43:55

标签: mongodb database-design

我有一个PostsUsers的集合,用户可以对每个帖子进行upvote / downvote。将此存储在mongodb数据库中以确保用户不能多次为给定文档投票的最佳方法是什么?

我提出的最简单的nosql-ish解决方案是存储一个user_id数组,这些user_id在每个Post文档中投票(甚至是(user_id, vote)的数组vote,其中{{1}}为+1或 - 1这样用户就可以改变他们的投票了。从表现的角度来看,这是一个好主意,因为每个帖子都有数千张选票吗?

Reddit等真正受欢迎的网站怎么样?顶级帖子可以有数十万票?

3 个答案:

答案 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))



})

如果有人正在寻找类似的答案,此答案将发布以供将来参考。