Memcache Feature-Bug Gotchas
Thursday, July 17th, 2008Recently 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); } } |
Despite my
For folks using a Mac, you’ll need to download the
I was unsatisfied with the SIM card insertion - there’s nothing but friction holding it in. I have a feeling over time it will fall out. The other connection that felt weak is the actual slot in the Mac. I’m not sure if this is a issue with the card or the laptop’s ExpressCard/34 slot. The old style PC Cards seemed to have a more solid connection and the handy eject button that popped out let you know if it was in all the way or not. 

My last test was to upload a few photos (in fact the ones I took for this post) just to see how that all performed. My expectations were pretty low given the issues with the download test. I was able to upload all of these photos (~204Kb) in about 10 seconds which I actually found reasonable. The larger screenshot above (~188Kb) uploaded in about 4 seconds which was even faster. My conclusion is that this certainly won’t be replacing my broadband cable modem for daily internet access (not to mention there’s a data cap on the service) but it’s a viable alternative to going without and for those who travel sufficiently, it’s a cheaper alternative to pay as you go WiFi in airports and hotels.






