Advanced Caching Strategies
Master advanced caching techniques to dramatically improve Django application performance.
75 min•By Priygop Team•Last updated: Feb 2026
Caching Fundamentals
Caching is one of the most effective ways to improve Django application performance. Understanding different caching strategies and when to use them is crucial for production applications.
Multi-Level Caching
Example
# Advanced Caching Strategies
# 1. View-Level Caching
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from django.views.generic import ListView
# Function-based view caching
@cache_page(60 * 15) # Cache for 15 minutes
def post_list(request):
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})
# Class-based view caching
@method_decorator(cache_page(60 * 15), name='dispatch')
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
# 2. Template Fragment Caching
{% load cache %}
{% cache 500 sidebar request.user.username %}
<!-- Expensive sidebar content -->
<div class="sidebar">
<h3>Recent Posts</h3>
{% for post in recent_posts %}
<div class="post-item">
<h4>{{ post.title }}</h4>
<p>{{ post.excerpt }}</p>
</div>
{% endfor %}
</div>
{% endcache %}
# 3. Low-Level Caching
from django.core.cache import cache
from django.core.cache import caches
# Using default cache
def get_user_posts(user_id):
cache_key = f'user_posts_{user_id}'
posts = cache.get(cache_key)
if posts is None:
posts = Post.objects.filter(author_id=user_id)
cache.set(cache_key, posts, 300) # Cache for 5 minutes
return posts
# Using specific cache backend
def get_category_posts(category_id):
cache_key = f'category_posts_{category_id}'
redis_cache = caches['redis']
posts = redis_cache.get(cache_key)
if posts is None:
posts = Post.objects.filter(category_id=category_id)
redis_cache.set(cache_key, posts, 600) # Cache for 10 minutes
return posts
# 4. Cache Versioning
def get_user_profile(user_id):
cache_key = f'user_profile_{user_id}'
version = cache.get(f'user_profile_version_{user_id}', 1)
profile = cache.get(f'{cache_key}_v{version}')
if profile is None:
profile = UserProfile.objects.get(user_id=user_id)
cache.set(f'{cache_key}_v{version}', profile, 3600)
return profile
def invalidate_user_profile(user_id):
"""Invalidate user profile cache by incrementing version"""
version_key = f'user_profile_version_{user_id}'
current_version = cache.get(version_key, 1)
cache.set(version_key, current_version + 1, 3600)
# 5. Cache Key Generation
import hashlib
import json
def generate_cache_key(prefix, *args, **kwargs):
"""Generate a consistent cache key from arguments"""
# Convert args and kwargs to a string
key_parts = [str(arg) for arg in args]
key_parts.extend([f"{k}:{v}" for k, v in sorted(kwargs.items())])
# Create a hash of the key parts
key_string = "|".join(key_parts)
key_hash = hashlib.md5(key_string.encode()).hexdigest()
return f"{prefix}_{key_hash}"
# Usage
cache_key = generate_cache_key('user_posts', user_id, category_id, page=1)
posts = cache.get(cache_key)
# 6. Cache Invalidation Patterns
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
# Invalidate related caches
cache.delete(f'user_posts_{self.author.id}')
cache.delete(f'category_posts_{self.category.id}')
cache.delete('recent_posts')
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
# Invalidate related caches
cache.delete(f'user_posts_{self.author.id}')
cache.delete(f'category_posts_{self.category.id}')
cache.delete('recent_posts')
super().delete(*args, **kwargs)
# 7. Cache Middleware
from django.middleware.cache import UpdateCacheMiddleware
from django.middleware.cache import FetchFromCacheMiddleware
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
# ... other middleware
'django.middleware.cache.FetchFromCacheMiddleware',
]
# Cache settings
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'KEY_PREFIX': 'myapp',
'TIMEOUT': 300,
'VERSION': 1,
},
'redis': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'KEY_PREFIX': 'myapp_redis',
'TIMEOUT': 600,
},
'session': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/3',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
},
'KEY_PREFIX': 'session',
'TIMEOUT': 86400, # 24 hours
}
}
# 8. Custom Cache Backend
from django.core.cache.backends.base import BaseCache
import pickle
import time
class CustomCacheBackend(BaseCache):
def __init__(self, location, params):
super().__init__(params)
self.location = location
self._cache = {}
def get(self, key, default=None, version=None):
key = self.make_key(key, version=version)
if key in self._cache:
value, expiry = self._cache[key]
if expiry > time.time():
return value
else:
del self._cache[key]
return default
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
expiry = time.time() + timeout if timeout else None
self._cache[key] = (value, expiry)
def delete(self, key, version=None):
key = self.make_key(key, version=version)
if key in self._cache:
del self._cache[key]
def clear(self):
self._cache.clear()Practice Exercise: Smart Cache Manager
Example
# Smart Cache Manager
from django.core.cache import cache
from django.db.models import Model
from django.core.exceptions import ObjectDoesNotExist
import hashlib
import json
import time
class SmartCacheManager:
"""Advanced cache manager with automatic invalidation and smart key generation"""
def __init__(self, cache_backend=None):
self.cache = cache_backend or cache
self.cache_patterns = {}
def register_pattern(self, pattern_name, pattern_func, dependencies=None):
"""Register a cache pattern with dependencies"""
self.cache_patterns[pattern_name] = {
'func': pattern_func,
'dependencies': dependencies or [],
'last_updated': 0
}
def get_or_set(self, pattern_name, *args, **kwargs):
"""Get cached value or compute and cache it"""
if pattern_name not in self.cache_patterns:
raise ValueError(f"Unknown cache pattern: {pattern_name}")
pattern = self.cache_patterns[pattern_name]
cache_key = self._generate_key(pattern_name, *args, **kwargs)
# Try to get from cache
cached_value = self.cache.get(cache_key)
if cached_value is not None:
return cached_value
# Compute value
value = pattern['func'](*args, **kwargs)
# Cache with TTL based on pattern type
ttl = self._get_ttl(pattern_name)
self.cache.set(cache_key, value, ttl)
# Update last updated time
pattern['last_updated'] = time.time()
return value
def invalidate_pattern(self, pattern_name, *args, **kwargs):
"""Invalidate a specific cache pattern"""
if pattern_name in self.cache_patterns:
cache_key = self._generate_key(pattern_name, *args, **kwargs)
self.cache.delete(cache_key)
self.cache_patterns[pattern_name]['last_updated'] = 0
def invalidate_dependencies(self, model_class, instance_id):
"""Invalidate all cache patterns that depend on a model instance"""
for pattern_name, pattern in self.cache_patterns.items():
if model_class.__name__ in pattern['dependencies']:
# Invalidate all instances of this pattern
self._invalidate_pattern_instances(pattern_name)
def _generate_key(self, pattern_name, *args, **kwargs):
"""Generate a consistent cache key"""
key_parts = [pattern_name]
key_parts.extend([str(arg) for arg in args])
key_parts.extend([f"{k}:{v}" for k, v in sorted(kwargs.items())])
key_string = "|".join(key_parts)
return hashlib.md5(key_string.encode()).hexdigest()
def _get_ttl(self, pattern_name):
"""Get TTL for a pattern based on its type"""
ttl_map = {
'user_posts': 300, # 5 minutes
'category_posts': 600, # 10 minutes
'recent_posts': 180, # 3 minutes
'user_profile': 3600, # 1 hour
'search_results': 900, # 15 minutes
}
return ttl_map.get(pattern_name, 300)
def _invalidate_pattern_instances(self, pattern_name):
"""Invalidate all instances of a pattern (complex operation)"""
# This would require maintaining a registry of all cached keys
# For simplicity, we'll just clear the pattern's last_updated
if pattern_name in self.cache_patterns:
self.cache_patterns[pattern_name]['last_updated'] = 0
# Usage Example
cache_manager = SmartCacheManager()
# Register cache patterns
def get_user_posts(user_id):
return Post.objects.filter(author_id=user_id).select_related('author')
def get_category_posts(category_id):
return Post.objects.filter(category_id=category_id).select_related('author')
def get_recent_posts(limit=10):
return Post.objects.filter(status='published').order_by('-created_date')[:limit]
cache_manager.register_pattern('user_posts', get_user_posts, ['Post'])
cache_manager.register_pattern('category_posts', get_category_posts, ['Post'])
cache_manager.register_pattern('recent_posts', get_recent_posts, ['Post'])
# Use in views
def user_posts_view(request, user_id):
posts = cache_manager.get_or_set('user_posts', user_id)
return render(request, 'user_posts.html', {'posts': posts})
def category_posts_view(request, category_id):
posts = cache_manager.get_or_set('category_posts', category_id)
return render(request, 'category_posts.html', {'posts': posts})
# Invalidate when posts change
def post_save_handler(sender, instance, **kwargs):
cache_manager.invalidate_dependencies(Post, instance.id)
# Connect signal
from django.db.models.signals import post_save, post_delete
post_save.connect(post_save_handler, sender=Post)
post_delete.connect(post_save_handler, sender=Post)