A Cache Status View for Django

Fredrik Lundh | August 2007

Since I’m running effbot.org on a shared server with limited memory for long-running processes, I’m quite interested in keeping track of my Django application’s resource usage.

As part of this, I wanted an easy way to see what the memcached server I’m using for caching is up to, and came up with a a simple Django view that grabs the current status from the server.

Here’s how to implement this on your own Django site:

The first step is to add an entry to the URL configuration file that points to the view module; something like:

urlpatterns = patterns('',
    ...
    (r'^status/cache/$', 'mysite.memcached_status.view'),
)

Next, add the following view to your application, in the location you specified in the URL file:

 
# memcached_status.py

from django import http
from django.shortcuts import render_to_response
from django.conf import settings

import datetime, re

def view(request):

    try:
        import memcache
    except ImportError:
        raise http.Http404

    if not (request.user.is_authenticated() and
            request.user.is_staff):
        raise http.Http404

    # get first memcached URI
    m = re.match(
        "memcached://([.\w]+:\d+)", settings.CACHE_BACKEND
    )
    if not m:
        raise http.Http404

    host = memcache._Host(m.group(1))
    host.connect()
    host.send_cmd("stats")

    class Stats:
        pass

    stats = Stats()

    while 1:
        line = host.readline().split(None, 2)
        if line[0] == "END":
            break
        stat, key, value = line
        try:
            # convert to native type, if possible
            value = int(value)
            if key == "uptime":
                value = datetime.timedelta(seconds=value)
            elif key == "time":
                value = datetime.datetime.fromtimestamp(value)
        except ValueError:
            pass
        setattr(stats, key, value)

    host.close_socket()

    return render_to_response(
        'memcached_status.html', dict(
            stats=stats,
            hit_rate=100 * stats.get_hits / stats.cmd_get,
            time=datetime.datetime.now(), # server time
        ))

This, somewhat rough, view collects all statistics and other status variables from the memcached server in a stats object that’s passed on to the template, and also calculates the hit rate.

Finally, to view the statistics, you need a suitable template. Here’s a simple one that displays some of the variables, plus the hit rate:

<!-- templates/memcached_status.html -->

<h1>cache status</h1>

<ul>
<li>memory usage:
    {{ stats.bytes|filesizeformat }}
<li>keys in cache:
    {{ stats.curr_items }} of {{ stats.total_items }}
<li>cache hits:
    {{ stats.get_hits }} of {{ stats.cmd_get }}:
    <b>{{ hit_rate }}%</b>
<li>cache traffic:
    {{ stats.bytes_read|filesizeformat }} in,
    {{ stats.bytes_written|filesizeformat }} out
<li>uptime: {{ stats.uptime }}
</ul>

With this in place, restart the server and point your browser to the URL you specified in the URL configuration file (e.g. /status/cache), and provided that you’re logged in as a “staff member”, you’ll get something like:

cache status

  • memory usage: 11.3 MB
  • keys in cache: 2867 of 14624
  • cache hits: 24025 of 38102: 63%
  • cache traffic: 62.0 MB in, 129.4 MB out
  • uptime: 2:18:54

The memory usage and hit rate figures are probably what’s most interesting here.

When you use small caches, memcached has a tendency to grab noticably more memory than it’s allowed to use, and usage grows somewhat over time even after the cache has filled up; I’m not sure if this is a bug, or if it’s just including I/O buffers and other extra structures in the reported memory usage, but not in the cache size checks.

The hit rate depends a lot on your site’s characteristics, user access patterns, and cache timeouts. The higher the value, the less work your Django server has to do. But there’s a trade-off here, of course: you can reach nearly 100% hit rate by making the cache large enough and setting CACHE_MIDDLEWARE_SECONDS to something really big, but that’ll make your server extremely static — even if you change things, old versions will keep being served from your cache, and will be stuck in caches around the net, for a very long time.

I recommend values from a few minutes for commonly updated sites to one hour for more static sites. For highly dynamic sites, with lots of user interaction, you should probably not use the cache middleware at all; let your views use the cache to store individual page fragments and database result sets instead.

More Statistics #

The full set of variables is described in the memcached protocol specification:

http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt (dead link)

For your convenience, here’s a template that displays all documented variables, without any formatting:

<pre>
pid                   {{ stats.pid }}
uptime                {{ stats.uptime }}
time                  {{ stats.time }}
version               {{ stats.version }}
rusage_user           {{ stats.rusage_user }}
rusage_system         {{ stats.rusage_system }}
curr_items            {{ stats.curr_items }}
total_items           {{ stats.total_items }}
bytes                 {{ stats.bytes }}
curr_connections      {{ stats.curr_connections }}
total_connections     {{ stats.total_connections }}
connection_structures {{ stats.connection_structures }}
cmd_get               {{ stats.cmd_get }}
cmd_set               {{ stats.cmd_set }}
get_hits              {{ stats.get_hits }}
get_misses            {{ stats.get_misses }}
evictions             {{ stats.evictions }}
bytes_read            {{ stats.bytes_read }}
bytes_written         {{ stats.bytes_written }}
limit_maxbytes        {{ stats.limit_maxbytes }}
</pre>

You can include this temporarily when tweaking the status template, or, if you like raw data, just use it instead of the nicely formatted version.

 

A Django site. rendered by a django application. hosted by webfaction.