Many Django views rerun the same query on every request even though the data rarely changes, putting pointless load on the database and slowing pages down as traffic grows. The fix is Django’s built-in cache framework: store query results with cache.set() or cache.get_or_set(), then serve them from cache for the duration of the timeout instead of asking the database. This works best for data that changes rarely, like categories or shared configuration.

The problem

Consider a familiar product list view: every time a user opens the page, the view fetches the full category list to render the menu, even though the categories haven’t changed all day.

def product_list(request):
categories = list(Category.objects.all()) # reruns on every request
products = Product.objects.select_related("category")[:20]
return render(request, "shop/list.html", {
"categories": categories,
"products": products,
})

The issue is volume: thousands of requests mean thousands of identical queries, and the queries that genuinely matter have to queue behind them.

The solution

Django ships the cache framework built in: just import cache from django.core.cache and start using it, no extra library needed. When you move to production, switch the backend to Redis or Memcached in CACHES without changing the code that uses the cache.

The most explicit style is to check the cache first, and only query and store when it misses:

from django.core.cache import cache
def get_categories():
categories = cache.get("categories:all")
if categories is None:
categories = list(Category.objects.all())
cache.set("categories:all", categories, timeout=300)
return categories

For a shorter version, use get_or_set() — one line does the same job: return from cache if present, otherwise call the data function and store the result with a timeout of 300 seconds:

def get_categories():
return cache.get_or_set(
"categories:all",
lambda: list(Category.objects.all()),
timeout=300,
)

The cache cannot know when data changes, so you must actively delete the old key wherever data is edited, for example after creating or updating a category:

cache.delete("categories:all")

Combining a short timeout with delete() right when data changes gives you a fast cache layer that never holds stale data for long.

Apply it to your project

Start from a single spot — don’t cache the whole application at once: pick read-heavy, rarely written data like a sidebar category list or shared display settings, set a timeout of about 5 minutes, then measure with django-debug-toolbar to see the real query reduction. Use a clear key naming convention, for example categories:all, so keys are easy to find and delete later.

Related posts

Frequently asked questions

When should you NOT cache query results?

Don’t cache data that changes constantly, like account balances or realtime stock levels — users would see stale numbers. Also avoid caching private data under a shared key; if you must, include the user identifier in the key, for example cart:user:42.

Where is the cache stored, and can LocMemCache be used in production?

The storage location is decided by the backend in CACHES. LocMemCache stores data in each process’s memory, so it only fits development and testing — each worker has its own separate cache. In production, use Redis or Memcached so all workers share one cache.

How do you handle stale cached data?

Two mechanisms complement each other: timeout so keys expire on their own, and cache.delete() as soon as the source data changes. Use a short timeout for fast-changing data and a longer one for stable data — never cache permanently.

How is get_or_set() different from hand-written get then set?

The result is the same, but get_or_set() is shorter: you don’t repeat the is None check pattern everywhere, and the code reads exactly as intended — “take from cache, compute and store if missing”.

💬 Have you tried this in your own project? If you hit an error or know a cleaner way, leave a comment below — I read them all and will fold the best ideas into the post.

Sources