Caching in Joomla 1.5 with Fault Tolerance

Overview

If you've used Joomla before, you may be aware that there are built-in caching features which you can activate to reduce the number of expensive database calls needed to render a page. This feature is also available to your own custom code, whether that be in the form of components, modules, or plugins.

The actual mileage for this feature may vary due to the speed of the server and what Joomla decides to cache. Personally I think Joomla's use of caching, specifically how they use it for articles, leaves something to be desired. The downsides include content that doesn't get flushed out once it has changed or content that has too short of a lifetime to prove useful. Because of this, I don't think lot of developers realize that you can use it to cache your own components, plugins, and modules reliably and without affecting the user experience in a negative way, all the while in a modest environment (i.e. a server environment that doesn't have special software like memcached installed.)

You might consider implementing a little caching if:

  • Your code is heavy and slow (Don't take it personally, mine is too sometimes)
  • Your code is high-latency requiring network resources that may or may not actually be available
  • You have a crazy number of visitors and your database is becoming a significant bottleneck

In my own recent project I was able to improve my access times of some SOAP requests by 500% (~1 second/request down to ~200ms/request).

Let's get our bearings

Overview

Here's a basic example of some code which initializes a JCache object and get's some data using it. You'll notice that there are two functions, one which just generically gets some data getData and another function getDataCached which instantiates the cache and calls getData. We need to do this so that our cache is aware of what the inputs and outputs are, that way it can do a lookup on the cache and return data without ever touching getData.

com_cachetest/cachetest.php:


class CacheTest {
    function getData($url) {
        // ... Get some data

        // in this case, I'm just returning some dummy data instead of
        // downloading a web page which if we plan for the worst case scenario,
        // might not be available.

        $data = $url;

        // send data back
        return $data;
    }
    function getDataCached($url) {

        $group = "cachetest";
        $function = array("CacheTest", "getData");

        // get a cache object and name it something useful
        $cache =& JFactory::getCache( $group );
        $cache->setCaching( 1 ); // enable caching (1 = enabled, 0 = disabled)

        // call our function "getData" through the cache
        $result = $cache->call( $function , $url);

        return $result;
    }
}

$data = CacheTest::getDataCached( "http://example.com" );
var_dump($data); // to see what we actually get

Fault Tolerant Caching

One issue in the above example is that if getData returns some bogus data, it's going to be stuck in the cache and you won't realize it until it's too late. This isn't a likely scenario for a quick database call but if you have a network request that is waiting and waiting and eventually reaches timeout, the cached data will possibly be empty. This can be problematic but it's also easily avoided. In the following example we do a quick check to see if the data is invalid, remove the old data and get a fresh copy.

com_cachetest/cachetest.php:


class CacheTest {
    function getData($url) {
        // ... Get some data

        // in this case, I'm just returning some dummy data instead of
        // downloading a web page which if we plan for the worst case scenario,
        // might not be available.

        $data = $url;

        // send data back
        return $data;
    }

    function getDataCached($url) {

        $group = "cachetest";
        $function = array("CacheTest", "getData");

        // get a cache object and name it something useful
        $cache =& JFactory::getCache( $group );
        $cache->setCaching( 1 ); // enable caching (1 = enabled, 0 = disabled)

        // call our function "getData" through the cache
        $result = $cache->call( $function, $url);

        // Check if we get sane data. Depending on your data this
        // could be more than just an empty check
        if ( empty( $result ) ) {

            // find the id to remove data for
            $id = $cache->_makeId( $function, $url );

            // invalidate this entry in the cache
            $cache->remove( $id, $group );

            // call our function again knowing the cache has been fixed
            $result = $cache->call( $function, $url );
        }
        return $result;
    }
}
$data = CacheTest::getDataCached( "http://example.com" );
var_dump($data); // to see what we actually get

In Closing

Joomla's caching API is simple and, by default, just writes these cache files to disk. Although not the fastest approach the default options are really handy for a less-than-stellar server setup. These caching options are available to even shared hosts. At the same time, I should mention that Joomla has done a fantastic job of integrating support for more advanced caching systems like memcache and xdebug so that the above code can be run even faster without any modifications.

Related Posts

  • Thomas Lange

    Good caching tutorial, but there is a typo in fault tolerant example code IMHO:

    $cache->store( $result, array('Utility', 'getData'), $params );
    should be
    $cache->store( $result, array('Utility', 'getData', $params ));

  • Thomas Lange

    Ok, I was too quick to write my comment… I should have tried my code first.

    I cannot get your Fault Tolerant Caching to work at all. As I see it, your cache->store is flawed and cannot work.

    The cache callback function stores output _and_ result by putting an array into cache:

    From callback.php:
    $cached = array();
    $cached['output'] = $output;
    $cached['result'] = $result;
    // Store the cache data
    $this->store(serialize($cached), $id);

    In your example, only the result is put in. Alas, it cannot work, even if we manage to generate the same $id as callback code creates.

  • http://garrettbluma.com Garrett Bluma

    Actually there is a type, but not the one you mention.

    $cache->store( $result, array('Utility', 'getData'), $params );

    should be:
    $cache->store( $result, array('CacheTest', 'getData'), $params );

    This is because the “store” function takes 3 parameters:
    * data to store
    * function name ( or if the function belongs to a class, array('ClassName', 'FunctionName'); )
    * function parameters

  • http://garrettbluma.com Garrett Bluma

    Thomas,

    I went through and tested out my code and found a few bugs. Originally I adapted this code from a real project and assumed certain things would work without testing — that was a mistake though. I fixed many of these bugs and also wrote a quick example you can peruse and/or try out.

    I hope that helps.

  • http://garrettbluma.com Garrett Bluma

    Actually there is a type, but not the one you mention.

    $cache->store( $result, array('Utility', 'getData'), $params );

    should be:
    $cache->store( $result, array('CacheTest', 'getData'), $params );

    This is because the “store” function takes 3 parameters:
    * data to store
    * function name ( or if the function belongs to a class, array('ClassName', 'FunctionName'); )
    * function parameters

  • http://garrettbluma.com Garrett Bluma

    Thomas,

    I went through and tested out my code and found a few bugs. Originally I adapted this code from a real project and assumed certain things would work without testing — that was a mistake though. I fixed many of these bugs and also wrote a quick example you can peruse and/or try out.

    I hope that helps.

  • Thomas Lange

    According to:
    http://api.joomla.org/Joomla-Framework/Cache/JC…
    store() is declared as:
    boolean store (mixed $data, string $id, [string $group = null])

    i.e. third arg is $group, not $parameters, or am I missing something?

  • Thomas Lange

    Thank you for the response. I tried the code from github, but it still does not work for me.

    1. I modified getData to return '' to force reread+store() call.
    Cached data was not updated on store().

    2. Checked return value of store() call. It returned false.

    3. Changed to this :

    $cache->store( $result, array(“CacheTest”, “getData”), “Garrett” )

    store() call now returns true, but new cache data is in /cache/Garrett/* instead of where we wanted it to be, /cache/cachetest/*

  • http://garrettbluma.com Garrett Bluma

    I updated the code to generate the correct cache_id (see $id) and remove the cache entry when it catches a failure. It also uses the $cache->call() function again instead of trying to store that data manually.

  • skid

    Hi, can I send an instance class like first parameter of the array that will receive the function call?

    Example:

    $this->_model = &$this->getModel();

    $this->_cache = & JFactory::getCache( 'mygroup' );
    $this->_cache->setCaching( 1 );

    $items = $this->_cache->call( array( $this->_model, 'getItems' ), $params );

    Im sending the instace class $this->_model but when I run this the cache doesnt work… always process the function getItems();

    Thank you

  • http://garrettbluma.com Garrett Bluma

    That's hard to say if an instance would work right. I think the function would get called initially and you would get your data, but you probably wouldn't be able to pull from the cache in a repeatable fashion.

    The issue would be due to the fact that Joomla generates an ID based on the calling function and the data passed in. If the function is part of a class instance I expect that it would use the memory address for the class which would almost never match for a cache hit. Beyond that, it would be hard to predict if the caching system would factor in any instance variables that could affect the “getItems” call.

    A better alternative would be to make the function static and pass in parameters, even if they is already a dynamic function that does the same thing. That way when the cache calls it, there is only one possible location where that data can come from. You can still store that data in a class instance too.

  • Guest

    Hello,

    I try to remove a certain page from the cache (Joomla 1.5) and do not know how to archive this. I can acces the cache: $cache =& Jfactory::getCache(‘com_content’); but how to access the article by its ID and remove it from the caceh?

    Any help would be very apreciated!

    Thanks
    Peter

  • http://garrettbluma.com Garrett Bluma

    Great question! Just off the top of my head, I recall that Joomla builds a hash of the requested parameters and then uses that as the key. In my example I create my cache object, generate an id, and use that id to remove the cached entry.

    $cache =& JFactory::getCache( $group );
    $id = $cache->_makeId( $function, $url );
    $cache->remove( $id, $group );

    For your example, I would probably trace through the code to see what parameters are being sent to $cache->_makeId() and use the same ones to produce that hash.

    Feel free to post back here if you have any questions.

  • Guest

    Thank you very much for your quick response!

    I found something in the page.php of the com_content component:

    function _makeId()
    {
    return md5(JRequest::getURI());
    }

    I did not tried it because I’m tired and go now to bed, but it should work if I create the md5 hash of the desired uri and delete then the corresponding file from the cache folder.

    I will post again if I get it work.

    Peter

  • Guest

    The page.php is not part of the com_content component but of the cache library which is located at /libraries/joomla/cache/handler/page.php.

    But I did not get it work as I hoped. So I searched on for another solution and found it on an old 1.0 page. I transfered it to Joomla 1.5 and here it is:

    1. Hack the cache.php in the directory /libraries/joomla/cache/:

    function store($data, $id, $group=null)
    {
    // Get the default group
    $group = ($group) ? $group : $this->_options['defaultgroup'];

    // hack to prevent from caching
    if(strpos($data,’‘) !== false) return false;

    // Get the storage handler and store the cached data
    $handler =& $this->_getStorage();
    if (!JError::isError($handler) && $this->_options['caching']) {
    return $handler->store($id, $group, $data);
    }
    return false;
    }

    2. Add the prevent form cache comment (‘‘) to any page you do not want to be cached.

    This works for me and I hope other can need it too.

    Regards
    Peter

  • Guest

    Ok, the comment is not visible here. But it is not important how it sounds. In the example it was “!–nocache–” which you have to put into brackets (lower than and greater than).