1

Cache Content to Speed Up Slow Loading Web Pages

Photograph by Joanne Markow

More and more sites contain dynamic vs static content web pages. These pages make constant database calls and run many data processing tasks. This eats up server resources and also increases your page load times. In this tutorial I will show you 3 server side content caching methods I’ve used in my web applications.

I will also show you how to use and implement Zend_Cache (part of the Zend Framework) to start caching content in your web apps.

#1 Caching All the Output

Caching page output is a great quick way to increase web page speed. The script content is generated once and cached for a duration of time, subsequent requests always get pulled from the content cache. The key benefit here is that all content going to the browser is cached and almost all server side processing is eliminated.

Using Zend_Cache to implement:

<?php 
 
require_once 'Zend/Cache.php';
 
$cache = Zend_Cache::factory
(
	'Page' // frontend caching method
	, 'File'
	, array('lifetime'=>3600)
	, array('cache_dir'=>$_SERVER['DOCUMENT_ROOT'] . '/cache')
);
 
$cache->start('uniquePageName');
 
// PAGE CONTENT
 
?>

Keep in mind that this is still a server side solution. There are added benefits to also implementing a client side caching solution using Expire and Modified-If or E-Tag headers where the browser pulls content from it’s web page cache rather than make another server request.

For the moment, it’s not implemented but we plan to add a HTTP conditional system to save bandwidth (the system will send a HTTP 304 Not Modified if the cache is hit and if the browser has already the good version).

#2 Cache Protions of the Output

This technique is rather interesting, allowing portions of a web page to remain dynamic while making other portions static through content caching. The benefit (which is speed) comes when you strike a balance between what content can be static and what remains dynamic. Ask yourself, what does “dynamic” mean to you and your users. If your content does not need to be “to-the-second” fresh, perhaps setting a lower cache expiration time (10-30 minutes) would suffice.

Using Zend_Cache to implement:

<?php 
 
require_once 'Zend/Cache.php';
 
$cache = Zend_Cache::factory
(
	'Output' // frontend caching method
	, 'File'
	, array('lifetime'=>3600)
	, array('cache_dir'=>$_SERVER['DOCUMENT_ROOT'] . '/cache')
);
 
if (!($cache->start('uniqueContentName'))) 
{
	// PAGE CONTENT
 
	$cache->end(); // output buffering ends
}
 
// DYNAMIC PAGE CONTENT
 
?>

#3 Caching Function Output

Another unique concept is the idea of caching the output of functions and methods. Reading cached function output is typically faster then hitting the database with a query. For data that doesn’t often change or data that doesn’t require constant freshness, this may be an ideal solution. I think this caching method is more attractive when dealing with complex web applications where you have interconnected components.

Using Zend_Cache to implement:

<?php 
 
function fooBar()
{
	require_once 'Zend/Cache.php';
 
	$cache = Zend_Cache::factory
	(
		'Core' // frontend caching method
		, 'File'
		, array('lifetime'=>3600,'automatic_serialization' => TRUE)
		, array('cache_dir'=>$_SERVER['DOCUMENT_ROOT'] . '/cache')
	);
 
	$cache_id = 'uniqueFunctionName';
 
	$rows = $cache->load($cache_id);
 
	if (!$rows)
	{
		$rows = array();
 
		// FUNCTION CONTENT
 
		$cache->save($rows, $cache_id);
	}
 
	return $rows;
}
 
?>

Here is a real world example from a method call I use at YomoMedia:

static function getSubscriberCounts()
{
	require_once 'Zend/Cache.php';
 
	$cache = Zend_Cache::factory
	(
		'Core'
		, 'File'
		, array
		(
			'lifetime' => 3600
			,'automatic_serialization' => TRUE
		)
		, array
		(
			'cache_dir' => SP_CACHE_LOCATION
		)
	);
 
	$cache_id = 'Bundle_getSubscriberCounts';
 
	$rows = $cache->load($cache_id);
 
	// check if rows were cached
	if (!$rows)
	{
		$rows = array();
 
		$sql =
		"
			SELECT
				b.bundle_id
				, COUNT(DISTINCT bs.user_id) AS subscriber_count
 
			FROM
				bundle_subscriptions AS bs
 
				INNER JOIN bundles AS b
					ON b.bundle_id = bs.bundle_id
 
				INNER JOIN users AS u
					ON u.user_id = bs.user_id
					AND u.role != 'temp'
 
			GROUP BY
				b.bundle_id
		";
 
		$result = @mysql_query($sql);
 
		if ($result)
		{
			while($row = @mysql_fetch_assoc($result))
			{
				// use id as key to be able to do faster lookups
				$rows[$row['bundle_id']] = $row['subscriber_count'];
			}
 
			@mysql_free_result($result);
 
			// generate a table to provide other methods and queries a JOIN
 
			$tb = 'gen_bundle_subscriber_counts';
 
			@mysql_query("DROP TABLE IF EXISTS `$tb`");
 
			$sql_create =
			"
				CREATE TABLE `$tb`
				(
					`bundle_id` int(11) unsigned NOT NULL DEFAULT '0'
					, `subscriber_count` int(10) NOT NULL DEFAULT '0'
					, PRIMARY KEY (`bundle_id`)
				) TYPE=MyISAM;
			";
 
			@mysql_query($sql_create);
 
			$sql = 'INSERT INTO `' . $tb . '` ' . $sql;
 
			@mysql_query($sql);
 
			$cache->save($rows, $cache_id);
		}
	}
 
	return $rows;
}

Tell me if you’ve used any of these content caching methods before and how it’s worked for you?

Related posts

  1. Integrate Twitter Into Your Web Site Using PHP

{ 1 comment… read it below or add one }

Montpellier April 6, 2010 at 5:35 am

Very useful

Thank you

Leave a Comment

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
<pre lang="" line="" escaped="">

Previous post:

Next post: