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

🧠 Test Your Knowledge

Which cache backend is recommended for production?