django-cachalot
django-cachalot is a Django package that automatically caches all Django ORM queries and transparently invalidates them when data changes. It aims to provide significant performance improvements by reducing database hits without requiring developers to write explicit caching logic. The library is actively maintained, with version 2.9.0 supporting recent Django releases and Python versions, and new releases frequently adapting to Django's evolution.
Common errors
-
TypeError: 'NoneType' object is not iterable
cause When `django-redis` is used as a cache backend and encounters a connection issue, it might return `None` for cache operations, which `cachalot`'s monkey-patching expects to be an iterable (e.g., an empty dictionary or list). This can crash the application.fixIn your `CACHES` setting for the `django-redis` backend, set `'OPTIONS': {'IGNORE_EXCEPTIONS': True}`. This configures `django-redis` to return default values (like an empty dictionary) on exceptions, allowing `cachalot` to gracefully fall back to database queries. -
Stale data displayed after modifying data outside Django's ORM.
cause django-cachalot relies on Django ORM operations (INSERT, UPDATE, DELETE) to trigger cache invalidation. If you modify database data directly (e.g., via SQL console, external scripts, or database triggers) without using the Django ORM, cachalot will not be aware of these changes and will continue to serve stale cached data.fixAfter performing external database modifications, manually invalidate the relevant cache entries using the `invalidate_cachalot` management command (`./manage.py invalidate_cachalot <app_label>[.<model_name>]`) or the `cachalot.api.invalidate()` function. -
Performance doesn't improve or even degrades on pages with many write operations.
cause django-cachalot's per-table invalidation strategy means that any write to a table invalidates *all* cached queries for that table. If a table is frequently updated, the cache might be invalidated more often than it's hit, leading to overhead without benefits or even a slowdown due to cache management.fixIdentify tables with high write frequencies. For these tables, consider excluding them from cachalot's caching using `CACHALOT_UNCACHABLE_TABLES` in your settings, or implement more granular caching strategies (e.g., per-object caching or manual caching for specific querysets) where appropriate. -
Queries using custom database aliases are not being cached.
cause By default, `django-cachalot` only caches queries on supported database engines (PostgreSQL, SQLite, MySQL). For custom or unsupported databases, it might be disabled by default.fixSet `CACHALOT_DATABASES` in your `settings.py` to explicitly list the database aliases where you want `django-cachalot` to be active. If your database is truly unsupported, you might also need to set `CACHALOT_USE_UNSUPPORTED_DATABASE = True` (though this is not recommended without careful testing).
Warnings
- breaking As of v2.9.0, Django 3.2 support has been dropped. Ensure your Django project is running a supported version (Django 4.2, 5.2, or 6.0).
- breaking With v2.6.0, support for Django 2.2 and 4.0 was dropped. Projects using these versions must stick to django-cachalot <2.6.0.
- gotcha django-cachalot caches results on a per-table basis, not per-object. If a single object in a table is modified, all cached queries related to that entire table are invalidated. This can lead to frequent cache invalidations and potential performance degradation if tables experience a high rate of modifications (e.g., >50 modifications per minute).
- gotcha Raw SQL queries (e.g., `QuerySet.extra`, `Model.objects.raw`, `cursor.execute`) are not cached by django-cachalot because it cannot reliably detect all affected tables for proper invalidation. Any data retrieved via raw SQL will bypass the cache.
- gotcha Using `django.core.cache.backends.locmem.LocMemCache` (local memory cache) is not suitable for multi-process environments (e.g., Gunicorn with multiple workers, Celery, RQ) because the cache is not shared between processes, leading to stale data.
- gotcha Changing the `CACHALOT_CACHE` setting to use a different cache alias in your `settings.py` will not automatically invalidate existing data in the *old* cache. This can lead to stale data being served until manually cleared.
- gotcha A recursion issue in atomic transactions (`transaction.atomic`) was fixed in v2.9.0. Prior versions might encounter unexpected behavior or errors when cachalot interacts with complex atomic blocks.
Install
-
pip install django-cachalot
Imports
- cachalot
INSTALLED_APPS = [ # ... 'cachalot', ] - cachalot_settings
from cachalot.settings import cachalot_settings
from cachalot import cachalot_settings
Quickstart
import os
# settings.py
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'a-very-secret-key')
INSTALLED_APPS = [
# ... other apps
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cachalot',
]
# Configure a cache backend (e.g., Redis)
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/1'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'IGNORE_EXCEPTIONS': True # Recommended for graceful degradation if cache is down
}
}
}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'password',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
# To verify caching, you can add CachalotPanel to Django Debug Toolbar
DEBUG_TOOLBAR_PANELS = [
# ... other panels
'cachalot.panels.CachalotPanel',
]