Django Cache
GitHub Overview
django/django
The Web framework for perfectionists with deadlines.
Topics
Star History
Library
Django Cache
Overview
Django Cache is a comprehensive caching system built into the Django framework. It supports multiple cache backends and provides caching functionality at site-wide, view-level, and template fragment levels.
Details
Django Cache is a flexible and powerful caching framework designed to significantly improve the performance of dynamic web applications. It supports major cache backends including Local Memory (locmem), Database, File-based, Memcached (PyMemcache, PyLibMC), Redis, and Dummy cache. Three levels of caching are available: site-wide caching that caches entire pages, view-level caching that caches individual view outputs, and template fragment caching that caches specific parts of templates. Fine-grained control is possible through rich decorators like cache_page, vary_on_headers, and cache_control decorators. It provides enterprise-level features such as cache key generation, versioning, timeout settings, and cache invalidation. Async APIs have also been introduced, supporting usage with async/await patterns. The framework addresses key performance bottlenecks in dynamic web applications by reducing database load and improving response times through intelligent caching strategies.
Pros and Cons
Pros
- Framework Integration: Fully integrated with Django, making configuration and usage simple
- Multiple Backends: Rich selection including Memcached, Redis, database, and file-based options
- Flexible Cache Levels: Control at site-wide, view, and template fragment levels
- Rich Decorators: Convenient decorators like cache_page, vary_on_headers, never_cache
- Cache Key Management: Supports versioning, prefixes, and custom key generation
- Async Support: Cache operations possible with async/await patterns
- Development Support: Dummy cache and Local Memory provide convenience during development
Cons
- Configuration Complexity: Multiple cache configurations can become complex in large projects
- Backend Dependencies: Dependent on limitations and characteristics of chosen backend
- Debugging Difficulty: Debugging can become challenging when cache is active
- Memory Usage: Local Memory cache is process-specific and memory inefficient
- File-based Performance: File-based cache shows significant performance degradation on write operations
- Key Limitations: Some backends like Memcached have restrictions on key length and character types
Key Links
- Django Cache Framework Official Documentation
- Django Cache Settings
- Django Cache API Reference
- Django Cache Decorators
- Django Template Fragment Caching
- Django Cache Versioning
Code Examples
Basic Cache Configuration
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
'OPTIONS': {
'db': '1',
},
'TIMEOUT': 300, # 5 minutes default timeout
'KEY_PREFIX': 'myapp',
'VERSION': 1,
},
'sessions': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 86400, # 24 hours
},
'localcache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
'TIMEOUT': 300,
'OPTIONS': {
'MAX_ENTRIES': 1000,
}
}
}
# Cache middleware configuration (site-wide caching)
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = 'site'
Low-Level Cache API
from django.core.cache import cache, caches
# Using default cache
def get_user_profile(user_id):
cache_key = f'user_profile_{user_id}'
# Try to get from cache
profile = cache.get(cache_key)
if profile is None:
# On cache miss, fetch from database
profile = UserProfile.objects.get(user_id=user_id)
# Store in cache (1 hour)
cache.set(cache_key, profile, 3600)
return profile
# Multiple key operations
def get_multiple_users(user_ids):
cache_keys = [f'user_{uid}' for uid in user_ids]
# Get multiple keys at once
cached_users = cache.get_many(cache_keys)
# Identify users not in cache
missing_ids = []
for uid in user_ids:
if f'user_{uid}' not in cached_users:
missing_ids.append(uid)
# Fetch missing users from database
if missing_ids:
fresh_users = User.objects.filter(id__in=missing_ids)
fresh_data = {f'user_{u.id}': u for u in fresh_users}
# Store in cache
cache.set_many(fresh_data, timeout=1800)
cached_users.update(fresh_data)
return list(cached_users.values())
# Using specific cache backend
session_cache = caches['sessions']
session_cache.set('session_data', session_info, 86400)
View-Level Caching
from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.vary import vary_on_headers, vary_on_cookie
# Cache entire page for 15 minutes
@cache_page(60 * 15)
def product_list(request):
products = Product.objects.filter(is_active=True)
return render(request, 'products/list.html', {'products': products})
# Use specific cache backend
@cache_page(60 * 30, cache='localcache')
def heavy_computation_view(request):
result = perform_heavy_computation()
return JsonResponse({'result': result})
# Vary cache based on headers
@vary_on_headers('User-Agent', 'Accept-Language')
@cache_page(60 * 60)
def responsive_view(request):
return render(request, 'responsive.html')
# Vary cache based on cookies (user-specific content)
@vary_on_cookie
@cache_page(60 * 10)
def user_dashboard(request):
if request.user.is_authenticated:
data = get_user_specific_data(request.user)
return render(request, 'dashboard.html', {'data': data})
else:
return redirect('login')
# Disable caching
@never_cache
def real_time_data(request):
current_time = timezone.now()
return JsonResponse({'timestamp': current_time.isoformat()})
Template Fragment Caching
<!-- templates/product_detail.html -->
{% load cache %}
<div class="product-detail">
<h1>{{ product.name }}</h1>
<!-- Cache product description for 30 minutes -->
{% cache 1800 product_description product.id %}
<div class="description">
{{ product.description|markdown }}
</div>
{% endcache %}
<!-- User-specific review section -->
{% cache 600 user_reviews product.id user.id %}
<div class="user-reviews">
{% for review in user_reviews %}
<div class="review">{{ review.content }}</div>
{% endfor %}
</div>
{% endcache %}
<!-- Language-specific price display -->
{% get_current_language as LANGUAGE_CODE %}
{% cache 3600 product_price product.id LANGUAGE_CODE %}
<div class="price">
{% if LANGUAGE_CODE == 'ja' %}
¥{{ product.price_jpy|floatformat:0 }}
{% else %}
${{ product.price_usd|floatformat:2 }}
{% endif %}
</div>
{% endcache %}
</div>
Cache Invalidation and Clearing
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
# Invalidate cache on update
product = self.get_object()
# Invalidate view-level cache
cache_key = f'product_detail_{product.id}'
cache.delete(cache_key)
# Invalidate template fragment cache
fragment_key = make_template_fragment_key(
'product_description',
[product.id]
)
cache.delete(fragment_key)
# Delete related cache using pattern matching
pattern_keys = cache.keys(f'product_{product.id}_*')
if pattern_keys:
cache.delete_many(pattern_keys)
return response
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
# Clear product list cache
cache.delete('product_list_cache')
return response
# Custom management command for cache clearing
# management/commands/clear_cache.py
from django.core.management.base import BaseCommand
from django.core.cache import cache
class Command(BaseCommand):
help = 'Clear all cache'
def add_arguments(self, parser):
parser.add_argument(
'--pattern',
type=str,
help='Clear cache keys matching pattern'
)
def handle(self, *args, **options):
if options['pattern']:
keys = cache.keys(options['pattern'])
cache.delete_many(keys)
self.stdout.write(f'Deleted {len(keys)} cache keys')
else:
cache.clear()
self.stdout.write('All cache cleared')
Asynchronous Cache Operations
from django.core.cache import cache
from asgiref.sync import sync_to_async
# Cache usage in async views
async def async_product_detail(request, product_id):
cache_key = f'product_{product_id}'
# Get from cache asynchronously
product = await cache.aget(cache_key)
if product is None:
# Fetch from database asynchronously
product = await Product.objects.aget(id=product_id)
# Store in cache asynchronously
await cache.aset(cache_key, product, 3600)
return JsonResponse({
'id': product.id,
'name': product.name,
'price': product.price
})
# Async multiple key operations
async def async_multiple_products(request):
product_ids = request.GET.getlist('ids')
cache_keys = [f'product_{pid}' for pid in product_ids]
# Get multiple keys asynchronously
cached_products = await cache.aget_many(cache_keys)
return JsonResponse({
'products': list(cached_products.values())
})
# Custom async cache helper
class AsyncCacheHelper:
@staticmethod
async def get_or_set_async(key, async_callable, timeout=3600):
"""Async version of get_or_set"""
value = await cache.aget(key)
if value is None:
value = await async_callable()
await cache.aset(key, value, timeout)
return value
@staticmethod
async def invalidate_pattern_async(pattern):
"""Async deletion using pattern matching"""
# Convert sync method to async
keys = await sync_to_async(cache.keys)(pattern)
if keys:
await cache.adelete_many(keys)
return len(keys)
Cache Versioning and Key Management
from django.core.cache import cache
from django.conf import settings
class VersionedCache:
def __init__(self, version=None):
self.version = version or getattr(settings, 'CACHE_VERSION', 1)
def make_key(self, key, version=None):
version = version or self.version
return f'v{version}:{key}'
def get(self, key, default=None, version=None):
versioned_key = self.make_key(key, version)
return cache.get(versioned_key, default)
def set(self, key, value, timeout=None, version=None):
versioned_key = self.make_key(key, version)
return cache.set(versioned_key, value, timeout)
def delete(self, key, version=None):
versioned_key = self.make_key(key, version)
return cache.delete(versioned_key)
def incr_version(self, key):
"""Increment key version"""
return cache.incr_version(key)
# Usage example
vcache = VersionedCache(version=2)
vcache.set('user_data', user_info, 3600)
user_data = vcache.get('user_data')
# Invalidate all cache through version increment
cache.incr_version('user_data') # Becomes version 3, invalidating past cache