Upcoming SuperCache-Plus feature. Maybe.

We're on a highway to hell

I've been pondering the mysteries of the next version of my SuperCache-Plus WordPress plugin which may include some useful extra functionality. The dilemma is that compared to the existing functionality, it's relatively complicated to use and thus it's relatively easy to make a big mess with it. Thus, it has come to pass that I've written this article about it - to test the level of interest versus the risk of having to deal with a barrage of “Ur shitty plugin broke my blog”.

The basic idea is that instead of just caching each full page, you also cache the bits that fit together to make the page. I'm not talking about query caching or object-caching that already exists in WordPress; I'm talking about the final rendered components of the page like the post and comment text.

None of this is particularly new or exciting - there are loads of sites out there doing it - but I'm unaware of any other attempts to do this with WordPress in a way that's accessible to anyone prepared to devote a bit of brain-power to the process.

To set the scene, let's confine our attention to just two things - the text of the posts and comments. Say we have a post that has 100 comments. What currently happens with SuperCache-Plus when a new comment gets added? Simple - The cached pages with that post_ID get removed from the cache along with any cached pages that have no post_ID. The next request for that page (which is most likely the redirect for whoever posted the comment) must rebuild the page from scratch. The next anonymous request will do the same thing. All the plugins that filter and format your comments will be run to regenerate content that hasn't even changed.

Imagine now a better world where adding that comment only requires that single comment to be rendered and the page gets built from 100 cached comments plus the new one. That's a lot less work and based on my observations so far will never (unlike some other caching strategies) merit a warning that it may make your site slower.

As another example; Consider the archive page /2009/01/. At the time of writing, what appears on that page will be very similar to what's on the page /2009/ and probably the ‘Home’ page too. If the rendered blocks are cached for the first one, then generation of the others that contain those blocks will take less time and effort. Similarly, if some of those are in the category ‘News’ then they can be reused to render the ‘News’ category page. The same applies to users. Currently SuperCache[-Plus] caches a copy of each page for each cookied user and one for anonymous users - With this the work done rendering a page for one user can be reused for others.

Here's what my comment loop in comments.php looks like...

<?php foreach ($comments as $comment) : ?>
<?php
$context = 'complete'.(current_user_can( 'edit_page', $id ) || current_user_can( 'edit_post', $id ));
if(wpscp_cache_begin('comment', $comment->comment_ID, $context)) {
?>
    blah blah blah...
 
<?php wpscp_comment_text(); ?>
 
    blah blah blah...
 
<?php wpscp_cache_end('comment', $comment->comment_ID, $context); } ?>
<?php endforeach; ?>

All that we've done is used wpscp_comment_text() instead of comment_text() and enclosed the comment block inside a wpscp_cache_begin / wpscp_cache_end structure. One line changed, three lines added and you've got comment caching that's linked to the SuperCache-Plus intelligent purging. There's a few lines of code to go in the theme's functions.php so that nothing breaks if SuperCache-Plus isn't available and that's about it really.

The above example uses nested caching. The inner text of the comment is always the same (on this site) so using wpscp_comment_text() is fine for that. Each comment block however has a ‘moderate’ link for admin users but not for other users so it needs to be cached with a context. In the example the comment block is cached in two versions, each being built using the inner cached text.

Note that the wpscp_comment_text() function can also take a ‘context’ argument plus all the ones that the standard comment_text() function does and that while the example shows nesting one level deep, there is no technical barrier to nesting deeper than this nor is there any requirement to nest anything at all - the example merely shows the the best way to do it for this site and theme.

Also; wpscp_comment_text() is just shorthand for...

if(wpscp_cache_begin('comment', $comment->comment_ID)) {
 
    comment_text();
 
    wpscp_cache_end('comment', $comment->comment_ID); 
}

...there's nothing special about it.

That example was for an old style (pre 2.7) comment loop - what about the newer style that uses wp_list_comments()? Easy; SuperCache-Plus will provide a custom comment walker that should do the trick. I say ‘should’ since I haven't actually done it yet, but that's a minor detail :\

The post loops are treated in a similar way. Here's the relevant bit of my index.php...

