2016-02-07 8 views
8

Ich habe folgendes mongoengine Modell:Mongoengine ist sehr langsam auf große Dokumente comapred zu nativem pymongo Nutzung

class MyModel(Document): 
    date  = DateTimeField(required = True) 
    data_dict_1 = DictField(required = False) 
    data_dict_2 = DictField(required = True) 

In einigen Fällen kann das Dokument in der DB können sehr groß sein (etwa 5-10MB) und die data_dict Felder enthalten komplexe verschachtelte Dokumente (Diktatliste, etc ...).

Ich habe zwei (möglicherweise im Zusammenhang) Probleme auftreten:

  1. Wenn ich laufen nativen pymongo find_one() -Abfrage, gibt sie innerhalb einer Sekunde. Wenn ich MyModel.objects.first() starte, dauert es 5-10 Sekunden.
  2. Wenn ich ein einzelnes großes Dokument aus dem DB abzufragen und dann sein Feld zuzugreifen, dauert es 10-20 Sekunden nur Folgendes zu tun:

    m = MyModel.objects.first() 
    val = m.data_dict_1.get(some_key) 
    

Die Daten in dem Objekt nicht der Fall ist Sie enthalten keine Verweise auf andere Objekte. Daher ist es kein Problem der Dereferenzierung von Objekten.
Ich vermute, dass es mit einer Ineffizienz der internen Datendarstellung von Mongoengine verbunden ist, die die Dokumentobjektkonstruktion sowie den Feldzugriff beeinflusst. Kann ich etwas tun, um dies zu verbessern?

+0

Es gibt zwei verschieden scheint Dinge. Native Methode lädt 1 Dokument und stoppt, während Mongoengine alle Dokumente lädt und die erste zurückgibt. Versuchen Sie, von 'find_one()' zu 'list (db.collection.find()) [0]' zu gleichen Methoden zu wechseln. – Valijon

+0

Ich habe auch versucht, Limit (1) in der Mongoengine Abfrage hinzuzufügen, es hat nicht geholfen. Scheint, dass die meiste Zeit damit verbracht wird, das mongoengine Document-Objekt mit allen verschachtelten Objekten zu erstellen. –

+0

'skip' und' limit' funktionieren, sobald Dokumente geladen sind :(Versuche nach spezifischen Abfragen zu filtern ... [http://docs.mongoengine.org/guide/querying.html#query-operators](http://docs.mongoengine.org/guide/querying.html#query-operators) – Valijon

Antwort

20

TL; DR: mongoengine verbringt Alter aller zurückgegebenen Arrays dicts

Umwandlung Um dies zu testen, ich eine Sammlung mit einem Dokument mit einem DictField mit einer großen verschachtelten dict gebaut. Das Dokument liegt ungefähr im Bereich von 5-10 MB.

Wir können dann timeit.timeit verwenden, um den Unterschied in Lesevorgänge mit Pymongo und Mongoengine zu bestätigen.

Wir können dann pycallgraph und GraphViz verwenden, um zu sehen, was mongoengine so verdammt lange dauert. Hier

ist der Code in voller Länge:

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() 

Und der Ausgang beweist, dass mongoengine ist sein sehr langsam im Vergleich zu pymongo:

pymongo took 0.87s 
mongoengine took 25.81118331072267 

Das resultierende Aufrufdiagramm zeigt recht deutlich, wo der Flaschenhals ist:

pycallgraph.png for mongoengine read of large doc hot spot in pycallgraph

Im Wesentlichen wird Mongoengine die Methode to_python auf jeder DictField aufrufen, die es von der db zurückerhält. to_python ist ziemlich langsam und in unserem Beispiel wird es eine wahnsinnige Anzahl von Zeiten genannt.

Mongoengine wird verwendet, um Ihre Dokumentstruktur elegant Python-Objekten zuzuordnen. Wenn Sie sehr große unstrukturierte Dokumente haben (für die mongodb ist großartig), dann Mongoengine ist nicht wirklich das richtige Werkzeug und Sie sollten nur Pymongo verwenden.

Wenn Sie jedoch die Struktur kennen, können Sie EmbeddedDocument Felder verwenden, um etwas bessere Leistung von Mongoengine zu bekommen.Ich habe einen ähnlichen, aber nicht gleichwertig Test laufe code in this gist und der Ausgang ist:

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

So können Sie mongoengine schneller machen, aber pymongo ist noch viel schneller.

UPDATE

Eine gute Verknüpfung zum pymongo Schnittstelle hier ist die Aggregation Framework zu verwenden:

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

Ausgezeichnete Analyse, vielen Dank! (Ich hätte es tun sollen.) mich selber !). Ich bin mir nicht sicher, ob Mongoengine alle eingebetteten Daten im DictField-Format speichern oder zumindest vermeiden kann. Ich habe ein Problem auf Mongoengine Github geöffnet, hoffe, es wird helfen: https://github.com/MongoEngine/mongoengine/issues/1230 –

+0

Danke, ja es scheint wie Sie in der Lage sein sollten, ein Feld als generische Pymongo-Referenz anzugeben. –

+0

Ihre Antwort ist so hilfreich. Danke für das Teilen. – Rahman