Django Cache
Django Cache#
The cache system requires a small amount of setup - you must tell it where to store the cache
- database
- filesystem
- memory
An important decision that affects your cache’s performance
It goes in the CACHE setting
Memcached#
Memcached is an entirely memory-based cache server.
Used to reduce database access and dramatically increase site performance
After installing Memcached itself, you’ll need to install a Memcached binding.
There are several Python Memcached bindings available; the two supported by Django are pylibmc and pymemcache
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
or using a socket:
Unix sockets are a form of communication between two processes that appears as a file on disk.
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
One excellent feature of Memcached is its ability to share a cache over multiple servers - this means you can run Memcached daemons on multiple machines, and the program will treat the group of machines as a single cache - without needing to duplicate the cache values
Example below uses shared cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
Memory-based caching has a disadvantage: because the cached data is stored in memory, the data will be lost if your server crashes
Without a doubt, none of the Django caching backends should be used for permanent storage
Redis#
Redis is an in-memory database that can be used for caching
To begin you’ll need a Redis server running either locally or on a remote machine.
The python binding for redis: redis-py is required and hiredis is recommended.
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
}
}
with auth:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://username:password@127.0.0.1:6379',
}
}
servers setup in replication mode, write operations are done on the first server read operations are performed on the others at random:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': [
'redis://127.0.0.1:6379', # leader
'redis://127.0.0.1:6378', # read-replica 1
'redis://127.0.0.1:6377', # read-replica 2
],
}
}
Database caching#
Django can store its cached data in your database
This works best if you’ve got a fast, well-indexed database server.
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
Unlike other cache backends, the database cache does not support automatic culling of expired entries at the database level. Instead, expired cache entries are culled each time
add()
,set()
, ortouch()
is called.
Creating the cache table#
python manage.py createcachetable
Like
migrate
,createcachetable
won’t touch an existing table. It will only create missing tables.
Filesystem caching#
The file-based backend serializes and stores each cache value as a separate file
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
}
}
LOCATION
should be absolute path
Make sure the cache is readable and writable by the server process.
Local-memory caching#
The default cache
This cache is per-process and thread safe
Thread safe means the code will work as expected even though being run simultaneously by multiple threads. Remember threads share memory and the data structures within.
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
The cache
LOCATION
is used to identify individual memory stores. If you only have one locmem cache, you can omit theLOCATION
- The cache uses a least-recently-used (LRU) culling strategy
- Note that each process will have its own private cache instance, which means no cross-process caching is possible.
- This also means the local memory cache isn’t particularly memory-efficient
- Probably not a good choice for production environments.
- Nice for development.
Dummy Caching#
Doesn’t actually cache - just implements the caching interface without doing anything
This is useful if you have a production site that uses heavy-duty caching in various places but a development/test environment where you don’t want to cache and don’t want to have to change your code to special-case the latter.
Settings:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
Cache Arguments#
Additional keys in cache settings:
TIMEOUT
: default timeout in seconds. Defaults to 300 seconds (5 mins).None
for not expire.0
for no cache/don’t cache.OPTIONS
:MAX_ENTRIES
: Max entries before old entries deleted. defaults to 300.CULL_FREQUENCY
: Fractions of entries removed whenMAX_ENTRIES
reached. Defaults to 3. So1/3
is culled each time. Value of0
means the entire cache is dumped.
KEY_PREFIX
: string prepended to all keysVERSION
: default version number for cache keysKEY_FUNCTION
: dotted path for function to create final cache key
Eg. Memcache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'binary': True,
'username': 'user',
'password': 'pass',
'behaviors': {
'ketama': True,
}
}
}
}
Eg. Redis
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
'OPTIONS': {
'db': '10',
'parser_class': 'redis.connection.PythonParser',
'pool_class': 'redis.BlockingConnectionPool',
}
}
}
The Per Site Cache#
The simplest way to cache is to cache the entire site
Add UpdateCacheMiddleware
and FetchFromCacheMiddleware
:
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
Then add required settings:
CACHE_MIDDLEWARE_ALIAS
– The cache alias to use for storage.CACHE_MIDDLEWARE_SECONDS
– The number of seconds each page should be cached.CACHE_MIDDLEWARE_KEY_PREFIX
– If the cache is shared across multiple sites using the same Django installation, set this to the name of the site, or some other string that is unique to this Django instance, to prevent key collisions. Use an empty string if you don’t care.
FetchFromCacheMiddleware
:
- caches GET and HEAD responses with status 200
- Responses to requests for the same URL with different query parameters are considered to be unique pages and are cached separately
- This middleware expects that a HEAD request is answered with the same response headers as the corresponding GET request; in which case it can return a cached GET response for HEAD request.
UpdateCacheMiddleware
sets:
Expires
to the current datetime plusCACHE_MIDDLEWARE_SECONDS
Cache-Control
to give a max age for the page
Cache keys:
- If
USE_I18N
is set to True then the generated cache key will include the name of the active language. - Cache keys also include the current time zone when USE_TZ is set to True.
The per-view cache#
django.views.decorators.cache.cache_page(timeout, *, cache=None, key_prefix=None)
Caching the output of individual views
Example:
from django.views.decorators.cache import cache_page
@cache_page(900)
def my_view(request):
...
900 seconds
The cache timeout set by cache_page takes precedence over the max-age
directive from the Cache-Control
header
The per-view cache, like the per-site cache, is keyed off of the URL. If multiple URLs point at the same view, each URL will be cached separately,
Example:
urlpatterns = [
path('foo/<int:code>/', my_view),
]
Cached seperately:
/foo/1/
/foo/23/
Direct to use a specific cache:
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
Override cache prefix (of CACHE_MIDDLEWARE_KEY_PREFIX
):
@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
...
cache_page
automatically sets Cache-Control
and Expires
headers in the response
Per-view cache with UrlConf#
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]
Template fragment caching#
More control and granularity
{% load cache %}
Eg.
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
Caching per-user or per some other paramter
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
Language specific
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% translate "Welcome to example.com" %}
{% endcache %}
The low-level cache API#
Your site includes a view whose results depend on several expensive queries - the results of which change at different intervals
The per site, page or fragment cache would not work well as the data still changes enough to affect the business but not often enough that it needs to query the database every time - slowing down the responses.
You can use this API to store objects in the cache with any level of granularity you like
You can cache any Python object that can be pickled safely: strings, dictionaries, lists of model objects, and so forth.
Access the cache:
from django.core.cache import caches
cache1 = caches['myalias']
Repeated requests will access the same object
To provide thread-safety, a different instance of the cache backend will be returned for each thread
Default cache:
from django.core.cache import cache
# equivalent to: caches['default'].
Basic Usage#
# cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)
cache.set('my_key', 'hello, world!', 30)
# cache.get(key, default=None, version=None)
cache.get('my_key')
The timeout argument is optional and defaults to the timeout argument of the appropriate backend in the CACHES
setting
If an object does not exist None
is returned.
If you need to determine whether the object exists in the cache and you have stored a literal value None, use a sentinel object as the default
sentinel = object()
cache.get('my_key', sentinel) is sentinel
sentinel value (flag, signal value) - condition for termination - wikipedia
Default argument:
cache.get('my_key', 'has expired')
Add (will not attempt update):
cache.add('add_key', 'New value')
Return value is True or False whether it did add
Get or set:
cache.get_or_set('my_new_key', 'my new value', 100)
Any callable can be used as a default value:
>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
Get many:
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
Set many:
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
Delete cache:
cache.delete('a')
Delete many:
cache.delete_many(['a', 'b', 'c'])
Remove everything in your cache (even stuff not set from your application):
cache.clear()
Set a new expiration for a key:
cache.touch('a', 10)
touch()
returns True if the key was successfully touched, False otherwise.
Increment and decrement:
>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6
incr()
/decr()
methods are not guaranteed to be atomic. On those backends that support atomic increment/decrement (most notably, the memcached backend), increment and decrement operations will be atomic
Close the connection:
cache.close()
The async variants of base methods are prefixed with a, e.g.
cache.aadd()
orcache.adelete_many()
Information on async support of cache
Cache key prefixing#
If you are sharing a cache instance between servers, or between your production and development environments, it’s possible for data cached by one server to be used by another server.
Django will automatically prefix the cache key with the value of the
KEY_PREFIX
cache setting
Cache versioning#
When you change running code that uses cached values, you may need to purge any existing cached values.
Easiest way is to flush the entire cache - but this can lead to the loss of cache values that are still valid and useful.
There is a system-wide identifier: VERSION
The value of this setting is automatically combined with the cache prefix and the user-provided cache key to obtain the final cache key.
Primitive (basic building block) cache functions accept the version parameter:
cache.set('my_key', 'hello world!', version=2)
Cache key transformation#
Give a dotted path to a function called make_key()
in the KEY_FUNCTION
setting.
def make_key(key, key_prefix, version):
return '%s:%s:%s' % (key_prefix, version, key)
Cache key warnings#
Memcached does not allow cache keys longer than 250 characters or containing whitespace or control characters, and using such keys will cause an exception.
Silence warnings with:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)