Memcached Caching in Django: Setup & Best Practices

Blog / Django · July 20, 2018 · Updated June 10, 2026 · 10 min read
Memcached Caching in Django: Setup & Best Practices

To add Memcached caching to a Django 5.x project, install the maintained pymemcache client and point your default cache at Django's PyMemcacheCache backend in the CACHES setting:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    }
}

That one setting unlocks Django's whole cache framework — per-site, per-view, template-fragment, and low-level caching. The important 2026 change: the old python-memcached library and its MemcachedCache backend are gone (deprecated in Django 3.2, removed in Django 4.1). Use PyMemcacheCache (or PyLibMCCache) instead.

This guide shows the correct backend, when Memcached beats Redis, and how to apply caching at every level — with copy-paste Django 5.2 LTS code.

Key takeaways

  • Use PyMemcacheCache (backed by pymemcache) — the old python-memcached/MemcachedCache backend was removed in Django 4.1. PyLibMCCache (pylibmc) is the other supported option.
  • Memcached is an in-memory, volatile key-value store with a ~1 MB default value-size limit — great as a cache, never as your source of truth.
  • Django 4.0+ ships a built-in Redis backend (RedisCache). Pick Memcached for a simple high-throughput cache; pick Redis when you also need persistence, data structures, or a queue/broker.
  • Cache at four levels: per-site middleware, per-view (cache_page), template fragments ({% cache %}), and the low-level API (cache.get/set/add/get_or_set).
  • Control behaviour with TIMEOUT, KEY_PREFIX, and VERSION; invalidate explicitly with cache.delete() and model signals.
  • For production, prefer a managed service such as Amazon ElastiCache for Memcached (or Redis) over a hand-rolled node.

Why and when should you cache a Django app?

Dynamic pages are expensive: every request can hit the database, run business logic, and render templates. Caching saves the result of that expensive work so the next request can skip it. Memcached is an in-memory key-value store that keeps those results in RAM, shared across all your Django worker processes.

Reach for caching when pages load slowly, the database is the bottleneck, or you need to scale read-heavy traffic. It complements — but does not replace — efficient queries; tighten the Django ORM with query optimization first, then cache what is still hot. Faster server responses also feed directly into Core Web Vitals and your PageSpeed score.

Two properties of Memcached shape how you use it:

  • It is volatile. Restart the daemon (or hit its memory limit) and the cache is gone. Never store anything you cannot recompute.
  • Values are capped at ~1 MB by default. Cache compact results — rendered fragments, serialized querysets, computed numbers — not giant blobs.

Which Memcached backend should you use in Django 5.x?

Most older tutorials (this post included, back in 2018) tell you to pip install python-memcached and use django.core.cache.backends.memcached.MemcachedCache. Do not. python-memcached is effectively unmaintained, and Django deprecated MemcachedCache in 3.2 and removed it in 4.1 — it does not exist in Django 5.x.

Django now supports two Memcached backends:

  • PyMemcacheCache — uses the pymemcache library. This is the recommended default and was added in Django 3.2.
  • PyLibMCCache — uses the pylibmc C-extension client. Slightly faster in some workloads, but needs the libmemcached system library to build.

Install the daemon and the Python client, then configure CACHES:

# 1. Install the Memcached daemon (Debian / Ubuntu)
sudo apt-get install memcached

# 2. Install the MAINTAINED Python client (NOT python-memcached)
pip install pymemcache


# settings.py — Django 3.2+ recommended backend
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
    }
}

# Connect over a Unix socket instead of TCP:
#   "LOCATION": "unix:/run/memcached/memcached.sock",

# Spread load across several Memcached nodes (a distributed cache):
#   "LOCATION": ["10.0.0.1:11211", "10.0.0.2:11211"],

# Alternative supported backend (uses the pylibmc C client):
#   "BACKEND": "django.core.cache.backends.memcached.PyLibMCCache",

Memcached vs Redis for Django: which should you choose?

Since Django 4.0 there is a built-in Redis backend (django.core.cache.backends.redis.RedisCache, backed by redis-py) — no third-party cache app required. So the real question is which engine fits your workload.

Factor Memcached Redis
Data model Simple string key-value Strings, hashes, lists, sets, sorted sets
Persistence None — purely volatile in RAM Optional (RDB snapshots / AOF)
Value size ~1 MB per item (default) Up to 512 MB per value
Concurrency Multi-threaded, high raw throughput Mostly single-threaded core
Eviction Built-in LRU Configurable eviction policies
Beyond caching Pure cache only Pub/Sub, atomic ops, queues, sessions
Django backend PyMemcacheCache / PyLibMCCache RedisCache (built in since 4.0)

