Advanced Caching with Django and Beaker

After seeing more than a few blog posts and packages attempt to provide more advanced caching capability for Django, it occurs to me I should actually just blog how to use Beaker in Django, rather than just keep mumbling about how “Beaker already does that”. So, if you've needed caching in Django that goes beyond using just one backend at a time, or maybe can actually cope with the Dog-Pile Effect, this is the blog entry for you (Until I flesh it out further into actual docs on the Beaker site).

Install Beaker

This is simple enough, if you have easy_install available, just:

easy_install -U Beaker

Or if you prefer to download tar files, grab the Beaker 1.4 tar.gz file

Configuring the Cache

Setting up Beaker's cache for your Django app is pretty easy. Since only a single cache instance is needed for an app, we'll set it up as a module global.

Create a beakercache.py file in your Django project with the following contents:

from beaker.cache import CacheManager
from beaker.util import parse_cache_config_options

cache_opts = {
    'cache.type': 'file',
    'cache.data_dir': 'cache/data',
    'cache.lock_dir': 'cache/lock'
}

cache = CacheManager(**parse_cache_config_options(cache_opts))

There's a lot more options available, such as memcached, configuring multiple cache backends at once, etc. Now that you know how to provide the configuration options, further customization can be done as needed using the Beaker configuration docs. (Note the very handy cache region configurations which make it easy to toggle cache backend configurations on the fly!)

Using the Cache

Beaker provides a conveinent decorator API to make it easy to cache the results of functions. In this example we'll just sleep and make a string including the time, add this to your views.py:

import time
from datetime import datetime

from django.http import HttpResponse

from YOURPROJECT.beakercache import cache

def hello(request):
    @cache.cache(expire=10)
    def fetch_data():
        time.sleep(4)
        return 'Hello world, its %s' % datetime.now()
    results = fetch_data()
    return HttpResponse(results)

In this case, the cached data is in a nested function with the decorator. It could of course be in the module elsewhere as well.

Hook the view function up in your urls.py, and hit the view. The first time it will wait a few seconds, then it will return the old time until the cache expires (10 seconds in this case).

The cached function can also accept positional (non-keyword) arguments, which will be used to key the cache. That is, different argument values will result in different copies of the cache that require those arguments to match.

That's it, it's really quite easy to use.

Update: It occurs to me this post does say ‘advanced', and that example wasn't very advanced, so here's something a bit more interesting. Let's configure cache regions to make it easy to toggle how long and where something is cached. Cache regions allow you to arbitrarily configure batches of settings, a ‘region'. Later you can then indicate you want to use that region, and it uses the settings you configured. This also make its easy to setup cache policies and change them in a single location.

In this case, we'll have ‘long_term' and ‘short_term' cache settings, though you can of course come up with as many regions as desired, with the name of your choice. We'll have the long_term settings use the filesystem, since we want to retain the results for quite awhile and not have them pushed out like memcached does. The short_term settings will use memcached, and be cached for only 2 minutes, long enough to help out on those random slashdog/digg hits.

In the beakercache.py file:

from beaker.cache import CacheManager
from beaker.util import parse_cache_config_options

cache_opts = {
    'cache.type': 'file',
    'cache.data_dir': 'cache/data',
    'cache.lock_dir': 'cache/lock',
    'cache.regions': 'short_term, long_term',
    'cache.short_term.type': 'ext:memcached',
    'cache.short_term.url': '127.0.0.1.11211',
    'cache.short_term.expire': '1200',
    'cache.long_term.type': 'file',
    'cache.long_term.expire': '86400',
}

cache = CacheManager(**parse_cache_config_options(cache_opts))

Now in our views.py:

import time
from datetime import datetime

from django.http import HttpResponse

from testdjango.beakercache import cache

def hello(request):
    @cache.region('long_term')
    def fetch_data():
        time.sleep(15)
        return 'Hello world, its %s' % datetime.now()
    results = fetch_data()
    return HttpResponse(results)

def goodbye(request):
    @cache.region('short_term'')
    def fetch_data():
        time.sleep(4)
        return 'Bye world, its %s' % datetime.now()
    results = fetch_data()
    return HttpResponse(results)     
Ben Bangert
Ben Bangert
Software Contriver

Code. Homebrew. Hike. Rollerblade.