2012-01-18 19 views
7

Abbiamo un'app con dati altamente correlati, vale a dire che ci sono molti casi in cui due oggetti possono riferirsi allo stesso oggetto tramite una relazione. Per quanto posso dire, Django non fa alcun tentativo di restituire un riferimento a un oggetto già recuperato se si tenta di recuperarlo tramite una relazione diversa, precedentemente non valutata.Evitare più riferimenti allo stesso oggetto in Django ORM

Ad esempio:

class Customer(Model): 
    firstName = CharField(max_length = 64) 
    lastName = CharField(max_length = 64) 

class Order(Model): 
    customer = ForeignKey(Customer, related_name = "orders") 

Allora supponiamo di avere un singolo cliente che ha due ordini nel DB:

order1, order2 = Order.objects.all() 
print order1.customer # (1) One DB fetch here 
print order2.customer # (2) Another DB fetch here 
print order1.customer == order2.customer # (3) True, because PKs match 
print id(order1.customer) == id(order2.customer) # (4) False, not the same object 

Quando si dispone di dati altamente interconnessi, il grado in cui l'accesso ai rapporti di i tuoi oggetti risultano in interrogazioni ripetute del DB per gli stessi aumenti di dati e diventa un problema.

Programmiamo anche per iOS e una delle cose belle di CoreData è che mantiene contesto, in modo che in un dato contesto ci sia sempre una sola istanza di un dato modello. Nell'esempio precedente, CoreData non avrebbe eseguito il secondo recupero in (2), poiché avrebbe risolto la relazione utilizzando il cliente già in memoria.

Anche se la riga (2) è stata sostituita con un esempio spuria progettato per forzare un altro recupero di database (come print Order.objects.exclude(pk = order1.pk).get(customer = order1.customer)), CoreData si renderebbe conto che il risultato di tale secondo recupero è stato risolto in un modello in memoria e restituisce il modello esistente anziché uno nuovo (es. (4) stamperebbe True in CoreData perché sarebbe effettivamente lo stesso oggetto).

di copertura contro questo comportamento di Django, stiamo un po scrivendo tutta questa roba orribile per cercare di modelli di cache in memoria per la loro (type, pk) e quindi controllare le relazioni con il suffisso _id per cercare di tirarli dalla cache prima di colpire ciecamente la DB con un altro recupero. Questo sta riducendo il throughput di DB, ma si sente davvero fragile e probabilmente causa problemi se le normali ricerche di relazioni tramite proprietà avvengono accidentalmente in alcuni framework di contrib o middleware che non controlliamo.

Esistono best practice o framework per Django per evitare questo problema? Qualcuno ha tentato di installare una sorta di contesto thread-locale nell'ORM di Django per evitare ripetute ricerche e avere più istanze in memoria che si collegano allo stesso modello DB?

So che roba da cache di query come JohnnyCache è disponibile (e aiuta a ridurre il throughput del DB), tuttavia esiste ancora il problema di istanze multiple che mappano allo stesso modello sottostante anche con quelle misure in atto.

risposta

2

David Cramer's django-id-mapper è un tentativo di farlo.

+0

Grazie Daniel - non è stato aggiornato in un tempo spaventosamente lungo, ma ci provo e vediamo se funziona ancora. – glenc

+0

Quindi, dopo una piccola ricerca, sembra che questa cosa potrebbe funzionare, perché fondamentalmente riaggancia il metaclass '__call__' per restituire le istanze memorizzate nella cache invece di nuove quando si ottengono hit della cache (type, pk). Questo comunque si basa totalmente sul caching delle query, perché non fornisce sottoclassi di ForeignKey che sanno come restituire le istanze memorizzate nella cache prima di lanciare una query reale, quindi non ideale al 100%. Probabilmente implementerà questo ForeignKey in un fork di github e pubblicherà qui i risultati. – glenc

1

C'è un documento rilevante DB optimization page nella documentazione di django; in pratica le callebles non vengono memorizzate nella cache, ma gli attributi sono (le successive chiamate a order1.customer non colpiscono il database), ma solo nel contesto del proprietario dell'oggetto (quindi, non condividendo tra diversi ordini).

utilizzando la cache

Come dici tu, un modo per risolvere il problema è quello di utilizzare una cache del database. Usiamo bitbucket johnny cache, che è quasi completamente trasparente; un'altra buona trasparenza è cache machine di mozilla. Hai anche la possibilità di scegliere sistemi di caching meno trasparenti che potrebbero effettivamente adattarsi meglio alla bolletta, vedi djangopackages/caching.

L'aggiunta di una cache può effettivamente essere molto utile se richieste diverse devono riutilizzare lo stesso Cliente; ma si prega di read this che si applica alla maggior parte dei sistemi di cache trasparenti per riflettere se il proprio modello di scrittura/lettura si adatta a un sistema di memorizzazione nella cache.

ottimizzando le richieste

Un altro approccio per il tuo esempio preciso è quello di utilizzare select_related.

order1, order2 = Order.objects.all().select_related('customer') 

In questo modo l'oggetto Customer verrà caricato immediatamente nella stessa richiesta SQL, con poco costo (a meno che non si tratta di un grande record) e non c'è bisogno di sperimentare con altri pacchetti.

Problemi correlati