Tags

, , , , ,

I don’t think it’s any secret that in most modern, dynamic web applications the DB becomes the bottleneck, umm…. let’s say, 96.7% of the time.

Thankfully, we have a relatively painless way to speed up any DB-driven application by employing some caching mechanism.
memcached, is a popular choice, because it is fast, simple to install and use, and works very well for alleviating the DB load. Not to mention, that it comes nicely integrated into the CakePHP core.

Before we proceed there are a couple of simple things to keep in mind:

1. Memcached is best used to cache small, often repeated queries. By small, I mean queries that do not return hundreds or thousands of results (well, you really shouldn’t have queries like that to being with ;))
2. Be mindful of the cache duration time, you don’t want to draw results from cache, if the query results change very often. So set your caching duration appropriately.

As always, let’s jump into an example…

We have a site, which neatly displays five of the most recent articles on a few select pages. The site is relatively popular and gets over 30,000 visitors a day. New articles, however, get published once a day or every other day.
You can imagine that going to the database for the same exact query (and results) over and over again, is going to put absolutely unnecessary stress on your DB.

Therefore, this is a perfect example of where cache can be a life (or database) saver.

First, some prerequisites…

You need to download, install and have memecached server (daemon) up and running. The installation should be very simple, and to keep this post reasonably short, I’ll let you google for that on your own (or check this post).
Secondly, you need to ensure that your PHP installation has the memcached lib enabled. The easiest way to do so is to check the phpinfo() and look for the “memcache” section.
If it’s not available, please refer to the PHP manual on how to install one… in many cases it could be as simple as un-commenting the memcached extension in php.ini and restarting your web server.
Last, but not least, you need to switch CakePHP caching engine from the “FileEngine” (default) to “Memcache”.

Just by making a little adjustment in core.php:

Cache::config(‘default’, array(‘engine’ => ‘Memcache’));
(This assumes that you are using all defaults, see core.php for more detailed options).

If all goes well, and you have the default homepage for CakePHP, it should tell you that: “The MemcacheEngine is being used for caching”.

Now we are ready for the fun stuff…

Going back to our example, we’ll assume an Article model, with a getRecentFive() method to return the five most recent articles.

function getRecentFive () {

return $this->find('all', array('conditions' => array('Article.status' => 1),
                                       'limit' => 5,
                                       'order' => 'Article.updated DESC',
                                       'recursive' => -1));
}

So far, so good…

To keep things rather plain we’ll simply call it from the Articles Controller, like so:

pr($this->Article->getRecentFive());

It goes without saying, that we should see an array returned by the find() with the five most recent articles.

Finally, let’s see how we can take advantage of the MemcacheEngine to easily save our DB a few hits by adjusting our getRecentFive() method with a couple of lines of code…

function getRecentFive () {

          if(!$recentFive = Cache::read('recentFive')) {

            $recentFive = $this->find('all', array('conditions' => array('Article.status' => 1),
                                                'limit' => 5,
                                                'order' => 'Article.updated DESC',
                                                'recursive' => -1));

            Cache::write('recentFive', $recentFive, 86400);
          }

          return $recentFive;
        }

Let’s take a quick look at the code above.

First, we are checking if a cache with a key “recentFive” is available, while we assign the contents of the cache to the $recentFive variable.

If the cache is not present, we do a good ol’ find(‘all’) and write the results to cache…

Now, let’s break down this line of code: Cache::write(‘recentFive’, $recentFive, 86400);… we are basically writing to cache to a key called “recentFive” (it can be any arbitrary string). The data we are writing is, of course, the result of our find(‘all’). And lastly, we set the duration in seconds, in this example I set it for one day.

So, to play a little scenario, the first time we are calling the getRecentFive() method, the cache is not available. Therefore, we do a find(), get the results, write them to cache and return the result back to our controller. If you are trying out an example, you’ll see a simple query displayed in the SQL debug.

The second time, however, the cache is already available, so rather than hitting our DB, we read the results from cache and return it back to the controller. Now, you’d notice that there is no query executed in the DB, yet the same result as previously is returned back to the controller.

Thus, we save precious hits on the DB and happily send it to take a little vacation.

P.S. This example can be extended to automatically generate cache using the afterSave() and afterDelete() call-backs, but that might wait for another post or something for you to play with ;)