DEV Community

Degisew Mengist
Degisew Mengist

Posted on

Caching in Django

Caching = Where to store + What to cache

What is Caching?

Caching is the process of storing copies of files or data in a temporary storage location so that future requests for that data can be served much faster than accessing the data’s primary storage location. It can also mean saving the result of an expensive calculation so you don’t have to perform the calculation again.

Cache is a high-speed data storage layer that stores either the result of an earlier computation or a copy of data stored elsewhere.

There are different types of caching like browser caching, CDN caching, DNS caching, in-memory caching but here, I’ll focus on server-side caching and how to implement it in Django.

Why Do We Need Caching?

Every time a user requests a page in your application, the web server performs numerous calculations: database queries, business logic processing, and template rendering (if you’re building a full-stack app). This creates the requested page from scratch each time. This approach becomes expensive, especially for medium- to high-traffic sites, and can lead to poor user experience or even server crashes when traffic exceeds server limits. Caching helps solve this by storing the result of these expensive computations and reuse precomputed responses, which significantly boosting performance and reducing load.

Now that you understand what caching is and why we need it, let’s explore the steps and techniques for implementing caching in Django — Python’s mature full-stack web framework.

1. Where to Store the Cache (Cache Backends)

First, you need to decide where to store your cache data. This choice significantly affects your cache’s performance, since some cache types are faster than others.

Django provides a robust cache system that lets you save dynamic pages so they don’t need to be recalculated for each request. You can choose from several storage options:

In-Memory Caching

Stores data in your computer’s RAM, making it extremely fast to access compared to reading from disk or a database. Django supports several in-memory caching options:

  • Local memory caching (Django’s default)

  • Redis

  • Memcached

File system Caching

This method stores cache data as separate files in your file system. File system caching is the simplest to configure but becomes the slowest option when storing numerous files.

Database Caching

You can use your database to store caches in Django. You might wonder: "Isn’t the database slow? Isn’t avoiding database hits the whole point of caching?"

Here’s the key difference: During normal requests, the server performs heavy calculations including database queries, external API calls, and other complex operations. With database caching, you perform these calculations once and store the result in the database. Future requests only need a single, fast query to retrieve the pre-calculated result, instead of repeating all those expensive operations.

Note: This works best with a fast, well-indexed database server.

Custom Cache Backend

While Django includes several cache backends out-of-the-box, you might occasionally need a customized solution. However, unless you have a compelling reason (such as host limitations), stick to Django’s included cache backends. They’ve been well-tested and are well-documented.

Note: You can find the cache backends configuration setting in the official documentation.

2. What to Cache (Caching Strategy)

Once your cache is configured, you can choose what data to cache for fast lookup. Django offers the following main caching strategies:

Per-Site Cache

Per-site caching means Django caches the entire response for every request. This works best for mostly static websites.

To implement per-site caching, add these middleware configurations to your settings:

MIDDLEWARE = [
    "django.middleware.cache.UpdateCacheMiddleware",
    # ... other middleware ...
    "django.middleware.cache.FetchFromCacheMiddleware",
]
Enter fullscreen mode Exit fullscreen mode
  • UpdateCacheMiddleware saves responses to cache after views run (place first)

  • FetchFromCacheMiddleware checks if URLs are cached before views run (place last)

And add these required settings to your Django settings file:

CACHE_MIDDLEWARE_ALIAS = 'default'  # The cache alias for storage
CACHE_MIDDLEWARE_SECONDS = 600      # Seconds each page should be cached
CACHE_MIDDLEWARE_KEY_PREFIX = ''    # Site name if using multiple sites
Enter fullscreen mode Exit fullscreen mode

Note: Per-site caching only caches GET and HEAD requests.

Per-View Caching

This caches the full response for specific views based on URL and request parameters. If multiple URLs point to the same view, each URL is cached separately. Per-view caching can be implemented using the cache_page decorator:

from django.views.decorators.cache import cache_page

@cache_page(60 * 5)  # Cache for 5 minutes
def my_view(request):
    # view logic here
    pass
Enter fullscreen mode Exit fullscreen mode

For class-based views, use the method decorator:

from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator

@method_decorator(cache_page(60 * 5), name='get') # set the name of the method like get, post
class MyView(View):
    def get(self, request):
        # view logic here
        pass

Enter fullscreen mode Exit fullscreen mode

But the above implementation has downsides because we’re hard-coding caching into the view itself. That means that the view is always cached, and we can’t easily reuse that view in a non-cached environment. It’s also not flexible since it’s tightly coupled to caching logic.

Better approach: Instead of hard-coding caching into views, apply caching in your URL configuration for more flexibility:

from django.views.decorators.cache import cache_page
from myapp.views import my_view

urlpatterns = [
    path('my-url/', cache_page(60 * 15)(my_view)),
]
Enter fullscreen mode Exit fullscreen mode

This approach keeps your views reusable and decouples caching logic from view implementation.

Template Fragment Caching

For full-stack Django applications, you can cache specific template fragments using the {% cache %} template tag. This is perfect for expensive-to-render or static page sections like sidebars or dropdowns.

{% load cache %}
{% cache 300 product_sidebar %}
    <!-- expensive rendering logic -->
{% endcache %}
Enter fullscreen mode Exit fullscreen mode

Low-Level Cache API

Use low-level caching when you need to cache any python data that isn’t views or templates, such as querysets, calculated values, or API responses. This API gives you full control over cache keys, timeouts, and invalidation.

To use low level cache API, you use Django’s built-in default cache object from django.core.cache. The default cache object has methods like cache.set(), cache.get(), cache.delete() to manage the cache.

from django.core.cache import cache

def get_expensive_data():
    key = "cache_unique_key"
    data = cache.get(key) # get the data from the cache

    if data is None:
        data = expensive_function()  # Database query or expensive operation
        cache.set(key, data, timeout=600)  # Cache for 10 minutes

    return data
Enter fullscreen mode Exit fullscreen mode

Now, you can use this function wherever you need. The function performs the expensive calculation only once, then serves cached results for subsequent requests. I personally use low-level caching to store data lookups (like dropdown values), because they rarely change. You can also handle cache invalidation on creation or update using Django signals.

Conclusion

Caching is essential for building performant Django applications. Start by choosing the right cache backend for your needs, then implement the appropriate caching strategy based on your application’s requirements. Remember: the best caching strategy often combines multiple approaches. You might use per-view caching for dynamic pages, while employing low-level caching for expensive data lookups.

The key to successful caching is understanding your application’s bottlenecks and choosing the right caching strategy for each use case. So before doing any caching or performance improvement, track the bottlenecks using tools like django debug toolbar, django silk. You can also use locust for performance/load testing.

Top comments (0)