Choose Memcached when you want a lean, blazing-fast cache for pages, fragments, and querysets and nothing more. Choose Redis when you also need persistence, richer data structures, atomic counters, or want one engine to also back your Celery broker, sessions, or rate limiting. Switching is a one-line BACKEND change because Django exposes the same cache API over both:

# settings.py — Django 4.0+ built-in Redis backend (same API as Memcached)
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}

What are the four levels of caching in Django?

Django's cache framework lets you cache at increasing granularity. Start coarse and get more surgical as your pages become more dynamic:

Level How you enable it Granularity Best for
Per-site Cache middleware The whole site Mostly-anonymous, read-heavy sites
Per-view @cache_page One view A few specific expensive pages
Template fragment {% cache %} tag Part of a page One slow widget on a dynamic page
Low-level API cache.get / set / ... Any Python object Querysets, computed values, fine control

The sections below show real code for each.

How does per-site (middleware) caching work?

The simplest option caches every page that does not carry GET parameters and whose response allows it. Add the two cache middlewares — UpdateCacheMiddleware must be first and FetchFromCacheMiddleware last — then set the timeout. This is ideal for a brochure or content site served mostly to logged-out visitors; avoid it when every page is personalised.

# settings.py
MIDDLEWARE = [
    "django.middleware.cache.UpdateCacheMiddleware",      # MUST be first
    "django.middleware.common.CommonMiddleware",
    # ... your other middleware ...
    "django.middleware.cache.FetchFromCacheMiddleware",   # MUST be last
]

CACHE_MIDDLEWARE_ALIAS = "default"        # which CACHES alias to use
CACHE_MIDDLEWARE_SECONDS = 600            # cache each page for 10 minutes
CACHE_MIDDLEWARE_KEY_PREFIX = "myapp"     # namespace keys for this site

How do you cache a single view with cache_page?

When only certain views are expensive, cache them individually with the cache_page decorator. It takes the timeout in seconds. Pair it with vary_on_headers (or vary_on_cookie) so visitors who differ by language, device, or login get the right cached copy instead of someone else's.

# views.py
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers


@cache_page(60 * 15)  # cache this view's response for 15 minutes
def article_list(request):
    ...


# Store a separate cached copy per language / device:
@cache_page(60 * 15)
@vary_on_headers("Accept-Language")
def localized_view(request):
    ...


# Or apply it in the URLconf instead of decorating the view:
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("articles/", cache_page(60 * 15)(views.article_list), name="articles"),
]

How do you cache just part of a template?

When a page is mostly dynamic but contains one slow region — a sidebar, a "popular posts" list, a navigation tree — cache only that fragment with the {% cache %} tag. Load the library with {% load cache %}, give the block a timeout (in seconds) and a name. Add extra arguments to vary the fragment by user, language, or any variable.

{% load cache %}

{# cache this fragment for 10 minutes under the name "sidebar" #}
{% cache 600 sidebar %}
  ... expensive sidebar markup ...
{% endcache %}

{# vary the cached fragment per user so each person sees their own copy #}
{% cache 600 sidebar request.user.username %}
  ... per-user sidebar ...
{% endcache %}

How do you use the low-level cache API?

For maximum control — caching a queryset, a computed total, an external API response — talk to the cache directly. Import the cache object and use set, get, add, and get_or_set. add only writes when the key is absent (handy for locks); get_or_set computes and stores a value when missing, and is the cleanest read-through pattern.

from django.core.cache import cache

# set with a per-call timeout in seconds (overrides the backend default)
cache.set("top_articles", data, 60 * 15)

# get with a fallback when the key is missing or expired
items = cache.get("top_articles", default=[])

# add() only sets if the key does NOT already exist (returns False if it does)
got_lock = cache.add("nightly_job_lock", "running", 3600)

# get_or_set(): compute + store on a miss. The callable runs only when needed.
profile = cache.get_or_set(f"profile:{user.id}", lambda: build_profile(user), 600)

# batch helpers cut round-trips
cache.set_many({"a": 1, "b": 2, "c": 3}, 300)
cache.get_many(["a", "b", "c"])

# atomic counters (great for view/hit counts)
cache.set("hits", 0)
cache.incr("hits")

# invalidate explicitly
cache.delete("top_articles")
cache.delete_many(["a", "b", "c"])
cache.clear()  # flush EVERYTHING in this cache — use with care

How do you handle timeouts, invalidation, and keys?

Good caching is mostly about controlling staleness. A few settings and patterns matter:

  • TIMEOUT — default lifetime in seconds (Django's default is 300). TIMEOUT=None caches forever; TIMEOUT=0 effectively disables caching for that entry.
  • KEY_PREFIX — prepended to every key, so multiple apps (or environments) can share one Memcached server without collisions.
  • VERSION — bump it to invalidate an entire generation of keys at once; perfect for a deploy that changes cached data shapes.
  • MAX_ENTRIES / CULL_FREQUENCY — these OPTIONS apply to the local-memory, database, and file backends. Memcached manages its own memory and evicts with built-in LRU, so you size the daemon (its -m megabytes) rather than setting MAX_ENTRIES.
  • Explicit invalidation — the hardest part of caching. For data that must stay fresh, delete keys when the underlying model changes using Django signals.

Here is a production-shaped CACHES block plus signal-based invalidation:

# settings.py — production-shaped Memcached cache
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": ["cache1.internal:11211", "cache2.internal:11211"],
        "TIMEOUT": 300,            # default entry lifetime (seconds)
        "KEY_PREFIX": "myapp",     # namespace keys for this app
        "VERSION": 2,              # bump to retire a whole key generation
        "OPTIONS": {               # passed through to the pymemcache client
            "no_delay": True,
            "ignore_exc": True,    # treat a dead cache as a miss, not a 500
        },
    }
}


# signals.py — invalidate cached data when the model changes
from django.core.cache import cache
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Article


@receiver([post_save, post_delete], sender=Article)
def clear_article_cache(sender, instance, **kwargs):
    cache.delete(f"article:{instance.pk}")
    cache.delete("top_articles")

How should you run Memcached in production?

For anything beyond local development, prefer a managed cache over a self-hosted daemon. On AWS, Amazon ElastiCache for Memcached (or for Redis) gives you a managed cluster with monitoring, automatic node replacement, and easy scaling — you just point LOCATION at the cluster endpoint. Other clouds offer equivalents (Memorystore on GCP, Azure Cache).

A few production reminders:

  • Treat the cache as disposable. Your app must work — just slower — when the cache is cold or unreachable ("ignore_exc": True helps).
  • Keep the app and cache in the same region/VPC to avoid network latency eating your savings.
  • Size memory to your working set, and watch the eviction and hit-rate metrics.
  • Caching sits alongside the rest of your stack: see our guide to deploying Django with Nginx and uWSGI for the serving layer in front of it.

Frequently Asked Questions

Which Memcached backend should I use in Django 5.x?

Use django.core.cache.backends.memcached.PyMemcacheCache, which is backed by the maintained pymemcache library and has been the recommended Memcached backend since Django 3.2. The alternative is PyLibMCCache (using pylibmc). Do not use the old MemcachedCache backend or the python-memcached package — that backend was deprecated in Django 3.2 and removed in Django 4.1, so it no longer exists in Django 5.x.

Is python-memcached still supported?

No. python-memcached is effectively unmaintained, and Django removed its corresponding MemcachedCache backend in version 4.1. Migrate to pymemcache with the PyMemcacheCache backend; for most projects the only change is the BACKEND string in your CACHES setting and installing pymemcache instead of python-memcached.

Should I use Memcached or Redis with Django?

Use Memcached when you want a simple, very fast, multi-threaded cache and nothing more. Use Redis when you also need persistence, richer data types (hashes, lists, sorted sets), atomic operations, or want one service to back your task queue, sessions, or rate limiting too. Django 4.0+ includes a built-in RedisCache backend, and both expose the same cache API, so switching is essentially a one-line change.

What is the maximum value size in Memcached?

Memcached's default maximum size for a single cached item is about 1 MB. Larger values are rejected unless you raise the daemon's item-size limit (the -I option), which is usually a sign you should cache something smaller or use Redis instead. Cache compact data such as rendered fragments, serialized querysets, or computed numbers rather than large objects.

How do I invalidate or clear the Django cache?

Delete specific keys with cache.delete("key") or cache.delete_many([...]), and wipe the whole cache with cache.clear() (use sparingly). For data that must stay fresh, hook into Django's post_save and post_delete signals to delete the affected keys when a model changes. You can also invalidate an entire generation of keys at once by bumping the VERSION value in your CACHES setting.

Does caching survive a Memcached restart?

No. Memcached stores everything in RAM and has no persistence, so restarting the daemon, a crash, or hitting its memory limit clears the cache. That is by design — a cache is disposable. Always write your application so it recomputes data on a miss, and never use Memcached as your only copy of important data. If you need persistence, use Redis with RDB/AOF enabled.

Ship a faster Django app with MicroPyramid

Caching is one lever; a genuinely fast Django product also needs tuned queries, a solid serving layer, and a sensible invalidation strategy so users never see stale data. With 12+ years of Django delivery since 2014 and 50+ projects shipped, our team can profile your bottlenecks and put the right cache at the right level. Explore our Django development services or get in touch to scope your performance work.

Share this article