2014-05-23 13 views
6

Sto lavorando per ottimizzare un'applicazione Django che è (principalmente) supportata da MongoDB. Sta morendo sotto test di carico. Nella pagina problematica corrente, New Relic mostra oltre 700 chiamate allo pymongo.collection:Collection.find. Gran parte del codice è stata scritta da programmatori junior e normalmente cercherò luoghi in cui aggiungere indici, creare join più intelligenti e rimuovere i loop per ridurre le chiamate di query, ma i join non sono un'opzione qui. Quello che ho fatto (dopo aver aggiunto gli indici basati su EXPLAIN) è provato a ridurre il costo nei loop facendo una query generale e quindi filtrando quella piccola serie nei loop *. Mentre ho ottenuto il numero da 900 query, 700 sembra ancora folle anche con l'intenso lavoro svolto sulla pagina. Ho pensato che forse find è stato chiamato anche durante il filtraggio di un queryset esistente, ma il codice suggerisce che è sempre un database query.Ridurre il numero di chiamate a MongoDB con mongoengine

Ho aggiunto alcune registrazioni a mongoengine per vedere da dove provengono le query e per dare un'occhiata alle istruzioni EXPLAIN, ma non sto avendo un sacco di fortuna spulciando il muro delle informazioni. la stessa mongoengine sembra essere parte del problema delle prestazioni: sono passato a mongomallard come test e ho ottenuto un miglioramento delle prestazioni del 50% sulla pagina. Purtroppo ho ricevuto degli errori su un sacco di altre pagine (come posso dire che sembra che Mallard non funzioni bene quando filtra un queryset esistente, l'errore si lamenta di una chiamata a deepcopy che avviene in un generatore, che non è possibile do-- Ho colpito un muro di mattoni lì). Sebbene Mallard non sembri un rimpiazzo praticabile per noi, suggerisce che gran parte del tempo di proiezione è dedicato alla conversione di oggetti da e verso Python in mongoengine.

Cosa posso fare per ridurre ulteriormente le chiamate? O mi sto concentrando sulla cosa sbagliata e dovrei attaccare il problema da qualche altra parte?

EDIT: fornendo alcuni codici/modelli

La pagina in questione mostra il programma per un corso, che mostra tutti i moduli del corso, le lezioni ei concetti sotto le lezioni. Per ogni concetto, viene mostrato anche il progresso dell'utente nel concetto. Quindi c'è un sacco di loop per ottenere la gerarchia presa in giro (e non è memorizzata secondo uno qualsiasi dei patterns the Mongo docs suggest).

class CourseVersion(Document): 
    ... 
    course_instances = ListField(ReferenceField('CourseInstance')) 
    courseware_containers = ListField(EmbeddedDocumentField('CoursewareContainer')) 

class CoursewareContainer(EmbeddedDocument): 
    id = UUIDField(required=True, binary=False, default=uuid.uuid4) 
    .... 
    courseware_containers = ListField(EmbeddedDocumentField('self')) 
    teaching_element_instances = ListField(StringField()) 

del corso moduli, lezioni e concetti sono memorizzati in courseware_containers; abbiamo bisogno di ottenere tutti i concetti in modo da poter ottenere l'elenco degli ID in teaching_element_instances per trovare quello più recente su cui l'utente ha lavorato (se esiste) per quel concetto e quindi cercare i loro progressi.

* Giusto per essere chiari, sto usando un profiler e guardo alle cose degli eventi e delle cose Il modo giusto come meglio lo so, non semplicemente cambiando le cose e sperando per il meglio.

+0

un sacco di chiamate suona come un'implementazione del modello "di riferimento" piuttosto che "embedded" a me. Ma non il giusto tipo di informazioni nella tua domanda per chiunque per determinare questo. Da una prospettiva di programmazione che è. –

+0

Non hai pubblicato alcun codice quindi sto solo cercando di indovinare, ma se esegui un loop su un set di query Django e accedi a una chiave esterna sulle istanze del modello in quel ciclo, viene attivata una query 'find' in quel punto. Hai provato http://docs.mongoengine.org/apireference.html#mongoengine.Document.select_related? fa ancora richieste aggiuntive ma potrebbe essere un po 'più ottimizzato http://stackoverflow.com/a/16166970/202168 – Anentropic

+0

Senza * qualsiasi * codice questo è impossibile eseguire il debug. Ma come detto sopra uso 'select_related' per il recupero efficiente delle relazioni. Un 'find()' non è una query poiché è un cursore lazy - solo quando è iterato diventa una query a MongoDB. Se si stanno eseguendo cicli in cui si filtrano i risultati, si ripetono i risultati filtrati, quindi si tratta di almeno una query, se esistono relazioni nidificate che esploderanno il numero di query. Potrebbe essere più efficiente ottenere il cast del singolo set di risultati in un elenco, quindi filtrare usando python. – Ross

