2012-01-18 24 views
7

Mamy aplikację z bardzo wzajemnie powiązanymi danymi, tj. Istnieje wiele przypadków, w których dwa obiekty mogą odwoływać się do tego samego obiektu poprzez relację. O ile mogę powiedzieć, Django nie podejmuje żadnej próby zwrócenia odwołania do już pobranego obiektu, jeśli spróbujesz go pobrać przez inną, uprzednio nieocenioną relację.Unikanie wielu odniesień do tego samego obiektu w Django ORM

Na przykład:

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

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

Następnie zakładamy mają mamy jednego klienta, który ma dwa zlecenia w 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 

Gdy masz dane silnie powiązane ze sobą, stopień, do którego dostęp relacje Twoje obiekty powodują powtarzające się zapytania do DB, ponieważ te same dane zwiększają się i stają się problemem.

Programujemy również dla iOS i jedną z miłych rzeczy na temat CoreData jest to, że zachowuje on kontekst, dzięki czemu w danym kontekście istnieje tylko jedna instancja danego modelu. W powyższym przykładzie CoreData nie wykonałby drugiego pobrania w (2), ponieważ rozwiązałoby to relację z wykorzystaniem klienta już w pamięci.

Nawet jeśli linia (2) została zastąpiona niepoprawnym przykładem zaprojektowanym do wymuszania kolejnego pobierania DB (jak print Order.objects.exclude(pk = order1.pk).get(customer = order1.customer)), CoreData uświadomiłaby sobie, że wynik tego drugiego pobrania został rozwiązany na modelu w pamięci i zwrócił istniejący model zamiast nowy (tj. (4) wypisze True w CoreData, ponieważ będą one faktycznie tym samym obiektem).

Aby zabezpieczyć się przed tym zachowanie Django, jesteśmy trochę pisanie wszystkie straszne rzeczy, aby spróbować modeli pamięci podręcznej w pamięci ich (type, pk) a następnie sprawdzić relacje z _id przyrostkiem, aby spróbować wyciągnąć je z pamięci podręcznej przed ślepo uderzanie DB z innym pobieraniem. Ogranicza to przepustowość bazy danych, ale wydaje się bardzo krucha i może powodować problemy, jeśli normalne poszukiwanie relacji poprzez właściwości przypadkowo stanie się w jakimś contrib framework lub middleware, którego nie kontrolujemy.

Czy istnieją jakieś dobre praktyki lub struktury dla Django, aby uniknąć tego problemu? Czy ktoś próbował zainstalować jakiś rodzaj lokalnego kontekstu wątku w ORM Django, aby uniknąć powtarzania wyszukiwań i posiadania wielu odwzorowań instancji w pamięci do tego samego modelu bazy danych?

Wiem, że pamięć podręczna, taka jak JohnnyCache, jest tam (i pomaga zmniejszyć przepustowość bazy danych), jednak nadal istnieje problem związany z mapowaniem wielu instancji do tego samego podstawowego modelu, nawet przy zastosowaniu tych środków.

Odpowiedz

2

David Cramer's django-id-mapper to jedna próba zrobienia tego.

+0

Dziękuję Danielowi - nie zaktualizowano go zbyt długo, ale sprawdzę, czy nadal działa. – glenc

+0

Po drobnych badaniach wygląda na to, że to coś może działać, ponieważ w zasadzie ponownie przywiązuje metaclass '__call__' do zwracania buforowanych instancji zamiast nowych, gdy otrzymujesz (cache, pk) trafienia w pamięci podręcznej. To nadal całkowicie opiera się na buforowaniu zapytań, ponieważ nie zapewnia podklasów obiektu ForeignKey, które wiedzą, jak zwracać buforowane wystąpienia przed uruchomieniem prawdziwego zapytania - więc nie jest to w 100% idealne. Prawdopodobnie zaimplementuje ten klucz obcy w widelcu github i opublikuje go tutaj z wynikami. – glenc

1

Istnieje odpowiednia DB optimization page w dokumentacji django; zasadniczo kalki nie są buforowane, ale są to atrybuty (kolejne wywołania do order1.customer nie trafiają do bazy danych), jednak tylko w kontekście ich właściciela obiektu (czyli nie dzielenia się między różnymi zamówieniami).

użyciu buforuje

jak mówisz, Jednym ze sposobów rozwiązania tego problemu jest użycie pamięci podręcznej bazy danych. Używamy bitbucket's johnny cache, który jest prawie całkowicie przezroczysty; kolejną dobrą przezroczystą jest mozilla cache machine. Masz również wybór mniej przejrzystych systemów buforowania, które mogą lepiej pasować do rachunku, zobacz djangopackages/caching.

Dodanie pamięci podręcznej może być bardzo korzystne, jeśli różne żądania wymagają ponownego użycia tego samego klienta; ale proszę, aby zastosować najbardziej przejrzyste systemy pamięci podręcznej do przemyślenia, czy wzór zapisu/odczytu pasuje do takiego systemu buforowania.

optymalizacji wniosków

Inne podejście do swojej precyzyjnej przykład jest użycie select_related.

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

ten sposób obiekt Customer zostanie załadowany od razu w tym samym wniosku sql, z niewielkim kosztem (chyba że jest to bardzo duży rekord) i nie ma potrzeby eksperymentowania z innymi pakietami.

Powiązane problemy