Boosting Django Performance with Redis Caching: A Comprehensive Guide
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.

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)})

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, 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.