Module 7: Performance & Optimization

Learn advanced Django performance optimization techniques, caching strategies, database optimization, and production-ready deployment practices.

Back to Course|4 hours|Advanced

Performance & Optimization

Learn advanced Django performance optimization techniques, caching strategies, database optimization, and production-ready deployment practices.

Progress: 0/3 topics completed0%

Select Topics Overview

Advanced Caching Strategies

Master advanced caching techniques to dramatically improve Django application performance.

Content by: Manali Trivedi

Python Django Developer

Connect

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

Code 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()
Swipe to see more code

Practice Exercise: Smart Cache Manager

Code 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)
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Additional Resources

📚 Recommended Reading

  • Django Performance Optimization Guide
  • Django Caching Documentation
  • PostgreSQL Performance Tuning Guide

🌐 Online Resources

  • Redis Caching Tutorial
  • Nginx Configuration Guide
  • Docker Django Deployment

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 8