MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Mongoengine è molto lento su documenti di grandi dimensioni rispetto all'utilizzo nativo di pymongo

TL;DR:mongoengine sta impiegando anni a convertire tutti gli array restituiti in dict

Per testarlo ho creato una raccolta con un documento con un DictField con un grande dict annidato . Il documento è all'incirca nel tuo intervallo di 5-10 MB.

Possiamo quindi utilizzare timeit.timeit per confermare la differenza nelle letture usando pymongo e mongoengine.

Possiamo quindi utilizzare pycallgraph e GraphViz per vedere cosa sta impiegando mongoengine da così tanto tempo.

Ecco il codice per intero:

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 l'output dimostra che mongoengine è molto lento rispetto a pymongo:

pymongo took 0.87s
mongoengine took 25.81118331072267

Il grafico delle chiamate risultante illustra abbastanza chiaramente dove si trova il collo di bottiglia:

Essenzialmente mongoengine chiamerà il metodo to_python su ogni DictField che torni dal db. to_python è piuttosto lento e nel nostro esempio viene chiamato un numero folle di volte.

Mongoengine viene utilizzato per mappare elegantemente la struttura del documento su oggetti Python. Se hai documenti non strutturati molto grandi (per i quali mongodb è ottimo), mongoengine non è proprio lo strumento giusto e dovresti semplicemente usare pymongo.

Tuttavia, se conosci la struttura puoi usare EmbeddedDocument campi per ottenere prestazioni leggermente migliori da mongoengine. Ho eseguito un test simile ma non equivalente codice in questo gist e l'output è:

pymongo with dict took 0.12s
pymongo with embed took 0.12s
mongoengine with dict took 4.3059175412661075
mongoengine with embed took 1.1639373211854682

Quindi puoi rendere mongoengine più veloce ma pymongo è ancora molto più veloce.

AGGIORNAMENTO

Una buona scorciatoia per l'interfaccia di pymongo qui è usare il framework di aggregazione:

def mongoengine_agg_doc():
    return list(MyModel.objects.aggregate({"$limit":1}))[0]