A Comprehensive Guide to Django Caching

    Khumbo Klein Chilamwa
    Share

    In this article, you’ll get a comprehensive understanding of caching in Django and web development as a whole. You’ll learn about what caching is, the benefits of caching, how to set up caching in Django, the backend systems that Django supports, and the best practices of caching.

    By the end of the article, as a backend web developer, you’ll have a solid understanding of caching and how you can implement it in your Django projects for the best performance.

    The Django framework is one of the most popular backend frameworks used by web developers these days. As a developer, building web applications with high performance should be one of the goals on your list. So building a web application in Django for high performance shouldn’t be a hassle, since Django comes with a caching framework that allows you to use various caching backends like in-memory cache, file-based cache, database cache, and more.

    Table of Contents

    Introduction to Django Caching

    Caching in web development is a technique used by web developers to improve the performance of web applications. Every web application possesses resources that its users consistently desire to utilize. A resource could be anything from a straightforward web page to data stored in a database. Caching plays a significant role in maximizing the speed at which resources are accessed.

    Common caching scenarios and use cases

    The Django caching framework provides a wide range of scenarios and use cases that enable a developer to cache frequently accessed data. The following are the common caching scenarios:

    • Page Caching. This is a scenario where frequently visited pages on a site are entirely cached. The application fetches the cached versions of the pages and renders them when a request is made, as opposed to creating the content of the page from scratch.

    • Database Query Caching. Database query caching is a good approach for applications that depend on frequently requesting the database for information. A series of requests can be satisfied by the same piece of cached data without the need to hit the database, hence lowering database hits and speeding up server response time.

    • Session and Authentication Caching. Caching may be used to offer a streamlined user experience and a quicker response time. Since cached data will enable users to move easily across the authenticated sections of an application, caching authentication and session details can help to reduce redundant authentication operations.

    Benefits of Django caching

    Caching has grown more advantageous to web developers in this modern environment where data is of the essence and speed is more essential. These are some of the benefits of caching:

    • Improved Performance. Caching enhances the performance of web applications by reducing server load. When the server gets requests from the application, Caching ensures that some requests are satisfied using previously cached data. This prevents the server from having to conduct those operations from scratch. As a result, the users’ experience is improved overall, and reaction times are sped up.

    • Reduced response time. Caching minimizes response time by reducing database hits. Caching enables the data to be fetched from a convenient location rather than directly from the database each time it is needed. Since some data need pricey calculations that can take some time to complete, fetching data every time it is needed from the database might not be the best choice for all data. By saving the data and promptly making it available whenever needed, caching saves the day.

    • Resource Optimization. Caching data or resources in a web application, allows resources to be accessible and retrievable without performing repetitive operations that output the same resources.

    Setting up A Django Project

    The main tasks in this section are to create the virtual environment and install the required modules for the project. To create the virtual environment, enter this command in your terminal:

    $ python -m venv project
    

    All the modules for the project will be installed inside this environment, so to start using it, we need to activate it.

    On Windows use this:

    $ .\project\Scripts\activate
    

    And on macOS or Linux, use this:

    $ source project/bin/activate
    

    Before we implement caching, the first thing to do is set up a Django project. So let’s first install Django. Open your terminal, and run this command:

    $ pip install django
    

    After successfully installing Django, let’s create a Django project and application. To create a Django project run:

    $ django-admin startproject cachedproject
    

    Navigate into the project’s folder. Here, we’ll create a Django application. Run this command:

    $ cd cachedproject
    

    And then run this:

    $ python manage.py startapp cachedapplication
    

    After successfully creating the project and the application, we have to register the application to the project. Open the settings.py file, and make the INSTALLED_APPS list look like this:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # new application added
        'cachedapplication',
    ]
    

    Note: to use the application in your Django project, it must be registered in the INSTALLED_APPS list.

    Now, to verify that the Django framework has been installed successfully, let’s test it. In the terminal, run this command:

    $ python manage.py runserver
    

    Make sure you get the output pictured below.

    terminal output

    Now copy the URL and paste it into the web browser. The expected output is pictured below.

    installed successfully message

    Note: you can read more about quickly starting a Django project and a Django app on SitePoint.

    Configuring Different Caching Settings in settings.py

    To use caching, we need to configure it in our Django project, so in this section, we’ll look at how we can configure different caching systems available in Django.

    Local memory caching

    As its name implies, Local Memory Cache, sometimes abbreviated as locmem, stores cached data in the RAM of the hosting machine.

    Django automatically uses local memory caching as the default caching system if you don’t provide an alternate caching mechanism in your settings.py file. Its thread-safe, quick, and responsive caching technique is notable.

    Local Memory Cache is less appropriate for use in production environments, since it includes a per-process mechanism that prevents any kind of cross-process caching and makes individual processes maintain separate private cache instances. It’s still a good choice, nevertheless, for development.

    To configure local memory caching in your application, add the following code in the settings.py file:

    # CACHES dictionary which contains caching configurations.
    CACHES = {
        # a cache alias or name. In this case, we use "default" as the alias.
        "default": {
            # Here, we're using the in-memory cache backend.
            "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
    
            # LOCATION parameter gives a unique name or identifier to this cache instance.
            "LOCATION": "unique-snowflake",
        }
    }
    

    File-based caching

    In file-based caching, each cache value is serialized and kept separately in a file when caching. It’s useful for small-scale applications or where memory-based caching is not practical.

    The downside of this caching system is that it’s relatively slower compared to memory-based caching.

    To configure file-based caching in your application, add the following code in the settings.py file:

    # A CACHES dictionary, which contains caching configurations.
    CACHES = {
        # we use "default" as the alias.
        "default": {
            # Here, we're using the file-based cache backend.
            "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
    
            # LOCATION parameter to specify the file system path where cached data will be stored.
            "LOCATION": "/var/tmp/django_cache",
        }
    }
    

    If you’re on Windows, make sure you start the location’s path with the respective drive letter like this:

    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
            "LOCATION": "C:/my/path-to/file",
        }
    }
    

    Database caching

    Apart from storing caches in files and hosting the machine’s RAM, Django also provides the ability to store the cache in a database.

    This works best if you’ve got a fast, well-indexed database server.

    To use database caching in your application, add the following code inside the settings.py:

    # CACHES dictionary, which contains caching configurations.
    CACHES = {
        # we use "default" as the alias.
        "default": {
            # Here, we're using the database-backed cache backend.
            "BACKEND": "django.core.cache.backends.db.DatabaseCache",
    
            # Provide a LOCATION parameter to specify the database table name where cached data will be stored.
            "LOCATION": "my_cache_table",
        }
    }
    

    Use the following command to construct the database table mentioned in the setup above before using the cache:

    python manage.py createcachetable
    

    The command above creates a table in the database in a proper format that Django’s database cache system expects. The name of the table is taken from the LOCATION. In this case, the table’s name will be my_cache_table.

    Memcached caching

    Memcached is an in-memory caching system that’s used by web developers even in several popular companies to reduce database access and increase site performance.

    In contrast to the locmem cache, Memcached operates as a daemon, which implies that the Memcached server runs as a background process, independently of any direct user interaction. Memcached must therefore be installed separately on your machine. Then in your Django application, install and configure one of its bindings, such as pylibmc or pymemcache, to use Memcached.

    A Django application can be linked to a Memcached daemon by adding the cache settings, location, IP address, and other details, as shown below:

    # A dictionary named CACHES, which contains caching configurations.
    CACHES = {
        # "default" is the alias.
        "default": {
            # Here, we're using the Memcached cache backend.
            "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
    
            # LOCATION parameter to specify the Memcached server's IP address and port.
            "LOCATION": "127.0.0.1:11211",
        }
    }
    

    Memcached technology is especially suited for applications with high read or query loads, as it was designed for fast data storage and retrieval.

    The downside of Memcached is that, since the data is stored in memory, it will be lost if your server crashes.

    Redis

    Redis is an in-memory database that can be used for caching. It caches data using the Redis in-memory data storage. Redis is renowned for its quickness and adaptability, making it a great option for distributed systems and high-performance caching.

    To cache data using Redis in your application, you need a Redis server running either locally or on a remote machine. To set up Redis on your machine, read the Redis getting started guide.

    After setting up the Redis server, you’ll need to install Python bindings for Redis. Use this command to install it:

    $ pip install django-redis
    

    The redis-py interface is the binding supported natively by Django or using the django-redis and redis packages.

    To configure Redis caching in your application, given that your Redis server is running on localhost (127.0.0.1), port=6379, add the following code in the settings.py:

    # A dictionary named CACHES, which contains caching configurations.
    CACHES = {
        # "default" is the alias.
        "default": {
            # Here, we're using the Redis cache backend.
            "BACKEND": "django.core.cache.backends.redis.RedisCache",
    
            # A LOCATION parameter to specify the Redis server's address and port.
            "LOCATION": "redis://127.0.0.1:6379",
        }
    }
    

    Redis’ complexity and reliance on outside services are its trade-offs. Redis installation and configuration could be trickier than with certain other cache backends. It needs a second server and upkeep when online. Redis usage creates a reliance on an outside service. Your application’s caching capabilities may be affected if Redis has problems or goes down.

    Performing Caching in Django Using Redis

    Okay, that’s enough theory. In this section, we’ll demonstrate how to perform caching in a Django application. We’ll focus on three forms of caching:

    • view caching
    • template fragment caching
    • per-site caching

    But before we perform each caching form in detail, let’s first set up the project properly. We’ll create, register, and populate the models, configure the application’s URLs, create a template, install the Django debug toolbar, and configure it.

    Inside the cachedapplication app, open the models.py file and make it look like this:

    from django.db import models
    
    class Programmer(models.Model):
        name = models.CharField(max_length=50)
        dob = models.CharField(max_length=20)
        language = models.CharField(max_length=20)
        quote = models.CharField(max_length=150)
    
        def __str__(self):
            return f"{self.name}"
    

    Next, open the admin.py file and paste this code:

    from django.contrib import admin
    from .models import Programmer
    
    admin.site.register(Programmer)
    

    This code snippet registers the model Programmer in the Django admin dashboard.

    Before populating the models, let’s do some migrations. In the terminal, run this:

    $ python manage.py makemigrations

    And also run this:

    $ python manage.py migrate

    In Django, we can populate our models in two ways: via the terminal, and via the admin dashboard. But for simplicity’s sake, we’ll use the admin dashboard. Since the admin dashboard is for the superuser only, we need to create one. In the terminal, run the following command:

    $ python manage.py createsuperuser
    

    This command will prompt you to enter superuser details like username, email, and the two passwords.

    After successfully creating the superuser, fire up the local server, and in your browser enter this URL: http://127.0.0.1:8000/admin/. The image below shows the page you’ll be taken to.

    dashboard login

    To log in, provide the superuser credentials, and once you’re in, populate the models with data, as pictured below.

    the registered model in the admin interface

    Inside the application, create a templates folder, and inside it create a list_all.html file. For now, leave the HTML file empty. Open the views.py file and make it look like this:

    from django.shortcuts import render
    from .models import Programmer
    
    def home(request):
    
        programmers = Programmer.objects.all()
    
        context = {
            'programmers': programmers
        }
    
        return render(request, 'list_all.html', context)
    

    Now let’s register the home view in the project’s urls.py file. Inside the cachedproject folder, open the urls.py file and paste this code:

    from django.contrib import admin
    from django.urls import path, include
    from cachedapplication import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        # the home view from the cachedapplication
        path('home/', views.home, name='home'),
    ]
    

    Now open the list_all.html file and paste the following code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Caching</title>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.4.2/mdb.min.css" rel="stylesheet"/>
    </head>
    <body>
        <div class="container py-5">
            <table class="table">
               <thead>
                  <tr>
                     <th>Name</th>
                     <th>DOB</th>
                     <th>Language</th>
                     <th>Quote</th>
                  </tr>
               </thead>
               <tbody>
                  {% for programmer in programmers %}
    
                  <tr>
                     <td>{{programmer.name}}</td>
                     <td>{{programmer.dob}}</td>
                     <td>{{programmer.language}}</td>
                     <td>{{programmer.quote}}</td>
                  </tr>
                  {% endfor %}
               </tbody>
            </table>
         </div>
    </body>
    </html>
    

    Let’s install a Django debug toolbar. This is a Python package that helps developers monitor the performance of their Django applications, providing detailed information about database queries, HTTP requests, template rendering times, and more. So in your terminal, enter this command:

    pip install django-debug-toolbar
    

    To configure the django-debug-toolbar, open the settings.py file and edit the INSTALLED_APPS list to look like this:

    # Application definition
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # new application added
        'cachedapplication',
    
        # the debug toolbar
        'debug_toolbar',
    ]
    

    Add the debug toolbar to the MIDDLEWARE list:

    MIDDLEWARE = [
    
        # debug toolbar middleware
        'debug_toolbar.middleware.DebugToolbarMiddleware',
    ]
    

    Make the debug toolbar middleware come right after this:

    django.middleware.csrf.CsrfViewMiddleware
    

    It should also come before this:

    django.contrib.auth.middleware.AuthenticationMiddleware
    

    Add the Redis cache configurations as follows:

    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/1",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        }
    }
    

    Also, add this code to the to the settings.py file:

    INTERNAL_IPS = [
        # ...
        '127.0.0.1',  # Add your development machine's IP address here
    ]
    

    Finally, let’s configure the debug toolbar URLs in the urls.py file. Just below the imports, add this line of code:

    import debug_toolbar
    

    Inside the urlpatterns list add this code:

    urlpatterns = [
    
        # debug toolbar URLS
        path('__debug__/', include(debug_toolbar.urls)),
    ]
    

    Having reached this far, we’re good to go. Run the server and paste this URL into your browser: http://127.0.0.1:8000/home/. The image below shows the page we get.

    the debug toolbar

    Every time you run the application using this view, it will make SQL queries. In this case, three queries have taken 2.60ms. So to avoid making SQL queries for the same data every time, we’ll implement the view caching.

    View caching

    As the name suggests, view caching caching involves caching the results of individual Django views. In this section, we’ll implement the view caching. To do this, we’ll make a few modifications to the view.py file. Open it and add this import:

    from django.views.decorators.cache import cache_page
    

    Right above the view, also add this decorator:

    @cache_page(60*15)
    

    (60*15) is the argument passed to @cache_page. It represents the cache timeout in seconds. The home view will be cached for 15 minutes.

    Now visit the same view and refresh the page. We’ll get the result pictured below.

    zero sql queries

    In the image above, we can see that there are 0 SQL queries performed, as data is being fetched from the cache. This helps reduce the load on the server by serving cached content to users.

    Template fragment caching

    This caching involves caching specific parts of a template in your project. When your template has portions with heavy calculations, template fragment caching comes in handy. To implement this caching, we use these tags: {% load cache %}, {% cache %}, and {% endcache %}. The {% cache %} tag takes two arguments: the cache timeout, and a unique cache key for identifying a specific cached fragment.

    Now try running the project before implementing this caching technique. the image below shows what we’ll get.

    time setting

    The total time is 220.26ms, and three SQL queries are performed in 7.75ms.

    Now let’s implement the caching technique. We’ll cache the <div> portion of the template. Open the templates/list_all.html, and modify it to look like this:

    {% load cache %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Caching</title>
        <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/6.4.2/mdb.min.css" rel="stylesheet"/>
    </head>
    <body>  
       {% cache 500 programmers %}
        <div class="container py-5">
            <table class="table">
               <thead>
                  <tr>
                     <th>Name</th>
                     <th>DOB</th>
                     <th>Language</th>
                     <th>Quote</th>
                  </tr>
               </thead>
               <tbody>
                  {% for programmer in programmers %}
    
                  <tr>
                     <td>{{programmer.name}}</td>
                     <td>{{programmer.dob}}</td>
                     <td>{{programmer.language}}</td>
                     <td>{{programmer.quote}}</td>
                  </tr>
                  {% endfor %}
               </tbody>
            </table>
         </div>
       {% endcache %}
    </body>
    </html>
    

    On top of the file, we’re loading the cache via {% load cache %} and we’ve enclosed the div portion with {% cache 500 programmers %} and {% endcache %}.

    If you run the project again you will get the result pictured below.

    better caching results: time 68.14ms, 2 queries in 2.13ms

    In the image above, we can see that the results have improved after the caching has been implemented.

    Per-site caching

    Per-site caching is also known as whole-site caching. It involves caching the whole site’s pages. To implement it, you need to add these middleware configurations in the settings.py file:

    MIDDLEWARE = [
        # …
        'django.middleware.cache.UpdateCacheMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.cache.FetchFromCacheMiddleware',
        # …
    ]
    

    And also add these lines:

    CACHE_MIDDLEWARE_ALIAS  = ' ' # cache alias
    CACHE_MIDDLEWARE_SECONDS = 600 # number of seconds each page should be cached.
    CACHE_MIDDLEWARE_KEY_PREFIX = ''  # name of site if multiple sites are used
    

    Conclusion

    In conclusion, understanding caching in Django is essential for web developers looking to create high-performance web applications. This article has provided a comprehensive guide to caching in Django, covering topics such as the benefits of caching, setting up caching in Django, and the best practices for implementation. Armed with this knowledge, backend web developers can confidently incorporate caching into their Django projects to optimize performance.

    If you enjoyed this article, check out some more Django articles on SitePoint.