How Django’s Per‑Site Cache Turns a Slow View into a Lightning‑Fast One
Learn how to enable Django’s per‑site cache, measure speed gains, and handle cache invalidation so your read‑heavy views stay fresh and fast.
06 Sept 2026, 05:54 UTC

Problem: A Read‑Heavy View That Eats Time
Imagine a blog that shows the latest 20 posts on every home‑page request. Each request fires a SELECT query that joins the Post table with Author and Category. On a busy site this can add 100‑200 ms per request, and if you have thousands of visitors per minute the database becomes a bottleneck.
Thesis: Wrap the Entire Request/Response Cycle with Per‑Site Caching
Django’s CacheMiddleware can cache the *entire* HTTP response for a given URL. Once cached, subsequent requests hit the cache store (Memcached, Redis, or the in‑memory backend) and bypass the database entirely. The result is a dramatic drop in latency for read‑heavy views.
What Per‑Site Caching Is and Why It Works
- Transparent to your code: No changes to models or queries.
- URL‑based key generation: Django automatically hashes the full request path and query string.
- Timeout‑driven expiration: You control how long a response stays fresh.
Getting Started: Enabling the Middleware and Choosing a Backend
1. Pick a backend. For production, Redis or Memcached are recommended. For local development, the django.core.cache.backends.locmem.LocMemCache is fine.
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
# Enable the middleware
MIDDLEWARE = [
# ... other middleware
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
# Cache every page for 5 minutes
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 300
CACHE_MIDDLEWARE_KEY_PREFIX = ''
Run the server with python manage.py runserver (you need runserver permission). The middleware will now cache every response that has a status code of 200.
Concrete Example: Caching a List View
Let’s see the performance difference with a minimal view that queries the database.
# views.py
from django.shortcuts import render
from django.views.decorators.cache import cache_page
from .models import Post
@cache_page(60 * 5) # 5‑minute per‑view cache (alternative to middleware)
def home(request):
posts = Post.objects.select_related('author').order_by('-published_at')[:20]
return render(request, 'home.html', {'posts': posts})
To measure the difference, you can use Django’s debug_toolbar or a simple time wrapper:
# utils.py
import time
from django.utils.decorators import method_decorator
from django.views import View
def time_view(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
response = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.3f}s")
return response
return wrapper
# In views.py
@method_decorator(time_view, name='dispatch')
class HomeView(View):
def get(self, request):
# same query as above
On the first request you’ll see a higher latency (e.g., 0.250 s). Subsequent requests will drop to ~0.010 s, confirming the cache hit.
Cache Invalidation: Keeping the Cache Fresh
Per‑site caching does not automatically detect changes. If an admin edits a Post, the cached page will still show the old content until the 5‑minute timeout expires. You must explicitly clear the key when data changes.
# admin.py
from django.contrib import admin
from django.core.cache import cache
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# Invalidate the home page cache
cache.delete_pattern('views.decorators.cache.cache_page.*')
Alternatively, use Django signals:
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
@receiver([post_save, post_delete])
def clear_home_cache(sender, **kwargs):
if sender.__name__ == 'Post':
cache.delete_pattern('views.decorators.cache.cache_page.*')
Run python manage.py shell to test that after updating a post the cached page is cleared and the new content appears immediately.
Trade‑Offs & Limitations
- Stale data risk: If you forget to invalidate, visitors see outdated content.
- Masking DB issues: A cache hit hides a slow query; you should still monitor and index.
- Memory usage: In‑memory cache grows with the number of cached pages; Redis/Memcached offloads to RAM/SSD.
- Complex URLs: Dynamic query strings generate separate cache keys; ensure consistent URLs for best hits.
Actionable Checklist
- Choose a backend (Redis recommended for production).
- Add
CacheMiddlewareor use@cache_pageon high‑traffic views. - Set a sensible
CACHE_MIDDLEWARE_SECONDS(e.g., 300 s for news sites). - Implement cache invalidation via admin overrides or signals.
- Use
django‑debug‑toolbarorcache.logto verify hits/misses. - Monitor memory usage and adjust backend configuration.
With these steps, you’ll reduce database load, lower response times, and keep your site responsive for thousands of concurrent visitors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.