MongoDB 与 $sample 的聚合非常慢
MongoDB Aggregation with $sample very slow
有很多方法可以从 mongodb 集合中 select 随机文档(正如聚合框架中讨论的那样 in this answer). Comments point out that with mongodb version >= 3.2 then using $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
这么慢。
我是不是漏掉了什么?这个例子是什么导致聚合花费这么长时间?
这是 known bug in the WiredTiger engine in versions of mongodb < 3.2.3. Upgrading to the latest version 应该解决的结果。
我可以确认 3.6 中没有任何变化
缓慢的 $sample 问题仍然存在。
~40m 小文档集合,无索引,Windows Server 2012 x64.
存储:
wiredTiger.engineConfig.journalCompressor: zlib
wiredTiger.collectionConfig.blockCompressor: zlib
2018-04-02T02:27:27.743-0700
我命令 [conn4] 命令 maps.places
command: aggregate { aggregate: "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}}]}
对于那些对 $sample
感到困惑的人,$sample
在以下条件下是有效的:
$sample
是管道的第一阶段
N
不到集合中文档总数的 5%
- 该集合包含 100 多个文档
如果不满足上述任何条件,$sample
执行集合扫描,然后对 select N
文档进行随机排序。
更多信息:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/
Mongo 表示
如果满足以下所有条件,$sample 使用伪随机游标 select 文档:
- $sample 是流水线的第一阶段
- N 小于集合中文档总数的 5%
- 该集合包含超过 100 个文档
如果不满足上述任何条件,$sample 将执行集合扫描,然后对 select N 个文档进行随机排序。在这种情况下,$sample 阶段受排序内存限制。
我相信你的情况mongo进行全面扫描
参考:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/
有很多方法可以从 mongodb 集合中 select 随机文档(正如聚合框架中讨论的那样 in this answer). Comments point out that with mongodb version >= 3.2 then using $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
这么慢。
我是不是漏掉了什么?这个例子是什么导致聚合花费这么长时间?
这是 known bug in the WiredTiger engine in versions of mongodb < 3.2.3. Upgrading to the latest version 应该解决的结果。
我可以确认 3.6 中没有任何变化 缓慢的 $sample 问题仍然存在。
~40m 小文档集合,无索引,Windows Server 2012 x64.
存储: wiredTiger.engineConfig.journalCompressor: zlib wiredTiger.collectionConfig.blockCompressor: zlib
2018-04-02T02:27:27.743-0700 我命令 [conn4] 命令 maps.places
command: aggregate { aggregate: "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}}]}
对于那些对 $sample
感到困惑的人,$sample
在以下条件下是有效的:
$sample
是管道的第一阶段N
不到集合中文档总数的 5%- 该集合包含 100 多个文档
如果不满足上述任何条件,$sample
执行集合扫描,然后对 select N
文档进行随机排序。
更多信息:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/
Mongo 表示
如果满足以下所有条件,$sample 使用伪随机游标 select 文档:
- $sample 是流水线的第一阶段
- N 小于集合中文档总数的 5%
- 该集合包含超过 100 个文档
如果不满足上述任何条件,$sample 将执行集合扫描,然后对 select N 个文档进行随机排序。在这种情况下,$sample 阶段受排序内存限制。
我相信你的情况mongo进行全面扫描
参考:https://docs.mongodb.com/manual/reference/operator/aggregation/sample/