<?php while (have_posts()) : the_post(); ?>
<?php
$context = 'indextopcomplete'.(current_user_can( 'edit_page', $id ) || current_user_can( 'edit_post', $id ));
if(wpscp_cache_begin('post', $id, $context)) {
?>
    blah blah blah...
 
<?php wpscp_the_content(); ?>
 
	blah blah blah...
 
<?php
	wpscp_cache_end('post', $id, $context);
} ?>
<?php endwhile; ?>

Unfortunately, doing single.php is a tricky one...

<?php
$context = "single{$page}complete".current_user_can( 'edit_post', $id );
if(wpscp_cache_begin('post', $id, $context)) {
?>
    blah blah blah...
 
<?php wpscp_the_content("single$page"); ?>
 
	blah blah blah...
 
<?php
	wpscp_cache_end('post', $id, $context);
} ?>

Scratch that; it was easy after all. We just had to use the page number as part of the context.

The contexts are the reason why this can't all be done automatically. Maybe your posts have adverts injected by a plugin for anonymous users or some content only appears to logged-in users or admins. You know your site and the different ways the content can appear. A context is required for each server-side ‘version’. My index.php example is simple and has a context indextopcomplete1 for admins and indextopcomplete for everyone else.

The great thing about doing this is that the more complicated the site is with respect to preprocessing (plugins and theme code) of posts and comments, the bigger the benefit.

The good news is that it's already running on this site. The eAccelerator implementation seems to be holding up well and the benefits are very noticeable. The purging system is functional but incomplete. so all that remains to do is finish that and then do it all again for XCache, APC and Memcached(x2) - I'm thinking of dropping the Zend-Cache backend since I doubt it ever gets used outside of apps built on the framework. ETA is likely to be a few weeks from now.

Now for the bad news. I doubt very much that I will be implementing this with Disk storage in mind. It's doable, but I don't think it's wise or particularly desirable - there can be hundreds of cached fragments making up a page.

But what, I hear you cry, does having thousands of things in the cache do to performance? Not much it seems - With 1000 cached items in eAccelerator, the time to get the list, go through it fetching every item (5MB total) from the cache and then deleting them is less than 50 milliseconds on my dog slow test server. That's much more work than would occur when some content is changed and the cache needs to be selectively purged. Similarly, I've tested the time to get a cached key from eAccelerator with 50,000 keys cached compared with when only one key is cached. The difference is a few microseconds.

In terms of time and effort. It took me about 10 minutes to modify my theme (including thinking through the required contexts).

I'd welcome feedback and opinions on whether this is worth releasing. It's getting done whether you like it or not, but do you think it sounds like something you would use? Does it seem too complicated? Have I missed something that really needs consideration? What if...?

 

The End is Just the Beginning

  • Reddit
Back to top ↑

Activity

