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:
- Entire site caching (every page gets the same treatment)
- Per view caching (You can enable caching per view, and set TTL)
- Low-level caching (where you would cache things manualy)
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}))