Memcache, memcache, memcache!

by Blake Schwendiman on May 20, 2009

Possibly the single-most important piece of advice I give to every software developer right now is to use memcache (or memcached to be specific). I’ve become an evangelist of the software since working for Squidoo.

Memcache is just exactly what it says it is — a memory cache daemon (service). It’s lightweight and very fast and can be used for all kinds of caching: page, database, remote data, etc., etc.

I use it on this blog in conjunction with fetching the Twitter comments that appear in the right-hand sidebar on the home page, for example. Here’s the general pseudo code for how memcache is used:

  key = generate_cache_key()
  if (value = is_key_in_cache(key))
    return value
  get_uncached_value() // DB, REST, complex code, etc.
  save_to_cache(key, value)

My actual implementation for the Twitter backtweets looks something like this:

$uri = 'http://backtweets.com/search.json?q=www.thewhyandthehow.com&key=API-KEY';
$cache_key = 'rest-service-' . md5($uri);
if ($result = Helpers::cacheGet($cache_key)) {  
  return $result;
}
 
$result = Helpers::remoteLoad($uri);
// check valid $result here
 
Helpers::cacheSet($cache_key, $result, 20, true);
return $result;

The helper functions look like this (class declaration not shown for brevity):

    public static function getCache() {
      if (self::$cache == null) {
         $temp = new Memcache();
         $temp->connect(MEMCACHE_HOST, MEMCACHE_PORT);
 
         self::$cache = $temp;
      }
 
      return self::$cache;
    }
 
    /**
     * set a value to the memcache
     *
     * @param string key name
     * @param mixed value
     * @param integer timeout (default 1 day)
     * @param boolean compression flag
     * @return boolean success
     */
    public static function cacheSet($key, $value, $timeout = 86400, $compress = true) {
      $cache = self::getCache();
      $result = false;
 
      if ($timeout > 0) {
        $timeout = min($timeout, 2592000);
      }
 
      if ($compress) {
        $result = $cache->set($key, $value, MEMCACHE_COMPRESSED, $timeout);
      } else {
        $result = $cache->set($key, $value, 0, $timeout);
      }
 
    /**
     * get an item from the cache
     *
     * @param string key
     * @return mixed value or false
     */
    public static function cacheGet($key) {
      $cache = self::getCache();
      return $cache->get($key);
    }

As you can see the code is very simple. The cacheGet function is trivial, so no discussion is needed. The cacheSet function is a little more complex, but basically that is to support a very simple calling convention and to ensure that the parameters are sane. One obviously missing check is to ensure that the Memcache variable returned by getCache is valid — I guess I have some work to do.

The advantage for using caching in this manner should be obvious. Instead of requiring an actual HTTP request per pageview, this method keeps a copy around for 20 minutes before initiating another request to the backtweets.com servers. This is friendly to the remote servers while also increasing the responsiveness of my application.

This logic applies to any big, slow or heavy processes that might occur on a web server, not just remote HTTP requests. If your application generates lots of mostly-static HTML markup that is re-used or requested frequently, it could be cached in memcache. If you have any big or frequently-requested database queries, throw the results in memcache and take the load off of your DB server.

AND…

Since memcache is based on TCP/IP, it doesn’t have to run locally relative to your application server. Have one big memcache box, or two, or three, and make requests from all of your application servers.

If your application is falling over because of load, you might want to take a look at the problem spots. You’ll probably find that using memcache will reduce overall load on your database and application servers and increase the responsiveness of your site.

Comments:

Comments on this entry are closed.