使用$ sample的MongoDB聚合非常慢

时间:2016-06-07 12:54:37

标签: mongodb pymongo mongoengine

有许多方法可以从mongodb集合中选择随机文档(如in this answer所述)。评论指出,使用mongodb版本> = 3.2,然后在聚合框架中使用$sample是首选。但是,在包含许多小文档的集合中,这似乎非常慢。

以下代码使用mongoengine来模拟问题并将其与" skip random"进行比较。方法:

import timeit
from random import randint

import mongoengine as mdb

mdb.connect("test-agg")


class ACollection(mdb.Document):
    name = mdb.StringField(unique=True)

    meta = {'indexes': ['name']}


ACollection.drop_collection()

ACollection.objects.insert([ACollection(name="Document {}".format(n)) for n in range(50000)])


def agg():
    doc = list(ACollection.objects.aggregate({"$sample": {'size': 1}}))[0]
    print(doc['name'])

def skip_random():
    n = ACollection.objects.count()
    doc = ACollection.objects.skip(randint(1, n)).limit(1)[0]
    print(doc['name'])


if __name__ == '__main__':
    print("agg took {:2.2f}s".format(timeit.timeit(agg, number=1)))
    print("skip_random took {:2.2f}s".format(timeit.timeit(skip_random, number=1)))

结果是:

Document 44551
agg took 21.89s
Document 25800
skip_random took 0.01s

在过去,无论我在哪里遇到mongodb的性能问题,我的答案一直是使用聚合框架,所以我很惊讶$sample是如此之慢。

我在这里遗漏了什么吗?这个例子导致聚合花费这么长时间是什么?

4 个答案:

答案 0 :(得分:2)

这是mongodb版本中WiredTiger引擎中known bug的结果< 3.2.3。升级to the latest version应解决此问题。

答案 1 :(得分:1)

我可以确认3.6中没有任何变化 缓慢的$ sample问题仍然存在。羞辱你,MongoDB团队。

〜40m小文档集合,没有索引,Windows Server 2012 x64。

存储:     wiredTiger.engineConfig.journalCompressor:zlib     wiredTiger.collectionConfig.blockCompressor:zlib

2018-04-02T02:27:27.743-0700 我命令[conn4]命令maps.places

命令:聚合{聚合:“位置”,管道:[{$ sample:{size:10}}]

 cursor: {}, lsid: { id: UUID("0e846097-eecd-40bb-b47c-d77f1484dd7e") }, $readPreference: { mode: "secondaryPreferred" }, $db: "maps" } planSummary: MULTI_ITERATOR keysExamined:0 docsExamined:0 cursorExhausted:1 numYields:3967 nreturned:10 reslen:550 locks:{ Global: { acquireCount: { r: 7942 } }, Database: { acquireCount: { r: 3971 } }, Collection: { acquireCount: { r: 3971 } } }

协议:op_query 72609ms

我已经安装了Mongo来在一个严肃的项目中尝试这个“现代和高性能的DBMS”。我有多么沮丧。

解释计划在这里:

db.command('aggregate', 'places', pipeline=[{"$sample":{"size":10}}], explain=True)

 {'ok': 1.0,
  'stages': [{'$cursor': {'query': {},
    'queryPlanner': {'indexFilterSet': False,
     'namespace': 'maps.places',
     'plannerVersion': 1,
     'rejectedPlans': [],
     'winningPlan': {'stage': 'MULTI_ITERATOR'}}}},
  {'$sampleFromRandomCursor': {'size': 10}}]}

答案 2 :(得分:1)

对于那些对$sample感到困惑的人,$sample在以下情况下会很有效:

  • $sample是管道的第一阶段
  • N少于馆藏文档总数的5%
  • 馆藏包含100多个文档

如果不满足以上任何条件,$sample将执行收集扫描,然后进行随机排序以选择N文档。

更多信息:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/

答案 3 :(得分:0)

蒙哥声明

如果满足以下所有条件,则$ sample使用伪随机游标选择文档:

  • $ sample是管道的第一阶段
  • N小于集合中文档总数的5%
  • 馆藏包含100多个文档

如果不满足上述任何条件,则$ sample执行收集扫描,然后进行随机排序以选择N个文档。在这种情况下,$ sample阶段受排序内存限制。

我相信mongo会对您进行全面扫描

参考:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/