1

I'm currently working this website. The problem is that the pages takes 3-4 seconds to get the first bye, and I think it's because query to load data in the pages is very slow.

In the store page, it basically uses an object of a store to show the store's basic information and uses ForeignKey relation to access the store's images. In addition, it uses ManyToManyField to access the store's similar stores which are a store object as well. For this solved, I used prefetch_related and select_related so minimized many of duplicated queries. But still it shows a low performance.

After that I thought, I could improve it with caching, so I did the below. To be honest, what I expected with caching was like super fast loading as it is supposed to store all the processed queries and just show the data from the requested cache. But, to be honest, it still shows 3-4 seconds loading performance. Am I using the cache in a wrong way?

settings.py

...
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'django_cache',
    }
}
...

views.py

class StoreDetailView(View):
    def get(self, request, *args, **kwargs):
        store_domainKey = self.kwargs['store_domainKey']

        cache_store = 'store-{0}'.format(store_domainKey)
        store = cache.get(cache_store, None)
        if not store:
            try:
                q = (
                    Store.objects
                    .select_related('price_range')
                    .prefetch_related(
                        'top_keywords', 'sub_keywords', 'ship_to', 'likes',
                        'question_set', 'question_set__answer_set',
                        'similar_stores', 'sponsored_stores', 'image_set'
                    )
                    .get(domainKey=store_domainKey)
                )
            except Store.DoesNotExist:
                raise Http404
            cache.set(cache_store, q)
            store = cache.get(cache_store)


        cache_similar_stores = 'similar-stores-{0}'.format(store_domainKey)
        similar_stores = cache.get(cache_similar_stores, None)
        if not similar_stores:
            sponsored_stores_ids = sponsored_stores.values_list('sponsor_receiver__id', flat=True)
            q = (
                Similarity.objects
                .select_related('similar_store', 'similar_store__price_range')
                .prefetch_related('similar_store__image_set')
                .filter(similar_target=store)
                .exclude(Q(similar_store__id__in=sponsored_stores_ids) | Q(similar_store=store))
            )
            cache.set(cache_similar_stores, q)
            similar_stores = cache.get(cache_similar_stores)


        page = request.GET.get('page', 1)
        paginator = Paginator(similar_stores, 5)
        try:
            similar_stores = paginator.page(page)
        except PageNotAnInteger:
            similar_stores = paginator.page(1)
        except EmptyPage:
            similar_stores = paginator.page(paginator.num_pages)


        context = {
            'store': store,
            'sponsored_stores': sponsored_stores,
            'similar_stores': similar_stores,
            'top_3_similar_store_string': top_3_similar_store_string,
        }


        return render(request, template, context)
Jay P.
  • 2,420
  • 6
  • 36
  • 71
  • 1
    Look into python profiling. You shouldn't optimise code without first working out exactly what is making it slow - otherwise you're probably just wasting your own time. – Shadow Jun 21 '19 at 00:46

1 Answers1

3

The comment above about profiling is definitely worth considering, but to answer the question, I normally cache generic class based views with a mixin. For example:

from django.views.decorators.cache import cache_page
from django.views.generic import View

class CacheMixin(object):
    cache_timeout = 3600 * 24 * 7

    def get_cache_timeout(self):
        return self.cache_timeout

    def dispatch(self, *args, **kwargs):
        return cache_page(self.get_cache_timeout())(super().dispatch)(*args, **kwargs)

class StoreDetailView(CacheMixin, View):
    def get(self, request, *args, **kwargs):
    ...

You can also cache at the template level with fragment caching, but have to be careful to account for any variables in the context as part of the template fragment identifier.

Django Debug Toolbar is very helpful in identifying problematic queries and their source.

Good luck!

FlipperPA
  • 13,607
  • 4
  • 39
  • 71
  • This is exactly what I was looking for, thanks! By the way, when I use `from django.core.cache import cache`, I could delete the cache when I save or delete an object in Django admin by doing `cache.delete(STORE_ID)` or something like that. Does cache_page supports that too? I tried to look it up, but didn't find anything useful – Jay P. Jun 21 '19 at 18:31
  • Unfortunately, I don't think there's an easy way to do that without looking up the cache key and using `cache.delete()`. If you found the answer helpful, can you mark it correct? I'm glad it works for you! – FlipperPA Jun 22 '19 at 22:03
  • sure! it already helped me a lot and thanks for checking if there's a way to delete page cache too! – Jay P. Jun 23 '19 at 23:25