openedx.core.djangoapps.cache_toolbox package#

Subpackages#

Submodules#

openedx.core.djangoapps.cache_toolbox.app_settings module#

Settings for cache_toolbox.

openedx.core.djangoapps.cache_toolbox.core module#

Core methods#

openedx.core.djangoapps.cache_toolbox.core.delete_instance(model, *instance_or_pk)#

Purges the cache keys for the instances of this model.

openedx.core.djangoapps.cache_toolbox.core.get_instance(model, instance_or_pk, timeout=None, using=None)#

Returns the model instance with a primary key of instance_or_pk.

If the data is cached it will be returned from there, otherwise the regular Django ORM is queried for this instance and the data stored in the cache.

If omitted, the timeout value defaults to settings.CACHE_TOOLBOX_DEFAULT_TIMEOUT instead of 0 (zero).

Example:

>>> get_instance(User, 1) # Cache miss
<User: lamby>
>>> get_instance(User, 1) # Cache hit
<User: lamby>
>>> User.objects.get(pk=1) == get_instance(User, 1)
True
openedx.core.djangoapps.cache_toolbox.core.instance_key(model, instance_or_pk)#

Returns the cache key for this (model, instance) pair.

openedx.core.djangoapps.cache_toolbox.middleware module#

Cache-backed AuthenticationMiddleware#

CacheBackedAuthenticationMiddleware is an django.contrib.auth.middleware.AuthenticationMiddleware replacement to avoid querying the database for a User instance in each request.

Whilst the built-in AuthenticationMiddleware mechanism will only obtain the User instance when it is required, the vast majority of sites will do so on every page to render “Logged in as ‘X’” text as well to evaluate the result of user.is_authenticated and user.is_superuser to provide conditional functionality.

This middleware eliminates the cost of retrieving this User instance by caching it using the cache_toolbox instance caching mechanisms.

Depending on your average number of queries per page, saving one query per request can—in aggregate—reduce load on your database. In addition, avoiding the database entirely for pages can avoid incurring any connection latency in your environment, resulting in faster page loads for your users.

Saving this data in the cache can also be used as a way of authenticating users in systems outside of Django that should not access your database. For example, a “maintenance mode” page would be able to render a personalised message without touching the database at all but rather authenticating via the cache.

CacheBackedAuthenticationMiddleware is AUTHENTICATION_BACKENDS agnostic.

Implementation#

The cache and session backends are still accessed on each request - we are simply assuming that they are cheaper (or otherwise more preferable) to access than your database. (In the future, signed cookies may allow us to avoid this lookup altogether – whilst we could not safely save User.password in a cookie, we could use delayed loading to pull it out when needed.)

Another alternative solution would be to store the attributes in the user’s session instead of in the cache. This would save the cache hit on every request as all the relevant data would be pulled in one go from the session backend. However, this has two main disadvantages:

  • Session keys are not deterministic – after making changes to an auth_user row in the database, you cannot determine the user’s session key to flush the now out-of-sync data (and doing so would log them out anyway).

  • Stores data per-session rather than per-user – if a user logs in from multiple computers the data is duplicated in each session. This problem is compounded by most projects wishing to avoid expiring session data as long as possible (in addition to storing sessions in persistent stores).

Dependency with SafeSessionMiddleware#

CacheBackedAuthenticationMiddleware middleware logs out the user if the session hash is changed due to password change. It flushes the session and mark cookies for deletion in request which are then deleted in the process_response of SafeSessionMiddleware.

Usage#

To use, find MIDDLEWARE in your settings.py and replace:

MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
]

with:

MIDDLEWARE = [
    ...
    'openedx.core.djangoapps.cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',
    ...
]

You should confirm you are using a SESSION_ENGINE that doesn’t query the database for each request. The built-in cached_db engine is the safest choice for most environments but you may be happy with the trade-offs of the memcached backend - see the Django documentation for more details.

class openedx.core.djangoapps.cache_toolbox.middleware.CacheBackedAuthenticationMiddleware(*args, **kwargs)#

Bases: AuthenticationMiddleware, MiddlewareMixin

See documentation above.

process_request(request)#

openedx.core.djangoapps.cache_toolbox.model module#

Caching model instances#

cache_model adds utility methods to a model to obtain ForeignKey instances via the cache.

Usage#

from django.db import models
from django.contrib.auth.models import User

class Foo(models.Model):
    name = models.CharField(length=20)

cache_model(Foo)
>>> a = Foo.objects.create(name='a')
>>> a
<Foo: >
>>> Foo.get_cached(a.pk) # Cache miss
<Foo: >
>>> a = Foo.get_cached(a.pk) # Cache hit
>>> a.name
u'a'

Instances returned from get_cached are real model instances:

>>> a = Foo.get_cached(a.pk) # Cache hit
>>> type(a)
<class '__main__.models.A'>
>>> a.pk
1L

Invalidation#

Invalidation is performed automatically upon saving or deleting a Foo instance:

>>> a = Foo.objects.create(name='a')
>>> a.name = 'b'
>>> a.save()
>>> a = Foo.get_cached(a.pk)
>>> a.name
u'b'
>>> a.delete()
>>> a = Foo.get_cached(a.pk)
... Foo.DoesNotExist
openedx.core.djangoapps.cache_toolbox.model.cache_model(model, timeout=None)#

Adds utility methods to the given model to obtain ForeignKey instances via the cache.

Module contents#

cache_toolbox — Non-magical object caching tools for Django#

Introduction#

cache_toolbox is intended to be a lightweight series of independent tools to leverage caching within Django projects.

The tools are deliberately non-magical. That is to say, instances are never retrieved from caches behind your back and regular Django .filter() / .get() queries continue to work exactly as before.

Because of this, you can introduce cache_toolbox into your project slowly when needed rather than “switching” to it with invasive changes.