Django Cache

PythonDjangoLibraryCachingWeb FrameworkPerformance

GitHub Overview

django/django

The Web framework for perfectionists with deadlines.

Stars85,495
Watchers2,277
Forks33,123
Created:April 28, 2012
Language:Python
License:BSD 3-Clause "New" or "Revised" License

Topics

appsdjangoframeworkmodelsormpythontemplatesviewsweb

Star History

django/django Star History
Data as of: 10/22/2025, 10:04 AM

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

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