10

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

我想出的最简单的 nosql-ish 解决方案是存储在每个Post文档中投票的 user_ids 数组(甚至是 +1 或 -1 的数组(user_id, vote)vote以便用户能够更改他们的投票)。考虑到每个帖子都可以有数千票,从性能的角度来看这是一个好主意吗?

像 Reddit 这样非常受欢迎的网站,其热门帖子可以有数十万票吗?

4

3 回答 3

8

像 Reddit 这样非常受欢迎的网站,其热门帖子可以有数十万票吗?

他们呢?在关系数据库中使用你的想法,你有一个整数作为用户 id 指针,一个整数作为 post 指针,一个字节作为投票。每张选票总共 9 个字节。

当然,有一些索引开销。每次投票总共 15 个字节。600 万张投票将占用 90 兆字节的磁盘空间。

Reddit 会在一段时间后锁定帖子,因此无法对其进行编辑或投票。因此,Reddit 不必永远存储个人选票。只是投票总数。

于 2012-08-27T18:50:37.310 回答
8

MongoDB 文档目前被限制为最大 16MB,因此假设 Gilbert 的计算是准确的,您将无法user_idPost文档中存储所有 6 百万。

但是,您可以考虑将投票存储在User文档中(即post_id特定用户投票的 s)。用户对 600 万个不同的帖子进行投票的可能性要小得多,因此这样您就不会很快达到大小限制。

处理此问题的另一种方法:如果您希望对特定帖子有很多投票,您可能希望将Post文档之外的投票存储在单独的集合中并执行附加查询,类似于 SQL 方式中的多对多 JOIN 表:

user_votes { user_id: ObjectId(...), post_id: ObjectId(...), vote:-1 }

并在 (user_id, post_id) 上创建复合索引。

于 2012-08-27T19:26:43.807 回答
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,您需要将该用户添加到该特定表中,然后去更新 post/question 中的 voteCount桌子。

帖子/问题路线:

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



})

This answer is posted for future reference if anyone is looking for a similar answere, here it is.

于 2020-01-31T11:37:32.067 回答