cache

Block caching with pagers

Drupal 6 and future core versions come with block caching. There are several constants you can set to determine how it is cached from none to per page, per role etc which then can hugely improve the performance on your site for both anonymous and authenticated users. But the cache also needs to be rebuild when there has been a total wipe of the page and block cache. Per page caching is an interesting one, but in some use cases leads to a lot of entries which you sometimes don't need.

An example: with faceted search (either with Apache Solr or with the Faceted search module), you can put a block in a sidebar giving you a list of facets to browse through results. Setting caching per page makes sense because the block contents will change depending on the facet you have clicked on. However, what with pagers ? The block will always have the same facet list, but will have a cache entry for each block because request_uri() will be different. We can avoid that one by having our own logic in a custom block.

This implementation exposes a new block and we deliberately set the cache to BLOCK_NO_CACHE because we will do our own logic in the function that will return the content of the block.

<?php
/**
* Implementation of hook_block().
*/
function test_block($op = 'list', $delta = 0, $edit = array()) {
  if (
$op == 'list') {
    return array(
     
0 => array(
       
'info' => 'Block cache per page with pager',
       
'cache' => BLOCK_NO_CACHE,
      ),
    );
  }
  elseif (
$op == 'view' && $delta == 0) {
    return
test_block_view();
  }
}
?>

The function that returns the content will use request_uri() and parse_url() to get the path that's requested and we will use that path as our own cache_id. We simply ignore all queries that might be available in the URI because we know that the block may have the same content for every page, despite any queries. This is a simple example, but you can add more logic depending on your use case of course. We store our entries in the cache_block table because that table is flushed enough so we don't have stale data on our pages.

<?php
/**
* Block callback.
*/
function test_block_view() {
 
$block = array();

 

// Get the URI without any query parameter.
 
$uri = parse_url(request_uri());
 
$cache_id = $uri['path'];

 

// Do we have something in cache ?
 
if ($cache = cache_get($cache_id, 'cache_block')) {
   
$block = $cache->data;
  }
 
// Otherwhise rebuild and cache it.
 
else {
   
$views_data = module_invoke('views', 'block', 'view', 'frontpage-block_1');
   
$block['subject'] = 'Frontpage';
   
$block['content'] = $views_data['content'];
   
cache_set($cache_id, $block, 'cache_block');
  }

  return

$block;
}
?>

Hack Drupal core!

No better title to get everyone's attention - well, any Drupal developer out there who once pledged never to touch any code of core when running sites in production. I used to be one of them, but I stepped over to the Dark side recently.

For a fairly large community site (think OG, node access control, private messages etc), we were getting in trouble giving authenticated users a decent browsing experience. The worst page was taking 16 (yes, sixteen) seconds to load and others were getting in trouble too now that the number of users and nodes was growing rapidly. Not acceptable of course, so we went searching for solutions:

  • Advcache: the advcache module comes with a few patches but we only applied the one that caches URL aliases per page. While drupal_lookup_path is generally fast, it's sometimes scary to see it appear 300 times in the query log for one page. With cache_get('cache_path') you suddenly feel much better.
  • Memcache: it's possible to swap the default caching implementation, so this seemed like a good solution. It took me a few clicks to realize that it actually makes loading pages extremely faster and seeing another 100 queries less, no further discussion. Since this is the first time we're using it, we opted to use memcache.db.inc which still writes the cache to the database so in case memcache dies we still have a fallback.
  • Block cache alter: I'm biased since I actually maintain the module and I don't feel any pain hacking block module :)
  • Rewriting one query generated by views that took around 11 seconds to give back its results. We analyzed what data we wanted and wrote our own block implementation. I'm not critising views here, in fact, we still have a lot of views pages and blocks in the site which run perfectly fine. It's up to a developer to take a good look at the mysql logs and see which ones are getting in trouble and fix them if needed.

