Fix Django Cache Errors After Deploy: "Can’t Get Attribute" When Unpickling

Fix Django errors after a deploy such as AttributeError Can’t get attribute on module, ModuleNotFoundError, or TypeError when reading cached objects: version cache keys per release, avoid pickling model instances, and clear or namespace the cache safely.

Problem statement

Right after a deploy, some pages fail with tracebacks from the cache layer:

AttributeError: Can't get attribute 'PriceSummary' on <module 'shop.services' ...>
ModuleNotFoundError: No module named 'shop.utils.pricing'
TypeError: __init__() missing 1 required positional argument: 'currency'

The errors fade as cache entries expire. Django's Redis and Memcached backends store Python objects with pickle. A pickle records the class's module path and name, and when read back, Python imports that module and rebuilds the object. If the new release renamed, moved, or changed the class, objects cached by the old release cannot be rebuilt. During a rolling deploy, old and new code even read each other's entries at the same time.

Why cached objects breakOld releasepickles objectsDeploy changesclass or moduleNew code readsold pickleUnpickle failsAttributeErrorPickled cache values refer to code by name, so renaming code breaks old entries.
  1. Short term: clear the affected keys, or the whole cache if it holds only disposable data.
  2. Version cache keys per release: set KEY_PREFIX or VERSION from the release identifier.
  3. Cache plain data (dicts, lists, strings, numbers) instead of instances of your own classes.
  4. Catch errors on cache reads and treat them as misses.
  5. Keep sessions out of a cache that you flush on deploys.

Step-by-step solution

1. Recover now

If the cache holds only data that can be recomputed:

python manage.py shell -c "from django.core.cache import cache; cache.clear()"

cache.clear() removes every key in that cache's Redis database, including sessions if you use the cache session backend, which logs everyone out. If sessions share the database, delete by pattern instead with django-redis: cache.delete_pattern("shop:*").

2. Namespace keys by release

Give every release its own key space so old and new code never read each other's entries:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": os.environ["REDIS_URL"],
        "KEY_PREFIX": "shop",
        "VERSION": os.environ.get("CACHE_VERSION", "1"),
    }
}

Set CACHE_VERSION to the commit SHA in your deploy. Old entries are ignored and expire on their own. The cost is a cold cache after every deploy, so do this for caches of computed objects, not for expensive data that rarely changes shape.

what survives a deployPickled class instances✕tied to module and class name✕breaks on refactors✕old and new code clashDicts, lists, primitives✓no code references✓safe across releases✓readable in redis-cliPlain data decouples the cache from your code layoutdjango-deployment.com
Convert objects to plain data before caching and rebuild them after reading.

3. Cache plain data

Instead of caching a PriceSummary instance, cache what it is made of:

def get_price_summary(product_id):
    key = f"price-summary:{product_id}"
    data = cache.get(key)
    if data is None:
        summary = compute_price_summary(product_id)
        data = {"net": str(summary.net), "tax": str(summary.tax), "currency": summary.currency}
        cache.set(key, data, 300)
    return PriceSummary.from_dict(data)

Model instances are a special case: Django pickles them with a version marker and warns about mismatches, but a changed model can still fail to load. Cache primary keys or values() output rather than instances.

4. Treat unreadable entries as misses

Wrap reads so a bad entry falls back to recomputing:

import logging, pickle
logger = logging.getLogger(__name__)

def safe_get(key, default=None):
    try:
        return cache.get(key, default)
    except (AttributeError, ModuleNotFoundError, ImportError, TypeError, pickle.UnpicklingError):
        logger.warning("discarding unreadable cache entry %s", key)
        cache.delete(key)
        return default

5. Mind rolling deploys

During a rolling or blue-green deploy, old pods keep writing entries the new pods read, and the reverse. Versioned keys solve this cleanly. Without them, renames must be done in two releases: first add the new class while keeping the old name importable, then remove the old one after the cache has turned over.

old and new code share one cacheOld release cachespickles PriceSummaryDeploy renames classnew pods startNew pod reads old keyAttributeErrorOld pod reads new keyalso failsVersioned keysno shared entriesBoth directions fail while old and new pods run togetherdjango-deployment.com
Errors stop on their own when entries expire, but users see them until then.

Explanation

pickle serializes an object as instructions that name its class by import path, plus its state. Loading runs those instructions, importing the module and looking up the class by name. Moving PriceSummary from shop.utils.pricing to shop.services breaks every existing pickle that names the old path, and changing __init__ or __slots__ can break reconstruction even without a move. Django's cache backends use pickle by default for convenience, which ties cache contents to the code that wrote them.

Edge cases / notes

  • JSON serializer. django-redis supports a JSON serializer, which forces plain data and avoids this class of error entirely, at the cost of not caching arbitrary objects.
  • Celery results. Celery's result backend can hit the same problem with pickle; use the JSON serializer for tasks and results.
  • Sessions. Django sessions use JSON by default, so sessions are not affected unless you switched to PickleSerializer.

Next steps

FAQ

Why do the errors disappear after a while?

Old entries expire based on their timeout, and new ones are written by the new code.

Is clearing the cache on every deploy a good idea?

It works for small sites but causes a load spike on busy ones. Versioned keys achieve the same isolation and let old entries expire quietly.

Can I cache model instances safely?

It works until the model changes. Caching primary keys or values() dictionaries is more robust.

Does this affect Memcached too?

Yes. Django's Memcached backend also pickles values.

Keep going