MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

O Mongoengine é muito lento em documentos grandes em comparação com o uso nativo do pymongo


TL;DR:o mongoengine está gastando muito tempo convertendo todos os arrays retornados em dicts

Para testar isso, criei uma coleção com um documento com um DictField com um grande dict aninhado . O documento está aproximadamente no seu intervalo de 5 a 10 MB.

Podemos então usar timeit.timeit para confirmar a diferença nas leituras usando pymongo e mongoengine.

Podemos então usar pycallgraph e GraphViz para ver o que está demorando tanto para o mongoengine.

Segue o código na íntegra:
import datetime
import itertools
import random
import sys
import timeit
from collections import defaultdict

import mongoengine as db
from pycallgraph.output.graphviz import GraphvizOutput
from pycallgraph.pycallgraph import PyCallGraph

db.connect("test-dicts")


class MyModel(db.Document):
    date = db.DateTimeField(required=True, default=datetime.date.today)
    data_dict_1 = db.DictField(required=False)


MyModel.drop_collection()

data_1 = ['foo', 'bar']
data_2 = ['spam', 'eggs', 'ham']
data_3 = ["subf{}".format(f) for f in range(5)]

m = MyModel()
tree = lambda: defaultdict(tree)  # http://stackoverflow.com/a/19189366/3271558
data = tree()
for _d1, _d2, _d3 in itertools.product(data_1, data_2, data_3):
    data[_d1][_d2][_d3] = list(random.sample(range(50000), 20000))
m.data_dict_1 = data
m.save()


def pymongo_doc():
    return db.connection.get_connection()["test-dicts"]['my_model'].find_one()


def mongoengine_doc():
    return MyModel.objects.first()


if __name__ == '__main__':
    print("pymongo took {:2.2f}s".format(timeit.timeit(pymongo_doc, number=10)))
    print("mongoengine took", timeit.timeit(mongoengine_doc, number=10))
    with PyCallGraph(output=GraphvizOutput()):
        mongoengine_doc()

E a saída prova que o mongoengine está sendo muito lento comparado ao pymongo:
pymongo took 0.87s
mongoengine took 25.81118331072267

O gráfico de chamadas resultante ilustra claramente onde está o gargalo:



Essencialmente, o mongoengine chamará o método to_python em cada DictField que ele volta do db. to_python é bem lento e em nosso exemplo está sendo chamado um número insano de vezes.

Mongoengine é usado para mapear elegantemente sua estrutura de documento para objetos python. Se você tiver documentos não estruturados muito grandes (para os quais o mongodb é ótimo), o mongoengine não é realmente a ferramenta certa e você deve usar o pymongo.

No entanto, se você conhece a estrutura, pode usar EmbeddedDocument campos para obter um desempenho ligeiramente melhor do mongoengine. Executei um teste semelhante, mas não equivalente, código nesta essência e a saída é:
pymongo with dict took 0.12s
pymongo with embed took 0.12s
mongoengine with dict took 4.3059175412661075
mongoengine with embed took 1.1639373211854682

Então você pode tornar o mongoengine mais rápido, mas o pymongo é muito mais rápido ainda.

ATUALIZAÇÃO

Um bom atalho para a interface pymongo aqui é usar a estrutura de agregação:
def mongoengine_agg_doc():
    return list(MyModel.objects.aggregate({"$limit":1}))[0]