【问题标题】:$inc follower count, or should I use an aggregate to track them?$inc 追随者数量,还是我应该使用聚合来跟踪他们?
【发布时间】:2017-04-19 00:06:38
【问题描述】:

我正在通过无限滚动加载产品,一次 12 个块。

有时,我可能想按他们有多少关注者来排序。

以下是我如何跟踪每个产品有多少关注者。


由于 16mb 的数据上限,关注在一个单独的集合中,并且关注的数量应该是无限的。

遵循架构:

var FollowSchema = new mongoose.Schema({
    user: {
        type: mongoose.Schema.ObjectId,
        ref: 'User'
    },
    product: {
        type: mongoose.Schema.ObjectId,
        ref: 'Product'
    },
    timestamp: {
        type: Date,
        default: Date.now
    }
});

遵循架构的产品:

var ProductSchema = new mongoose.Schema({
    name: {
        type: String,
        unique: true,
        required: true
    },
    followers: {
        type: Number,
        default: 0
    }
});

每当用户关注/取消关注产品时,我都会运行此功能:

ProductSchema.statics.updateFollowers = function (productId, val) {
    return Product
        .findOneAndUpdateAsync({
            _id: productId
        }, {
            $inc: {
                'followers': val
            }
        }, {
            upsert: true,
            'new': true
        })
        .then(function (updatedProduct) {
            return updatedProduct;
        })
        .catch(function (err) {
            console.log('Product follower update err : ', err);
        })
};

我的问题:

1:产品中增加的“关注者”值是否有可能遇到某种错误,导致数据不匹配/不一致?

2:编写一个聚合来计算每个产品的关注者会更好,还是会太贵/太慢?

最终,我可能会在 graphDB 中重写它,因为它似乎更适合,但现在——这是一个掌握 MongoDB 的练习。

【问题讨论】:

  • 关于#1:单个文档更新是原子的,但是您要更新 2 个集合中的 2 个文档,这不是整体原子的。这可能是任何一个步骤都失败了。例如,跟随成功但增量失败。阅读:Two phased commits.

标签: javascript node.js mongodb mongoose


【解决方案1】:

1 如果插入后递增或删除后递减,则可能导致数据不一致。例如插入成功但递增失败。

2 直观地说,在这种情况下,聚合比 find 要昂贵得多。我做了一个基准测试来证明这一点。

首先随机生成1000个用户、1000个产品和10000个关注者。然后,使用此代码进行基准测试。

import timeit

from pymongo import MongoClient
db = MongoClient('mongodb://127.0.0.1/test', tz_aware=True).get_default_database()

def foo():
    result = list(db.products.find().sort('followers', -1).limit(12).skip(12))

def bar():
    result = list(db.follows.aggregate([
        {'$group': {'_id': '$product', 'followers': {'$sum': 1}}},
        {'$sort': {'followers': -1}},
        {'$skip': 12},
        {'$limit': 12}
    ]))

if __name__ == '__main__':
    t = timeit.timeit('foo()', 'from __main__ import foo', number=100)
    print('time: %f' % t)

    t = timeit.timeit('bar()', 'from __main__ import bar', number=100)
    print('time: %f' % t)

输出:

time: 1.230138
time: 3.620147

创建索引可以加快查找查询。

db.products.createIndex({followers: 1})

time: 0.174761
time: 3.604628

如果您需要产品的属性(例如名称),则需要另一个 O(n) 查询。

我猜当数据规模扩大时,聚合速度会慢很多。如果需要,我可以对大规模数据进行基准测试。

【讨论】:

    【解决方案2】:

    1) 这依赖于应用层来强制执行一致性,因此您最终可能会遇到不一致。我要问的问题是:在这种情况下,一致性有多重要,出现大的不一致的可能性有多大?我的想法是,被一个追随者关闭并不像尽可能快地无限滚动加载以改善用户体验那么重要。

    2) 可能值得看看性能,但如果我不得不猜测,我会说这种方法会很慢。

    【讨论】:

      【解决方案3】:

      对于数字 1,如果对该字段的唯一操作是递增和递减,我认为你会没事的。如果您出于某种原因开始复制该数据或在联接中使用它,您将面临数据不一致的风险。

      对于第 2 点,我建议您在 mongo shell 中运行这两种方案来测试它们。您还可以review the individual explain plans 查询这两个查询,以了解哪个查询的性能更好。我只是在猜测,但似乎更新路线会表现良好。

      此外,预期数据量也会有所不同。它最初可能以一种方式表现良好,但在一百万条记录之后,另一条路线可能是要走的路。如果你有一个测试环境,那最好检查一下。

      【讨论】:

        猜你喜欢
        • 2018-11-19
        • 2019-08-27
        • 2011-08-12
        • 1970-01-01
        • 2012-07-12
        • 1970-01-01
        • 2011-07-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多