Boosting Django Performance with Redis Caching: A Comprehensive Guide

Augustine Joseph
4 min readMar 1, 2025

--

Caching is a cornerstone of high-performance web applications. By temporarily storing frequently accessed data, caching reduces redundant computations, minimizes database load, and accelerates response times.

Github Link : Redis Cache

Introduction to Caching in Web Applications

Caching involves storing copies of data in temporary storage (cache) to serve future requests faster. Common use cases include:

  • Reducing database queries.
  • Speeding up computationally expensive operations.
  • Mitigating traffic spikes by serving cached content.

Without caching, every user request could trigger resource-heavy operations, leading to sluggish performance and scalability challenges.

What is Redis?

Redis (Remote Dictionary Server) is an open-source, in-memory data structure store. It supports versatile data types like strings, hashes, lists, sets, and more. Redis excels at caching due to:

  • Blazing-fast performance: Data resides in memory, enabling microsecond read/write speeds.
  • Persistence: Optional disk backups prevent data loss on restarts.
  • Scalability: Built-in replication and clustering support horizontal scaling.

Setting Up Redis for Django

# Install Redis server (Ubuntu)
sudo apt-get install redis-server

# Install django-redis
pip install django-redis

Update settings.py file:

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

Testing in shell:

python manage.py shell

from django.core.cache import cache
cache.set('my_key', 'Hello, Redis!')
print(cache.get('my_key'))

Load initial data

python manage.py load_initial_products
python manage.py load_initial_students

Basic Caching with Redis in Django

Using Cache Decorators

Cache entire views with @cache_page:

from django.http import JsonResponse
from .models import Product
import time
from django.views.decorators.cache import cache_page


@cache_page(60 * 1)
def product_list(request):
time.sleep(2)
products = Product.objects.all()
product_list = products.values("id", "name", "description", "price")
return JsonResponse({"products": list(product_list)})
Without caching

It took 2.02 seconds without caching, which included the 2-second timeout to accommodate the expensive, time-consuming database fetch and calculation.

With caching

With caching, it took 5 ms for the response.

Class Based Approach

from django.http import JsonResponse
from django.views.generic import ListView
import time
from Students.models import Student
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

@method_decorator(cache_page(60 * 1), name='dispatch')
class StudentsListView(ListView):
model = Student

def get(self, request):
time.sleep(2)
all_students = Student.objects.all().values('id', 'first_name', 'last_name', 'age', 'grade')
return JsonResponse({'students': list(all_students)})

Fetching a Single Product and Caching It

def get_single_product(request, id):
cache_key = f"product_details:{id}"
cache_timeout = 60 * 1
product_details = cache.get(cache_key)
if product_details:
return product_details
product_details_qs = get_object_or_404(Product, id=id)
product_details = {
"id": product_details_qs.id,
"name": product_details_qs.name,
"description": product_details_qs.description,
}
cache.set(cache_key, product_details, timeout=cache_timeout)
return JsonResponse({"data": product_details})
  • Cache Lookup: We first check if the product details are already cached. If they are, we return the cached data immediately, avoiding a database query.
  • Cache Miss: If the product is not in the cache, we fetch it from the database. Then we store the product details in the cache using cache.set() to speed up future requests for the same product.
  • Cache Expiry: The cache is set with a timeout of 1 minute (cache_timeout = 60 * 1). After that time, the cached product data will expire, and the next request will fetch the data again from the database.
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Product)
def clear_product_cache(sender, instance, **kwargs):
cache.delete(f'product_{instance.id}'

Handling Cache Misses Gracefully

Implement fallback logic when data isn’t cached:

def get_product_details(product_id):
key = f'product_{product_id}'
product = cache.get(key)
if not product:
product = Product.objects.get(id=product_id) # DB fallback
cache.set(key, product, timeout=60 * 60)
return product

Per-Object Caching and Invalidation

Cache individual objects and invalidate on updates:

from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=Product)
def update_product_cache(sender, instance, **kwargs):
cache.set(f'product_{instance.id}' , instance, timeout=60*15)

@receiver(post_delete, sender=Product)
def delete_product_cache(sender, instance, **kwargs):
cache.delete(f'product_{instance.id}', instance, timeout=60*15)

When a Product is created or updated, we want to update the corresponding cache entry to reflect the most recent data. The post_save signal ensures that every time a Product is saved, the cache is updated with the latest version of the object.

Similarly, when a Product is deleted, we use the post_delete signal to invalidate (or remove) the corresponding cache entry to ensure no outdated or non-existent data is served.

Github Link : Redis Cache

Sign up to discover human stories that deepen your understanding of the world.

--

--

No responses yet

Write a response