risposta

9

L'esempio di codice non è negativo per-sae, ma ci sono alcune aree che dovrebbero essere considerate e possono aiutare a migliorare le prestazioni.

class CourseVersion(Document): 
    ... 
    course_instances = ListField(ReferenceField('CourseInstance')) 
    courseware_containers = ListField(EmbeddedDocumentField('CoursewareContainer')) 

class CoursewareContainer(EmbeddedDocument): 
    id = UUIDField(required=True, binary=False, default=uuid.uuid4) 
    .... 
    courseware_containers = ListField(EmbeddedDocumentField('self')) 
    teaching_element_instances = ListField(StringField()) 

recensione

  1. liste non limitati.
    course_instances, courseware_containers, teaching_element_instances

    Se questi campi sono illimitati e continuamente crescere allora il documento si sposterà su disco man mano che cresce, causando contesa disco su sistemi fortemente caricati.Esistono due modelli per ridurre al minimo questo:

    a) Turn on Power of two sizes. Ciò costerà lo spazio su disco ma dovrebbe diminuire la quantità di io churn man mano che il documento cresce

    b) Riempimento iniziale: inserire il documento sull'inserto in modo personalizzato, quindi inserirlo in misura maggiore e quindi rimuovere il riempimento. Davvero un modello anti, ma potrebbe darti un po 'di chilometraggio.

    La barriera finale è la dimensione massima del documento - 16 MB non è possibile far crescere i dati più grandi di così.

  2. Elenchi di ReferenceFields - course_instances

    MongoDB non ha join in modo che costa una query in più per cercare un ReferenceField - essenzialmente sono un app in join. Che non è male per-sae, ma è importante capire il compromesso. Per impostazione predefinita, mongoengine non invierà automaticamente il campo solo a course_version.course_instances eseguirà un'altra query e quindi compila l'intero elenco di riferimenti. Quindi può costare un'altra query - se non hai bisogno dei dati allora exclude() dalla query per fermare qualsiasi query che perde.

  3. EmbeddedFields

    Questi campi sono parte del documento, quindi non c'è alcun costo per loro, oltre ai costi di filo di trasmissione e il caricamento dei dati. ** Poiché fanno parte del documento, non è necessario il numero select_related per ottenere questi dati.

  4. teaching_element_instances

    Sono questi un elenco di id? Dice che è un StringField nell'esempio di codice qui sopra. In entrambi i casi, se non è necessario rimuovere la notifica dell'intero elenco, memorizzare come StringField e manualmente il dereferenziamento potrebbe essere più efficace se codificato correttamente, soprattutto se è necessario solo l'ultimo (ultimo?) Id.

  5. complessità del modello

    Il CoursewareContainer è complessa. Per ogni dato CourseVersion avete nCoursewareContainers con se stessi hanno una lista di n contenitori e quelli hanno ciascuno n contenitori e su ...

  6. Trovare i più recenti casi

    Dobbiamo ottenere tutte le concetti in modo da poter ottenere l'elenco di id in teaching_element_instances per trovare il più recente l'utente ha lavorato su (se presente) per tale concetto e quindi cercare i loro progressi.

    Non sono sicuro se è presente una singola istanza o una per Container o una per Corso. In entrambi i casi, la logica per l'interrogazione dei dati dovrebbe essere esaminata. Se si tratta di una singola istanza, è possibile archiviarla contro l'utente in modo da semplificare la logica della ricerca. Se il suo corso o contenitore, quindi, per migliorare le prestazioni, assicurati di ridurre al minimo il numero di query - se possibile raccogliere tutto il numero ids e poi emettere una singola query $in, anziché eseguire una query per contenitore.costa

  7. Mongoengine

    Attualmente, v'è un costo delle prestazioni per il caricamento dei dati in classi Mongoengine - se non hai bisogno di classi e sono felice di lavorare con i dizionari semplici poi o emettere una query pymongo crudo o utilizzare as_pymongo.

  8. disegno dello schema

    Lo schema sembra abbastanza logico, ma è adatto per il caso d'uso - in sostanza sta utilizzando punti di forza di MongoDB o è mettendo un piolo relazionale in un database di documenti a forma di buco? Non posso rispondere che per te, ma so che la via verso il percorso felice con MongoDB è la progettazione dello schema in base al suo caso d'uso. Con la progettazione di schemi di database relazionali fin dall'inizio è semplice - si normalizza, con i database di documenti come vengono utilizzati i dati è un fattore primario.

  9. MongoDB migliori pratiche

    ci sono molte altre buone pratiche e MongoDB avere una guida che potrebbe essere di interesse: MongoDB Operations Best Practices.

esitate a contattarmi tramite il Mongoengine mailing list per discutere ulteriormente e se necessario discutere in privato.

Ross

+0

Wow, cose fantastiche, grazie! – Tom

Problemi correlati