RSS
10 total comments, leave your comment or trackback.
  1. ovidiu

    Jan 27th 2009
    #

    <blockquote.The End is Just the Beginning haha – nice finishing touch.

    I’d be prepared to give it a try although it practically excludes wpmu as there is no way I am going to edit 100s of themes, and do so every time some of them get updated :-(


  2. Murmatron Server Admin
    Admin Avatar
    Jan 27th 2009
    #

    WPMU is a problem. I’ve been trying to find a way to do this without having to mess with the themes but unfortunately I don’t believe there is one.

    I looked at hooking the loop_start and loop_end actions for posts and comments and do it all automatically but while it would probably be ok for some themes, it would break horribly for others – the one on this site is a good example since home.php and index.php each have two post loops.

    Add that to the fact that there’s just no way to guess or assume how many different ways that content can appear after plugins and theme code have done their work and you could end up with an automatically cached disaster area.

    I suppose I could call it ‘WP-SiteRandomizer’ ;)

    At the end of the day, this functionality is aimed at squeezing the maximum performance out of a single site after everything that can be done automatically has already been done. It requires precise knowledge of how the site works and a text-editor.


  3. Angsuman Chakraborty

    Mar 1st 2009
    #

    You should do some live load simulation testing. There is a cost of loading multiple files. At some point disk IO will prove too be the bottleneck. How soon depends upon changing page fragments in the page.


  4. dennyhalim.com

    May 22nd 2009
    #

    yes. wpmu please…
    if not in short term,
    perhaps a long term plan…

    tia


  5. Andy Beard

    Jun 7th 2009
    #

    A few thoughts

    1. You are worrying too much about theme modifications – this is the age of theme frameworks, and a lot of bases can be covered fairly quickly and lots of demanding users use frameworks either free of paid
    2. I am looking to optimize sites that get absolutely hammered, and often fail on small clusters (though I am sure the tech people might be partially to blame)
    3. Caching parts of content – some people like to use PHP based split testing

    Going to hit the SVN


  6. Andy Beard

    Jul 7th 2009
    #

    Would there be any advantage in using both eaccelerator and memcached in parallel?

    That way you could have eaccelerator handling fragments and op-code, and memcached pages being served directly by Nginx on the front end.

    If a site is then under very heavy load, the memcached version could always be served until a newer version is generated.


  7. Murmatron Server Admin
    Admin Avatar
    Jul 7th 2009
    #

    Andy,

    Interesting. The idea of separate storage for pages and fragments hadn’t occurred to me.

    The idea of having some metric for a ‘normal’ page generation time and serving the cached page if things are going badly had occurred to me in the form of a dynamic TTL for the pages. What you suggest seems to require having no TTL on the cached pages – this would be fine for memcached, but the other SHM caches don’t overwrite when full in the same way so would need additional logic to expire the pages. On second thoughts, that’s not necessarily a bad thing.

    I’ve already implemented ‘background’ regeneration of fragments based on request rate, but I’m not wholly convinced that in its current form it produces the profound effect I thought it would.

    Anyway, you’ve sent me into a spin of ideas at a time when my head is so full of other stuff that I can just about remember how the plugin works. :)


  8. Andy Beard

    Jul 7th 2009
    #

    As far as I am aware, the only “in memory” page caching plugins for WordPress other than yours require memcached anyway. Other accelerator plugins are only op-code.

    Thinking about it a little more, you would need a batcache arrangement and only create page caches of public pages.
    Nginx would only serve memcached pages if they exist.


  9. Andy Beard

    Jul 12th 2009
    #

    Running bleeding edge / 2.8.1 / apache/nginx/eaccelerator/memcached
    Switched to memcached as you now have fragments supported, but hit a strange bug when updating a post – 500 server error (though the data was saved to DB)
    Seemed to be working fine otherwise thus might not be my server config.

    It appears to be a clash between Ryan’s memcached object cache, and Supercache Plus running memcached.

    If I disable Ryan’s, I can use SCP with memcached, or if using SPC with eaccelerator, I can use Ryan’s object cache.

    Note: I am using the latest 2.0 of Ryan’s, and this only seems to be a problem when saving/updating a post so far.


  10. Murmatron Server Admin
    Admin Avatar
    Jul 12th 2009
    #

    Andy,

    I tried, but couldn’t replicate that.

    I did notice a bit of weirdness in the memcache object-cache.php in and around the parsing of $memcached_servers. It doesn’t necessarily work the way I’d initially read it to work and I’m not convinced it works the way Ryan intended it to either. However; that was throwing a php fatal for me when triggered, not a server error. Consequently; my assertion in the README that my plugin can use the object-cache setting is patently false and for the moment it needs to be configured using $wpscp_memcache_servers to avoid problems.

    I ended up using

    $wpscp_memcache_servers = array('192.168.0.200:11211');
    $memcached_servers = array('default' => array('192.168.0.200:11211'));

    To summarise – Nothing I did produced a 500 server error, and both caches seemed to get along just fine once I’d got the configuration right.

    I also noticed that the object-cache doesn’t deal with multiple servers ( I run my dev server on the same box on port 81 and things got very messy due to the lack of host name in the cache keys. My plugin works fine with multiple servers.

    I’m not sure there’s much I can do to track down the source of your server error since mine won’t do the same thing. Sorry :(


Leave a Reply

Comment

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <br> <cite> <code> <del datetime=""> <em> <i> <li> <ol> <p> <q cite=""> <strike> <strong> <tt> <ul> <pre lang="" line="">

Top of comments ↑

Search

There is much more rubbish here. Feel free to fritter upon the handy box supplied below.