Vincent's Site

Adding Cache to Django

Date: 2024-12-15
Category: Tutorial
Tags: django

Adding a cache can speed up a website a lot, Django makes it verry easy to add a cache. It allows for three types of implementation out fo the box:

I added per-view caching to this blog as a test (my blog doesn't really need caching), and it allowed my 2 core VM to handle 600 requests/second instead of 300/second. This is the case I will demonstrate in this pose. 

Configuring the Cache

Django supports various cache backends, like Memcached, Redis and more, including caching into local memory of the application process (this is handy during development)

Memcached

First you will need to install pymemcache via pip, then you can add a configuration setting like this to your settings.py file

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    }
}

Locmem

I use locmem cache for my development environment, so I don't need to worry about setting up memcached on my dev machine. To use locmem cache you can add the following to your settings.py file

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
    }
}

Adding the decorators

In the views.py file of your application, add the following import:

from django.views.decorators.cache import cache_page

Then you can add a @cache_page decorator to the views you want to cache, in my case I want to cache the page for 3600 seconds (1 hour)

@cache_page(3600)
def index(request):
    post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
    return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))

If you load the page, it will be stored into cache, and subsequent requests will first attemt to fetch the rendered page from the cache. The page will also contain headers to cache the page on the client.

Automatically invalidating the cache on page updates

It can be handy to automatically invalidate the cache when a page updates, so pages still update instantly. I opted to clear the entire cache for simplicity, but you could edit this function to remove only the pages that contain changes.

In your models.py file, you can add the following

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import caches

# this gets triggered both on Page and Post updates
@receiver(post_save, sender=Page)
@receiver(post_save, sender=Post)
def clear_cache(sender, instance, **kwargs):
    cache = caches["default"]
    cache.clear()

If you update a page, the entire cache is cleared and users will see the latest changes instantly.

Not caching the page on the client (or downstream caches)

Sometimes, you want a page to be cached, but not on downstream caches or the client (because you set up a signal receiver to update the cache when a page/post changes, and we want clients to see it instantly). To do this, simply wrap the decorated function with @never_cache like so

from django.views.decorators.cache import cache_page,never_cache

@never_cache
@cache_page(3600)
def index(request):
    post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
    return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))

Alternatively, you can also set custom cache_control headers (in case you want the page to be cached for 1 hour on the server, but only 10 minutes on downstream caches. 

from django.views.decorators.cache import cache_page,cache_control

# cache page for 1 hour, let clients cache the page for 10 minutes, and let any downstream server caches cache the page for 20 minutes
@cache_control(max_age=60*10,s_maxage=60*20)
@cache_page(60*60)
def index(request):
    post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
    return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))

Previous post Next post