Igor Kromin |   Consultant. Coder. Blogger. Tinkerer. Gamer.

One of the first features I wanted to implement over at AtariGamer.com has been the use of Memcache for static or near-static data caching. The intent was to reduce the load on the instance and to avoid spawning new instances unnecessarily (i.e. keep costs down). My goal was to cache everything that could be generated once and reused many times. The following qualified in my case: minified JavaScript and CSS resources, generated static pages, cached JSON responses for reference data kept in the DataStore.

Even though Google's memcache documentation is quite good and the examples are very straight forward and aren't all that much code, I still didn't want to repeat the same code over and over so I came up with something reusable. Read on to check it out.

On a side note — as it stands now, I'm getting around a 70% hit rate on the cached data, which is quite good I think.
memcache.png


These are the steps I wanted to execute for every request that could be put into Memcache:
  1. Check a global Memcache disable flag (useful for debugging)
  2. Check if the logged in user is an Admin user and skip Memcache (to avoid poisoning the cache with admin requests)
  3. Check if content is in Memcache
  4. Return content if found
  5. Generate/query content, store in Memcache and return the content if not found


All simple steps but totally unnecessary to repeat everywhere that Memcache was being used. So with that said, lets look at the code.

First I defined a consts.php file that held a couple of global constants that I could use to completely disable Memcache use and control the default expiry time of items in the cache...
 consts.php
/* whether data should be stored in MemCache */
const AG_USE_MEMCACHE = true;
/* default amount of seconds to keep data in memcache (30 days) */
const AG_MEMCACHE_EXPIRE = 2592000;


Then I put the utility code into MemcacheUtil.php...
 MemcacheUtil.php
<?php
use google\appengine\api\users\UserService;
/**
* Utility for working with memcache.
*/
class MemcacheUtil
{
/**
* Fetches content from memcache if it exists, otherwise calls the $contentFunc
* to generate content to store in memcache and return.
*/
public static function serveFromMemcache($key, $contentFunc,
$expire = AG_MEMCACHE_EXPIRE) {
$content = false;
$isAdmin = false;
$user = UserService::getCurrentUser();
if (isset($user) && UserService::isCurrentUserAdmin()) {
$isAdmin = true;
}
if (AG_USE_MEMCACHE && !$isAdmin) {
$memcache = new \Memcache;
$content = $memcache->get($key);
}
if ($content === false) {
if (is_callable($contentFunc)) {
$content = $contentFunc();
}
else {
throw new \Exception('Content function not callable.');
}
if (AG_USE_MEMCACHE && !$isAdmin) {
$memcache->set($key, $content, 0, $expire);
}
}
return $content;
}
/**
* Removes an entry from the memcache.
*/
public static function remove($key) {
if (AG_USE_MEMCACHE) {
$memcache = new \Memcache;
$memcache->delete($key);
}
}
}
?>




The code is fairly straight forward and executes the steps that I outlined above, so I won't be going through it line by line. Instead lets see how it is used.

Anywhere that I wanted to either serve data from Memcache or generate it and store it in Memcache if it wasn't available, I could do something like this...
 PHP
$data = MemcacheUtil::serveFromMemcache('datakey', function() {
return 'somedata';
}
);


The 'datakey' is just an example key string that is used to look up or store data in Memcache. In most cases this should be based on some ID e.g. the page ID being generated. I actually ended up adding some other functions to the MemcacheUtil class that would generate these data keys for me in a consistent manner (not shown in the code above).

The anonymous function in the example is what is called to generate data if the utility code doesn't find a value in Memcache. In the example above it always returns the same string, a string - 'somedata' but in reality the body of this function can be much more complicated e.g. looking up something from the DataStore then populating a Smarty/Twig template and generating the HTML for a specific page.

The content generation function can also make use of closures, so it's possible to do something like this...
 PHP
$someOtherString = 'derp';
$data = MemcacheUtil::serveFromMemcache('datakey', function() use ($someOtherVar) {
return $someOtherVar . ' somedata';
}
);


The above is a trivial example, in my case I make use of closures to pass the Symphony Request object to my content generation function.

It is also possible to provide a custom data expiry time if the default is not sufficient, just pass an integer value after the content generation function.

The remove() method on the MemcacheUtil class is just a convenience method to remove data from the cache. It does a basic global disable check and removes data if Memcache is not disabled.

-i

Hope you found this post useful...

...so please read on! I love writing articles that provide beneficial information, tips and examples to my readers. All information on my blog is provided free of charge and I encourage you to share it as you wish. There is a small favour I ask in return however - engage in comments below, provide feedback, and if you see mistakes let me know.

If you want to show additional support and help me pay for web hosting and domain name registration, donations, no matter how small, are always welcome!

Use of any information contained in this blog post/article is subject to this disclaimer.
comments powered by Disqus
Other posts you may like...