Archive for July, 2008

Memcache Feature-Bug Gotchas

Thursday, July 17th, 2008

Recently I’ve been doing a lot of work with memcached using PHP and have been bitten a few times by different things with how things worked. I’m calling those items out here so anyone getting started with Memcache can learn from my mistakes. Memcached is an amazingly powerful caching layer with lots and lots of online documentation. It’s easy to get running on Linux and hooking PHP into it - I’ll save yet another post about how to do it since there are so many excellent resources already. The hard part is determining where you’ll implement it and in what way. For this post, I’ll leave the implementation strategy aside and walk you through a couple of examples of where I’ve been bit. I’ve created a layer to further abstract the memcache() object in PHP so I can ignore dealing with the add() vs. replace() vs. set() switches and allowing me to have one stop shopping for all of my configuration settings. The source for that basic class is included at the bottom of this post, feel free to use/distribute as you see fit.

One last item before we delve into the examples. Memcached and memcache are not quite the same thing. When I reference Memcached - I’m actually referring to the server instance of Memcache which you are running on the server, which is accessible using a variety of methods, on a variety of platforms including but not limited to PHP. When I use memcache in this post, I’m referring to the API hooks that have been created for PHP to interact with your Memcached server instance. Some (or possibly all) of these items ONLY APPLY TO PHP and shouldn’t be construed as feature-bugs with the Memcached server itself.

Compression and Object Sizes

It took me a while to track down this bug - but I finally read up on memcache a bit more and learned that memcache behaves oddly with small chunks of information if compression is turned on. I’m pretty sure this is memcache not memcached that’s causing the issue because the compression layer happens in PHP. The output for the following code is The value is not the same. However, if $compression is set to false, it will work as expected. Integers and character strings seem to be okay with compression on - as do complex objects. The setCompressThreshold method allows adjustment of this size but I’ve gotten in the habit of not caching simple values like true and false - instead opting to cache objects, classes, arrays and alternately JSON.

// create the cache
$cache = new Memcache();
$cache->addServer("localhost","11211");
$compression = true;
 
// create the value in the cache
$x = true;
$cache->add("x", $x, $compression, 10);
 
// access the value
$y = $cache->get("x", $compression);
 
// check what happened
if($x == $y){
   print "The value is the same";
} else {
   print "The value is not the same";
}

Caching of Class Objects

When caching complex objects like classes, memcache serializes the entire object and then caches it was as it was instantiated. So be warned if your class definition changes, you’ll need to flush your cache entirely of those objects or you might find it behaves a little differently than your expecting. Let’s say you have a class with a few properties that update multiple properties when certain methods are called and you wish to change one of those properties slightly. Any objects that are in the cache already will continue to work with the old values until they are flushed from the cache. It’s not sufficient to read it out and put it back in - the object really is your OLD class definition.

class Foo{
   protected $property = array();
   public function __construct($arr){
      if(count($arr) > 0){
         $this->properties = $arr;
      }
   }
   public function __get($key){
      return $this->property[$key];
   }
   public function __set($key, $value){
      $this->property[$key] = $value;
   }
}

So now you can create Foo objects all day and stuff all sorts of information into them and cache them. You can also get them back out willy nilly later (I’ll use my cache class to save time on the code below).

$foo1 = new Foo(array("apples"=>11,"orange"=>20));
$cache->set("foo1",$foo1, 60);
$foo2 = $cache->get("foo1");
echo $foo2->apples; // should be 11

So we see that all works but, what if we change the way the class works? For example adding a layer of math to calculate a tax or something along those lines.

class Foo{
   protected $property = array();
   public function __construct($arr){
      if(count($arr) > 0){
         $this->properties = $arr;
      }
   }
   public function __get($key){
      return round($this->property[$key] * .9); // calculate storage/depreciation loss
   }
   public function __set($key, $value){
      $this->property[$key] = $value;
      $this->property['num_items'] = count($this->properties) - 1;
   }
}

Our existing cached object doesn’t behave as expected. One of two things seems to happen, and I haven’t fully flushed it out what happens when. First, the object just comes back as it was initially instantiated or second, it silently dies without returning an error. This might be a good reason to create a version value for your cached objects so you can switch on the version to determine if the cached value is valid.

// let's access our existing cache object from before again...
$foo2 = $cache->get("foo1");
echo $foo2->apples; // we might hope for 10 but...

Database Results

Caching of resources doesn’t work. The data being cached needs to be able to be serialized by memcache so it can be inserted into memcached. Database handles are much like your memcached connection - they’re a socket you talk to and unfortunately, so are MySQL results. There are good reasons for this so don’t gripe about it. You’ll need to write a simple wrapper that does all of the result parsing for you prior to caching. Then you can easily create a cacheable MySQL object that can be inserted into memcached. It only takes a few minutes to do this and I may even post later describing the wrappers I’m now using to do just this. Until then - know that you can quickly create an array of your data using the following code and cache that result instead.

// create a cache object (using class from below)
$cache = new Cache();
 
// create an array to populate the data with
$data_array = array();
 
