2016-02-07 11 views
8

Ho il seguente modello mongoengine:Mongoengine è molto lenta su documenti di grandi dimensioni a paragone di utilizzo pymongo nativo

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

In alcuni casi il documento nel DB può essere molto grande (circa 5-10 MB), e il i campi data_dict contengono documenti nidificati complessi (dict di liste di dicts, ecc ...).

ho incontrato due (possibilmente correlate) questioni:

  1. Quando ho Esegui query nativa pymongo find_one(), restituisce in un secondo. Quando eseguo MyModel.objects.first() ci vogliono 5-10 secondi.
  2. Quando interrogo un unico documento di grandi dimensioni dal DB, e quindi accedere suo campo, ci vogliono 10-20 secondi solo per effettuare le seguenti operazioni:

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

I dati in oggetto non lo fa contenere riferimenti a qualsiasi altro oggetto, quindi non è un problema di dereferenziazione di oggetti.
Sospetto che sia correlato a una qualche inefficienza della rappresentazione dei dati interni di mongoengine, che influisce sulla costruzione dell'oggetto documento e sull'accesso ai campi. C'è qualcosa che posso fare per migliorare questo?

+0

Sembra che ci sono 2 cose diverse. Il metodo nativo carica 1 documento e si ferma, mentre mongoengine carica tutti i documenti e restituisce il primo. Prova a cambiare da 'find_one()' a 'lista (db.collection.find()) [0]' a metodi uguali. – Valijon

+0

Ho anche provato ad aggiungere il limite (1) nella query di mongoengine, non ha aiutato. Sembra che la maggior parte del tempo viene speso per costruire l'oggetto mongoengine documento, con tutti gli oggetti nidificati .. –

+0

'' skip' e lavoro limit' una volta i documenti vengono caricati :(Prova a filtrare per query specifica ... [http://docs.mongoengine.org/guide/querying.html#query-operators](http://docs.mongoengine.org/guide/querying.html#query-operators) – Valijon

risposta

20

TL; DR: mongoengine sta spendendo età la conversione di tutti gli array restituiti al dicts

Per testare questo fuori ho costruito una collezione con un documento con una DictField con un grande nidificato dict. Il documento è all'incirca nella tua gamma 5-10 MB.

Possiamo quindi utilizzare timeit.timeit per confermare la differenza di letture utilizzando pymongo e mongoengine.

Possiamo quindi utilizzare pycallgraph e GraphViz per vedere cosa sta prendendo mongoengine così dannatamente lungo.

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

pymongo took 0.87s 
mongoengine took 25.81118331072267 

Il grafico delle chiamate risultante illustra abbastanza chiaramente dove il collo della bottiglia è:

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

In sostanza mongoengine chiamerà il metodo to_python su ogni DictField che torna 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 agli oggetti Python. Se disponi di documenti non strutturati molto grandi (a cui mongodb è utile), la mongoengine non è davvero lo strumento giusto e dovresti semplicemente usare pymongo.

Tuttavia, se si conosce la struttura, è possibile utilizzare i campi EmbeddedDocument per ottenere prestazioni leggermente migliori rispetto a mongoengine.Ho eseguito un test simile ma non equivalente code in this 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 

modo da poter rendere più veloce mongoengine ma pymongo è molto più veloce ancora.

UPDATE

Un buon collegamento all'interfaccia pymongo qui è quello di utilizzare il quadro di aggregazione:

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

un'eccellente analisi, grazie mille! (avrei dovuto farlo me stessa !). Non sono sicuro che sia saggio per mongoengine conservare tutti i dati incorporati nel formato DictField, o almeno avere un modo per evitarlo. Ho aperto un problema su github di mongoengine, spero che vi aiuterà: https://github.com/MongoEngine/mongoengine/issues/1230 –

+0

Grazie, sì, sembra che si dovrebbe essere in grado di specificare un campo come riferimento pymongo generico. –

+0

La tua risposta è molto utile. grazie per averlo condiviso – Rahman

Problemi correlati