Django caching cheatsheet.
Backends
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://localhost:6379/1",
},
}
Other backends:
LocMemCache— in-memory per-process (dev only).DatabaseCache— DB-backed (overkill).FileBasedCache— filesystem.MemcachedCache— Memcached.
Low-level API
from django.core.cache import cache
cache.set("key", value, timeout=60)
value = cache.get("key")
value = cache.get("key", "default")
cache.delete("key")
cache.delete_many(["k1", "k2"])
cache.clear()
cache.add("key", value, timeout=60) # only if missing
cache.get_or_set("key", default, timeout)
# Multiple
cache.set_many({"a": 1, "b": 2})
cache.get_many(["a", "b"])
# Atomic counter
cache.incr("counter")
cache.decr("counter")
cache.incr_version("key")
Pattern: cache aside
def get_post(post_id):
key = f"post:{post_id}"
post = cache.get(key)
if post is None:
post = Post.objects.get(id=post_id)
cache.set(key, post, timeout=300)
return post
Invalidation
def update_post(post_id, **data):
Post.objects.filter(id=post_id).update(**data)
cache.delete(f"post:{post_id}")
Or via signal:
@receiver(post_save, sender=Post)
def invalidate(sender, instance, **kwargs):
cache.delete(f"post:{instance.id}")
Versioning
cache.set("user:1", data, version=2)
cache.get("user:1", version=2)
Switch versions to invalidate ranges.
Per-view cache
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def view(request):
...
# CBV
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name="dispatch")
class MyView(ListView):
...
Caches the entire response. Watch for per-user content.
vary_on_headers / vary_on_cookie
from django.views.decorators.vary import vary_on_headers, vary_on_cookie
@vary_on_cookie
@cache_page(60 * 15)
def view(request):
...
@vary_on_headers("User-Agent")
@cache_page(60)
def view(request): ...
Per-site caching
MIDDLEWARE = [
"django.middleware.cache.UpdateCacheMiddleware",
...,
"django.middleware.common.CommonMiddleware",
"django.middleware.cache.FetchFromCacheMiddleware",
]
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = "myapp"
Caches every page. Aggressive; usually not what you want.
Template fragment caching
{% load cache %}
{% cache 600 sidebar request.user.id %}
... expensive sidebar ...
{% endcache %}
Cache key includes the timeout and any vars listed.
QuerySet caching
def get_top_posts():
return cache.get_or_set(
"top_posts",
lambda: list(Post.objects.filter(featured=True)[:10]),
timeout=300,
)
Wrap in list() to materialize before caching (QuerySets are lazy).
Cached property
from django.utils.functional import cached_property
class Post:
@cached_property
def expensive(self):
return compute()
Per-instance cache (memory, lasts as long as instance).
Locking (cache stampede)
When many requests hit a cold cache, all of them re-compute. Avoid via lock:
from django.core.cache import cache
import time
def get_with_lock(key, fn, timeout=300):
val = cache.get(key)
if val is not None: return val
lock_key = f"{key}:lock"
if cache.add(lock_key, "1", timeout=10): # atomic
try:
val = fn()
cache.set(key, val, timeout=timeout)
finally:
cache.delete(lock_key)
return val
# Someone else is computing; wait briefly and retry
time.sleep(0.1)
return cache.get(key) or fn()
Redis directly
For lists, sets, hashes — use Redis client directly:
import redis
r = redis.from_url(settings.REDIS_URL, decode_responses=True)
r.lpush("queue", "task1")
r.rpop("queue")
r.zadd("leaderboard", {"alice": 100, "bob": 80})
r.zrevrange("leaderboard", 0, 9)
r.hset("user:1", mapping={"name": "A", "email": "[email protected]"})
r.hgetall("user:1")
Django cache is just KV — for more, use Redis directly.
Sessions in cache
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
Or cached_db (cache + DB fallback). Fast and persistent.
Cache headers
from django.views.decorators.cache import cache_control
@cache_control(max_age=3600, public=True)
def view(request):
...
Sets Cache-Control: public, max-age=3600. Useful for CDN caching.
Cache key conventions
post:{id} # individual entity
posts:list:{q_hash} # collection by query
user:{id}:permissions # derived data
counter:posts:total # counters
Make keys explicit and grep-able.
Common mistakes
cache.setwith non-pickleable object → silent failure.- Caching per-user data without including user in key.
cache_pageon a view that uses cookies → wrong content served.- Forgetting to invalidate on update.
- Cache TTL too long → stale data; too short → useless cache.
Read this next
If you want my caching patterns, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .