ThingyMaJig

Thingy Ma Jig is the blog of Nicholas Thompson and contains any useful tips, sites and general blog-stuff which are considered interesting or handy!

Connect

LinkedIn GitHub

Topics

announcement 25 apache 3 Apple 1 bash 8 code 7 cool 30 Days Out 8 Dark Basic Pro 4 design 12 doctor who 1 Drupal 74 E4600 1 EOS 400D 3 firefox 2 Flickr 3 free 21 games 5 geek 38 git 2 GreaseMonkey 1 hardware 7 Homebrew 1 How to 37 humour 5 iphone 1 javascript 1 jquery 1 K800i 6 k850i 4 lighttpd 3 linux 33 mac 9 miscellaneous 4 mobile phone 9 music 4 mysql 8 n73 1 n95 1 New Relic 1 Ogre3D 1 OS X 2 performance 3 photos 10 programming 40 Quicksilver 1 review 19 security 3 SEO 6 software 12 svn 2 technology 4 tip 7 tips 10 tv 3 video 3 vim 7 webdev 2 websites 33 wii 1 windows 1 YADS 10

XCache Variables, Drupal Page Cache and page_cache_fastpath()

Posted on 24 February 2010 in
tips programming performance How to Drupal

I run XCache on the server that powers this site. XCache is cool. Out of the box, it allows caching of PHP compile code in memory, after all once a file is compile you shouldn't keep needing to compile it on every page load, should you?

There is another feature XCache has which not many people know about or use; Variable Caching. When you configure your XCache (the ini file is usually found in /etc/php.d/xcache.ini), you should see some options in there for allocating memory for variables as well as code (see var_size). This is essentially a persistent place to put stuff which can be called back out on the next page load.

Another handy thing which I'd never seen before (and I've been using Drupal for around 4 years), is the page_cache_fastpath() function. This is not implemented by default, but if you take a look in _drupal_bootstrap, there is a stage called DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE. I'd never noticed this before.

In both Drupal 5 and Drupal 6, this stage will check if the page_cache_fastpath variable is set to TRUE and, if so, will call a the page_cache_fastpath() function. This function can then echo out a page and return TRUE which causes Drupal to exit there and then. This all happens BEFORE Drupal connects to the database.

So I set myself a little challenge… and then Googled for it to find that it's pretty much been done before, although it seemed to require Memcache. I also found that CacheRouter could do this too but thought it was a little excessive for me needs, plus I still wanted a challenge!

It turns out to be pretty easy.

  1. Copy includes/cache.inc into your sites folder.
  2. In your settings.php add the following:
    $conf = array(
      'cache_inc' => './sites/example.com/cache.inc',
    );
    
  3. Your site should now be using your "custom" cache.inc file. Now you need to tweak it.
  4. I decided to define a prefix in the header - this is because there is only one XCache Variables place to put "stuff". The prefix can be used to fix content to a specific site. Add the following at the top of the file:
    define('XCACHE_PREFIX', 'mysite_');
    
  5. Next, you want it to insert this code at the end of cache_set:
        if ($table == 'cache_page') {
        cache_get($cid, $table);
      }
    

    This will, for cache_page entries only, insert the data and headers into XCache as an array.

  6. The only thing we need to add to cache_get is some code to re-insert data into XCache if we clear XCache but not the cache_page table (Eg, restarting Apache or Lighttpd). Add the following code just before the return $cache part:
      if ($table == 'cache_page') {
        // If we're here and on cache_page, looks like the xcache wasn't present... lets chuck it in there for next time.
        $xcache_expire = $cache->expire > 0 ? $cache->expire - time() : NULL;
        $ret = xcache_set(XCACHE_PREFIX . $table . ':'. $cid, array('data' => $cache->data, 'headers' => $cache->headers), $cache->expire);
      }
    
  7. Almost there now... We need to control cache_clear_all. For this to work, you need to make sure you're running the very latest 1.3.0 version of XCache. There is a known bug where some releases don't contain the xcache_unset_by_prefix function (for some odd reason). Under the cache_clear_all(NULL, 'cache_page'); line near the beginning of cache_clear_all, add the following:
    xcache_unset_by_prefix(XCACHE_PREFIX .'cache_page');