Inevitably, there will be occasions during your applications development or
deployment where some task takes a significant amount of time to complete.
When this occurs, the best way to speed things up is with caching.
There are several ways to cache data under Pylons, depending on where the slowdown
is occurring:
- Templates - You can cache the results of an entire rendered template
using the 3 cache keyword arguments to the render calls.
These render commands can also be used inside templates.
- Mako/Myghty Templates - Built-in caching options are available for both
Mako and
Myghty template engines. They allow
fine grained caching of only certain sections of the template, as well as
caching of the entire template.
- Controllers - The cache object is available in controllers and templates
for use in caching anything that can be pickled.
- Browser-Side Caching - An ETag caching system is available that will allow
the browser to use its own cache of the page instead of requiring you to
regenerate the entire page. This system will work avoid repeated generations
of content, but if the browser has never seen the page it will still be
generated. Therefore using ETag caching in addition to one of the other types
of caching listed above will get you optimal through-put and avoid resource
intensive operations.
Note that this only helps if the entire page can be cached.
The two primary concepts to keep in mind when caching is that cache's have a
namespace and can have keys under that namespace. Consider that for a single
template, there might be multiple versions of the template you wish to keep
a cache for. The key's in the namespace are the "version" and the name of the
template is the "namespace". Both of these values must be Python strings.
In templates, the cache "namespace" will be set to the name of the template
being rendered automatically. Nothing else is required for caching, unless you
wish to control how long the template is cached for, and have multiple versions
of the template cached.
Inside a controller, the cache object will be available. If you have an action
or block of code that is resource or time intensive, it can be handy to cache
the result. The cache object can cache any Python structure that can be
pickled.
Consider an action where you'd like to cache some code that does a
time-consuming or resource intensive lookup and returns an object that can be
pickled (list, dict, tuple, etc.):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | def some_action(self, day):
# hypothetical action that uses a 'day' variable as its key
def expensive_function():
# do something that takes a lot of cpu/resources
# Get a cache for a specific namespace, you can name it whatever
# you want, in this case its 'my_function'
mycache = cache.get_cache('my_function')
# Get the value, this will create the cache copy the first time
# and any time it expires (in seconds, so 3600 = one hour)
c.myvalue = mycache.get_value(key=day, createfunc=expensive_function,
type="memory", expiretime=3600)
return render('/some/template.myt')
|
The createfunc option requires a callable object (function), that is then called by the cache anytime a value for the provided is not in the cache, or has expired in the cache. Since it is called with no arguments, the expensive function must not require any as well.
The cache also supports removing values from the cache by their key, and clearing the cache should you wish to reset it.
1
2
3
4
5 | # Clear the cache
mycache.clear()
# Remove a specific key
mycache.remove_value('some_key')
|
All render commmands have caching functionality built in. To use it, merely
add the appropriate cache keyword to your render call.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | class SampleController(BaseController):
def index(self):
# Cache the template for 10 mins
return render('/index.myt', cache_expire=600)
def show(self, id):
# Cache this version of the template for 3 mins
return render('/show.myt', cache_key=id, cache_expire=180)
def feed(self):
# Cache for 20 mins to memory
return render('/feed.myt', cache_type='memory', cache_expire=1200)
def home(self, user):
# Cache this version of a page forever (until the cache dir is cleaned)
return render('/home.myt', cache_key=user, cache_expire='never')
|
Pylons also provides the beaker_cache
decorator for caching the results of an entire function call (memoizing) to
pylons.cache.
It takes the same cache arguments (minus their cache_ prefix) as the
render function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | from pylons.decorators.cache import beaker_cache
class SampleController(BaseController):
# Cache this controller action forever (until the cache dir is cleaned)
@beaker_cache()
def home(self):
c.data = expensive_call()
return render('/home.myt')
# Cache this controller action by its GET args for 10 mins to memory
@beaker_cache(expire=600, type='memory', query_args=True)
def show(self, id):
c.data = expensive_call(id)
return render('/show.myt')
|
By default it uses all of the decorated function's arguments as the cache
key. It can alternatively use the request.GET query args as the cache key
when the query_args option is enabled. The cache key can be further
customized via the key argument.
Caching via ETag involves sending the browser an ETag header so that it knows
to save and possibly use a cached copy of the page from its own cache, instead
of your application sending it another.
Since the ETag cache relies on sending headers to the browser, it works in a
slightly different manner. The etag_cache function will return a Response
object with the proper HTTP headers set if the browser doesn't yet have a copy
of the page. Otherwise a 304 HTTP Exception will be thrown that is caught by
Paste middlware and turned into a proper 304 response to the browser. This will
cause the browser to use its own copy.
ETag based caching requires a single key, which is sent in the ETag HTTP header
back to the browser. The RFC specification for HTTP headers indicates that your
ETag header merely needs to be a string. This value does not need to be unique
for every URL, as the browser determines whether to use its own copy based on
the URL and the ETag key.
1
2
3 | def my_action(self):
etag_cache('somekey')
return render('/show.myt', cache_expire=3600)
|
Or to change other aspects of the response:
1
2
3
4 | def my_action(self):
etag_cache('somekey')
response.headers['content-type'] = 'text/plain'
return render('/show.myt', cache_expire=3600)
|
Note
In this example that we're using template caching in addition to ETag
caching. If a new visitor comes to the site, we avoid re-rendering the
template if a cached copy exists, and repeat hits to the page by that
user will then trigger the ETag cache. This example also will never
change the ETag key, so the browsers cache will always be used if it
has one.
Your ETag cache key will likely change depending on how often you want to have
the browser fetch a fresh copy of the page.
Pylons comes with caching middleware enabled that is part of the same package
that provides the session handling, Beaker. It
supports backends in memory, the filesystem, and memcached.