// run the query 
$mysqli_result = $mysqli->query("select * from table where condition=true");
 
// stuff all the data into the array
while($row = $mysqli_result->fetch_assoc()){
   $data_array[] = $row;
}
 
// cache the array
$cache->set("query_data",$data_array,90);

Cache Time to Live

Nothing too big here, but if you provide 0 (zero) or false for a time to live/expiration value, the item never expires, it just gets pushed out if needed later. This all happens on a LRU basis and is well documented.

Protect your Namespaces

This may seem trivial, but I’ve been bit here too. Often it’s sufficient to use one server for multiple tasks. Since Memcached is easy to run in one large pool and share it among multiple resources (much like you would with MySQL) it’s easy to share across multiple applications. There are some nice economies of scale this will afford you. But consider the following bug you could create for yourself in your logic.

Application 1 accessing it’s DB table.

 
// Application 1 fetching content about a user
$memcache = new Cache();
$query = "select * from users where userid = 12";
$result = $memcache->get(hash(md5,$query));
 
// The data wasn't in cache, so we run the query below and store the data
if(!$result){
     $result = $mysql->query($query);
     $memcache->set(hash(md5,$query), $result, 600);
}

Application 2 accessing it’s DB table.

// Application 2 fetching content about a user
 
$memcache = new Cache();
$query = "select * from users where userid = 12";
$result = $memcache->get(hash(md5,$query));
 
// the value existed in cache - so it skips the query and uses the cached value
if(!$result){
     $result = $mysql->query($query);
     $memcache->set(hash(md5,$query), $result, 600);
}

Application 2 and Application 1 are using the EXACT same key to reference their data. Unless this is intentional (because they share a common database) it can be a real pain to debug. The easiest way to correct this is to create a namespace for the cache layer and append it to any keys you may use. The example class provided below does just that with minimal fuss. The code above would be changed to reflect the correct namespace for each application and they could co-exist using the memcached server together.

// in app 1 - use that namespace
$cache = new Cache("app1");
 
// in app 2 - use that namespace
$cache = new Cache("app2");

Memcache Abstraction Class

This is the abstraction class I use to handle all memcache interaction. It’s little more than a thin veneer over the existing PHP object. You can see where it’s easy to expand this basic cache layer within the constructor and you can tune for your data, servers and other bits relevant to your implementation as needed.

class Cache{
 
   protected $cache = false;
   protected $namespace = "";
 
   public function __construct($namespace = ""){
      $this->cache = new Memcache();
      $this->cache->addServer("localhost","11211");
      $this->cache->setCompressThreshold(127,0.2);
      $this->namespace = $namespace;
   }
 
   public function __destruct(){
      $this->cache->close();
   }
 
   public function set($key, $value, $ttl = 600){
      $this->cache->set($key . $this->namespace, $value, true, $ttl);
   }
 
   public function get($key){
      return $this->cache->get($key . $this->namespace, true);
   }
 
}

Tesla Roadster

Wednesday, July 9th, 2008

Tesla Roadster Right now I think I’d rather have a Tesla Roadster than an iPhone. Sure that’s a bold statement considering I can afford an iPhone and not a roadster but I think the car takes the cake for innovation. Being a huge fan of technology and cars I’ve drooling over this car since I first heard about it about a year ago and now that they’re “available” I’m equally as excited to see what they do with the sedan rumored for 2010 production.

See what the hype is about in these videos.

Game Networks on Facebook - Fail!

Friday, July 4th, 2008

Muddl M Logo When I finished the Facebook version of Muddl, it was clear the game wasn’t sufficiently viral to draw big traffic on it’s own, it needed a little boost. Zynga was present at GSP East and talked about their game network that developers could join for free in a link exchange type format. I’d heard about it before and even looked at it for a different application I work on — I signed up to give it a whirl. Since I’m not one to put all of my eggs in one basket I also looked at the game bar from SGN as well — here’s what I found. (more…)

Development on the road

Tuesday, July 1st, 2008

Long Road For the last couple of weeks I’ve been developing and working completely remotely. Removed from my office, removed from my resources and often while in remote locations from the car with my family as we travel from destination to destination. It’s been enjoyable to be part of everything the family is doing but it has not been without difficulty. For example, it can be hard to debug complex problems with kids unhappy in the back seat or to read a terminal session with the sun washing out the screen. It can also be challenging to be working via SSH and have the connection drop because we travel through an area without data connectivity.

Those sorts of problems aside, the one thing I miss most is my reference library. I’ve amassed a number of books that are helpful references for obscure bits of knowledge I don’t keep locked away in my mind. Last year I travelled with my books and never used them. This year, however, I’ve found myself wanting them in a couple of instances while working. While I can use Google for the majority of the code snips and shortcuts I would need, it’s a highly inefficient, to continually switch windows. I’ve contemplated signing up for O’Reilly Safari but have found digital books don’t save any time over Google and the $22.99 or more per month seems expensive for materials generally available elsewhere and locatable through Google.

© 1998-2008 AF-Design, All rights reserved.