Django Caching
Improving performance with caching strategies
⚡ What is Django Caching?
Caching stores expensive computation results temporarily to speed up your application. Instead of recalculating or querying the database repeatedly, Django retrieves cached data instantly, dramatically improving performance and reducing server load.
# Simple cache example
from django.core.cache import cache
# Store data
cache.set('my_key', 'my_value', 300) # Cache for 5 minutes
# Retrieve data
value = cache.get('my_key')
Cache Backend Types
Database
Store cache in database tables
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
File System
Cache data in files on disk
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
}
}
Redis
Fast in-memory caching (recommended)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
Memcached
High-performance distributed cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
🔹 Basic Cache Configuration
Configure caching in settings.py to choose your cache backend. For development, use local memory cache. For production, use Redis or Memcached for better performance.
# settings.py
# Simple local memory cache (development)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
# Redis cache (production - recommended)
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, # 5 minutes default
}
}
🔹 Low-Level Cache API
Use Django's cache API to manually store and retrieve data. This gives you fine-grained control over what gets cached and for how long.
# views.py
from django.core.cache import cache
from django.shortcuts import render
from .models import Article
def article_list(request):
# Try to get from cache
articles = cache.get('all_articles')
if articles is None:
# Cache miss - fetch from database
articles = list(Article.objects.all())
# Store in cache for 15 minutes
cache.set('all_articles', articles, 60 * 15)
print("Fetched from database")
else:
print("Fetched from cache")
return render(request, 'articles.html', {'articles': articles})
# Other cache operations
def cache_operations():
# Set with default timeout
cache.set('key', 'value')
# Get value
value = cache.get('key')
# Get with default if not found
value = cache.get('key', 'default_value')
# Delete from cache
cache.delete('key')
# Clear entire cache
cache.clear()
Console Output:
First request: "Fetched from database"
Second request: "Fetched from cache" (much faster!)
🔹 Per-View Caching
Cache entire view responses using decorators. This is the simplest way to cache and works great for views that don't change often.
# views.py
from django.views.decorators.cache import cache_page
from django.shortcuts import render
# Cache this view for 15 minutes
@cache_page(60 * 15)
def homepage(request):
# Expensive operations here
return render(request, 'home.html')
# Cache with custom cache backend
@cache_page(60 * 15, cache='special_cache')
def special_view(request):
return render(request, 'special.html')
# In urls.py - cache at URL level
from django.views.decorators.cache import cache_page
urlpatterns = [
path('articles/', cache_page(60 * 15)(article_list)),
]
🔹 Template Fragment Caching
Cache specific parts of templates instead of entire pages. Perfect for caching expensive template sections while keeping other parts dynamic.
<!-- template.html -->
{% load cache %}
<h1>Welcome {{ user.username }}</h1>
<!-- Cache this section for 10 minutes -->
{% cache 600 sidebar %}
<div class="sidebar">
<h3>Popular Articles</h3>
{% for article in popular_articles %}
<div>{{ article.title }}</div>
{% endfor %}
</div>
{% endcache %}
<!-- Cache with variable key -->
{% cache 600 user_profile user.id %}
<div class="profile">
<h3>{{ user.username }}'s Profile</h3>
<p>Posts: {{ user.posts.count }}</p>
</div>
{% endcache %}
<!-- This part is always dynamic -->
<p>Current time: {{ current_time }}</p>
🔹 Caching Database Queries
Cache expensive database queries to reduce database load. Use cache keys that include relevant parameters to avoid serving stale data.
# utils.py
from django.core.cache import cache
from .models import Product
def get_featured_products():
"""Get featured products with caching"""
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(
Product.objects.filter(featured=True)
.select_related('category')
.order_by('-created_at')[:10]
)
cache.set(cache_key, products, 60 * 30) # 30 minutes
return products
def get_user_stats(user_id):
"""Get user statistics with caching"""
cache_key = f'user_stats_{user_id}'
stats = cache.get(cache_key)
if stats is None:
stats = {
'total_posts': Post.objects.filter(author_id=user_id).count(),
'total_comments': Comment.objects.filter(user_id=user_id).count(),
'followers': Follow.objects.filter(following_id=user_id).count(),
}
cache.set(cache_key, stats, 60 * 10) # 10 minutes
return stats
🔹 Cache Invalidation
Clear cache when data changes to prevent serving outdated information. Use signals to automatically invalidate cache when models are updated.
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from .models import Article
@receiver(post_save, sender=Article)
def clear_article_cache(sender, instance, **kwargs):
"""Clear article cache when article is saved"""
cache.delete('all_articles')
cache.delete(f'article_{instance.id}')
print(f"Cache cleared for article {instance.id}")
@receiver(post_delete, sender=Article)
def clear_cache_on_delete(sender, instance, **kwargs):
"""Clear cache when article is deleted"""
cache.delete('all_articles')
# Manual cache invalidation in views
def update_article(request, article_id):
article = Article.objects.get(id=article_id)
article.title = request.POST.get('title')
article.save()
# Clear related caches
cache.delete(f'article_{article_id}')
cache.delete('all_articles')
return redirect('article_detail', article_id=article_id)
🔹 Practical Example: API Response Caching
Cache API responses to reduce external API calls and improve response times. Include cache headers for client-side caching too.
# views.py
from django.core.cache import cache
from django.http import JsonResponse
import requests
def weather_api(request):
city = request.GET.get('city', 'London')
cache_key = f'weather_{city}'
# Try cache first
weather_data = cache.get(cache_key)
if weather_data is None:
# Fetch from external API
response = requests.get(
f'https://api.weather.com/data?city={city}'
)
weather_data = response.json()
# Cache for 1 hour
cache.set(cache_key, weather_data, 60 * 60)
weather_data['cached'] = False
else:
weather_data['cached'] = True
return JsonResponse(weather_data)
Response:
First call: {"temp": 20, "cached": false} - Slow
Second call: {"temp": 20, "cached": true} - Fast!
🔹 Cache Best Practices
Follow these guidelines for effective caching:
- Cache expensive operations: Database queries, API calls, complex calculations
- Use appropriate timeouts: Short for frequently changing data, long for static content
- Unique cache keys: Include relevant parameters (user_id, page_number, etc.)
- Invalidate on updates: Clear cache when underlying data changes
- Monitor cache hit rates: Track how often cache is used vs. missed
- Don't cache everything: Only cache what provides real performance benefit