Feed Sign in with OpenID OpenID

Simon Willison’s Weblog

Instant caching with PHP

I’ve been getting more database timeout errors today, probably due to increased traffic from links to recent entries. A permanent solution would be to switch to generating static HTML (this site is currently dynamically generated for every request), but I don’t have time for a full re-write at the moment. Luckily, PHP offers an extremely simple solution to dynamic caching in the form of output buffering. The front page of the site (which generates by far the most traffic) is now served from a cached copy if it has been cached within the last 5 minutes. Best of all, I didn’t have to edit any of my core application logic—I just dropped in some extra code at the top and bottom of the index.php file. Here’s how it works:

<?php
$cachefile = 'cache/index-cached.html';
$cachetime = 5 * 60;
// Serve from the cache if it is younger than $cachetime
if (file_exists($cachefile) && time() - $cachetime < filemtime($cachefile)) {
    include($cachefile);
    echo "<!-- Cached copy, generated ".date('H:i', filemtime($cachefile))." -->\n";
    exit;
}
ob_start(); // Start the output buffer

/* The code to dynamically generate the page goes here */

// Cache the output to a file
$fp = fopen($cachefile, 'w');
fwrite($fp, ob_get_contents());
fclose($fp);
ob_end_flush(); // Send the output to the browser
?>

I’ve added a few extra bits of logic to check that the page being cached isn’t itself a database error page, but the above is the bulk of the code. Best of all, due to the nature of output buffering it should work on practically any PHP application.

This is Instant caching with PHP by Simon Willison, posted on 5th May 2003.

View blog reactions

Next: Living on a knife edge

Previous: Better structural markup rants

11 comments

  1. there's a bit of a race condition in there where another process could end up serving an empty or partial cached page (by sneaking in between the fopen() and all of the contents being written to disk). it would be a little safer to write to a temporary file and rename that into place. and without any locking, you could end up with multiple processes all regenerating the cached file. but that may be cheaper to live with than the locking. (you would want to use something process-unique to identify the temp file above, though, or you could get the same sort of wacky race conditions.) and you might want to use readfile() to send the cached page, rather than include().

    jim winstead - 6th May 2003 00:31 - #

  2. Different idea: Whenever you add a post, update your blogroll, or add a comment to the site, touch a file. When a request for the index page comes in, compare the timestamp on the file to the timestamp on the cached copy. If you ever update the code on your index page, just delete the timestamp file.

    You can also go on and do caching properly. I've a blog that's been sitting half-written on my hard drive for months that already handled all sorts of caching situations (last-modified, etags, conditional gets, pipelining, etc). It's not hard, but you need to pay attention when testing, and test very thoroughly.

    Jim - 6th May 2003 01:50 - #

  3. Eventually I'm going to sit down and write something better, but what I've been doing is just having lynx --source run on a cron job every 2 minutes and dump index.php -> index.html for my front page. Things such as the blogroll change commonly enough that I wouldn't benefit much from doing some sort of elaborate scheme that figured out what has changed, etc., and the front page is as fast as a perfectly static file from Apache. I also have a link to index.php in my header so if anyone really wants to see the latest and greatest, they can. Actually, it's more for me.

    For WordPress I've been considering something akin to a funky caching system that generates static files and uses a method similar to Jim's except instead of touching a file it touches a field in the database. However Smarty caching is looking smooth enough that this might not even be an issue. It looks like I'll be able to take the best of baked and fried (was it fried?) without messing with writing to the filesystem.

    Matt - 6th May 2003 02:53 - #

  4. Jim, that's a nice idea which would certainly work for caching the entries here, but Simon has the updating blogroll!

    Swannie - 6th May 2003 19:43 - #

  5. I totally agree with Matt's comment. I can't think of a better solution to caching. The only thing I would suggest is to make most part of the page static (graphics and permanent text) and to have smaller part of the page regenerated (f.ex. using SSI). Andy

    Andy - 2nd June 2004 16:24 - #

  6. jkljk

    klkjljk - 13th September 2005 21:10 - #

  7. I built a CMS for my website using php and used the above example as a base for the logic to create a static version of my site. Instead of caching a new page every five minutes I only create new cached pages whenever the time stamp has changed on an include file for that page, which is it's main content block. Even though I modified the code quite a bit I still wanted to thank-you for providing me with the logic that got me thinking about it in the first place.

    Not Given - 3rd February 2006 22:11 - #

  8. sdas

    asd - 25th April 2006 14:09 - #

  9. Simon, Excellent article. I really like how you cut to the chase by giving a bare bone, yet fully functional caching example. It's very refresing when an author of a techincal article gives me the useful information upfront without me having to wade through many useless lines of filler code that serve no real useful purpose. I would like to implement your caching scheme with pagnation. Any ideas of how to do that would be greatly appreciated. Thanks Kyle

    Kyle Lougnot - 27th May 2006 06:40 - #

  10. Thanks so much Simon! This was just what I needed, my server went from slow and overloaded to blazing fast with just 20 minutes of work. Fantastic!

    Marcel - 14th August 2006 21:59 - #

  11. Thanks Simon !

    3 years later and your post is still of great help for many of us... especially usefull for fetching RSS Feeds...

    Thank you

    Tim - 22nd October 2006 23:17 - #

Comments are closed.

Previously hosted at http://simon.incutio.com/archive/2003/05/05/cachingWithPHP

A django site