After every step, done in the same order listed above, we recorded the numbers that Yslow gave us which you can see in this picture. I still regret not logging the number of queries that dropped after every step; some pages which sometimes needed 600 queries now only need 60-70 db hits to generate the page for an authenticated user. The conclusion is that our server now has less load, pages are generated faster so we have a happy user now - and hacker too in my case.

I hope I don't have to apply patches anymore though in D7, so I'll start following http://drupal.org/node/367257 (and swappable path.inc and handlers) even more closely than I did right now - we all should!

Topics 

drupal, cache, memcache, planet

Block cache alter just started rocking!

A few months ago I released block cache alter which did nothing more than adding a fieldset on the block configuration page enabling you to change the cache setting of a block. A small first step making block cache rock a bit more in Drupal 6. I wasn't sure how to take it a step further, but I'm happy I'm not alone on this case.

With a tremendous patch submitted by batbug2 (see #354080), the module got much smarter. You are now able to set expire values for each block or have it refreshed on certain actions (nodeapi, comment and user). In fact, a lot of the patch by batbug came from the excellent Blockcache module which never got ported to D6. After some thinking I decided to add the patch into the module because in the end, they really have some differences:

  • You need to apply a patch to the block module if you want to have all possibilities. 2 patches are available, take a look at the documentation that comes with the download which one suits you best. Note: you can still simply run without a patch, you simply won't have that much control when the cache is cleared.
  • If you choose to refresh on say a node action (insert, update, delete), you can also specify which node type.
  • 206 passes, 0 fails, and 0 exceptions. That's right, it's fully tested, if that ain't nice ..

If you're interested, please help out testing. The development release is available at http://drupal.org/node/395422. The sooner I get feedback - good or bad, post comments and follow up patches at #354080 - the faster I can release a new version and start writing patches to fix block caching for good in D7!

Enabling boost on a production site

This is not going to be a tutorial on how to install and configure boost on a Drupal installation. No, I'm here to tell you a funny story and to remind myself a frustrating debugging session we had this friday trying to install the module. For those who don't know boost yet: when enabled, the module will create html files for every page on your site and with a little help of some htaccess rules, you can bypass a complete Drupal bootstrap for anonymous users. So far, so good. We really needed to speed up a project, so we told a client to test this on an acceptance version of their site and after the green light, we enabled it too on the production site. Few minutes later, the phone rang, nobody was able to edit the site anymore, boost was serving cached pages to authenticated users, panic all over the place. It took us a few hours to realize we were so stupid not seeing a very obvious and logical boost behavior, so for myself and every one else, remember this:

  • A cookie called DRUPAL_UID is set or deleted whenever a user is authenticating or logging out from the site.
  • If this cookie ain't found, the access rule will try to match a cached version of the incoming page request.
  • After enabling boost and turning on static page caching on the performance page, there is no check to see if current users are authenticated, so DRUPAL_UID does not exist for their session.
  • Therefore, the htaccess rules will serve up any existing cached pages to everyone, authenticated or not.

This is all very logical, but on a friday morning and with a fairly upset client, trying to find a fast solution for a very strange problem can be mindboggling. After skimming the access and the boost code, we finally understood what was happening so we simply truncated the session table and told the client they should go to the login page and everything would be solved. You can even tell your client to go to /logout and /user.

So if you ever experience this too: Don't panic, truncate session table, sit back and relax :)

Topics 

drupal, cache, boost, planet

Performance: Block Cache Alter

I just submitted Block Cache Alter for Drupal 6 which lets you alter cache settings per block on the configuration screen. Cache settings per block are now set in code, but if you don't like the default - usually none, you can now easily change this. Install this to speed up block rendering for authenticated users.

There are also two patches in the D7 queue now that might interest you:

  • Currently block caching support in views 2 is disabled, probably until 235673 gets in and backported.
  • I submitted this module as a patch (imagine how small it is) as a feature request for D7, review it on #347350.

Off-topic, I'm still looking for another maintainer for PicLens. File an issue and I'd be happy to move it over.

Topics 

drupal, cache, planet, blocks

Pages

Subscribe to RSS - cache

You are here