HomePHP Page 4 - Caching Result Sets in PHP: Cost-efficient PHP acceleration
Putting the pieces together: implementing the result set caching script - PHP
With many websites using a database backend for storing and delivering content, certain common problems arise. One of these is a situation that causes the server to slow down or even bring the system to a complete halt. What can you do to reduce the load on your server? This tutorial discusses one method, a result set caching system, and demonstrates how to implement it with either a procedural or an object-oriented approach.
Undoubtedly, there is much to be gained by implementing a caching system like this. If a website delivers content that doesn't change very frequently, let's say by offering a list of articles or products, we might be able to cache the data in a file and force a new cache generation based on a time expiry caching strategy.
In this case, the script follows that approach, establishing a time expire value in seconds for the validity of the cache file. The first occurrence of the script connects to MySQL and obtains the corresponding result set, which after being serialized, is cached to the file. As a result, all of the subsequent page requests will display the data retrieved from the cache, until the time expiry will be reached.
The above logic is better understood by looking at the code listed below:
// define cache file $cacheFile='cacheFile.txt'; // define expire time in seconds (2 hours) $expireTime=7200; // check to see if cache file is valid (time triggered caching) if(file_exists($cacheFile)&&filemtime($cacheFile)>(time()- $expireTime)){ // read data from cache file $data=readCache($cacheFile); } else{ // read data from MySQL connectMySQL('host','user','password','databasename'); $result=query('SELECT * FROM users'); // store result set in array while($row=mysql_fetch_array($result,MYSQL_ASSOC)){ $data[]=$row; } // store serialized data in cache file writeCache($data,$cacheFile); } // display data foreach($data as $key=>$row){ echo 'First Name :'.$row['firstname'].' Last Name :'.$row['lastname'].'<br />'; }
As you can see, the above script first defines to the cache file where to store the result set, in this case "cacheFile.txt". Then, it specifies the time period, expressed in seconds, for considering the cached data valid. For this particular example, I've specified a cache validity of two hours (7,200 seconds). These values are defined in the following lines:
// define cache file $cacheFile='cacheFile.txt'; // define expire time in seconds (2 hours) $expireTime=7200;
As long as the time expiry is not reached, the result set is obtained from the cache. Otherwise, a new connection to MySQL is established, performing a query and returning a new result set, which results in another cache generation. This process is carried out by the lines:
// check to see if cache file is valid (time triggered caching) if(file_exists($cacheFile)&&filemtime($cacheFile)>(time()- $expireTime)){ // read data from cache file $data=readCache($cacheFile); } else{ // read data from MySQL connectMySQL('host','user','password','databasename'); $result=query('SELECT * FROM users'); // store result set in array while($row=mysql_fetch_array($result,MYSQL_ASSOC)){ $data[]=$row; } // store serialized data in cache file writeCache($data,$cacheFile); }
Finally, the script is capable of directly caching results sets without major inconveniencies. Eventually, we might introduce another improvement by wrapping the above code into a function. Doing so, the overall code would hide all of the internal processing, being even more flexible and portable.
Thus, this new wrapping function, which I've denominated "readData()" (or anything you like), is defined as follows:
function readData($options=array),$expireTime=7200,$cacheFile='default_cache.txt') { // check parameters if(count($options)<5){ trigger_error('Invalid number of parameters'); exit(); } // create connection variables foreach($options as $key=>$value){ ${$key}=$value; } // check if query starts with SELECT if(!preg_match("/^SELECT/",$query)){ trigger_error('Invalid query. Must start with SELECT'); exit(); } // check to see if cache file is valid (time triggered caching) if(file_exists($cacheFile)&&filemtime($cacheFile)>(time()- $expireTime)){ // read data from cache file $data=readCache($cacheFile); } else{ // read data from MySQL connectMySQL($host,$user,$password,$database); $result=query($query); // store result set in array while($row=mysql_fetch_array($result,MYSQL_ASSOC)){ $data[]=$row; } // store serialized data in cache file writeCache($data,$cacheFile); } return $data; }
Now, suppose that we want to cache a result set for 24 hours, and store it in the "cache_file.txt" cache file. We may invoke the function with these parameters and then display the data, like this:
// read data from MySQL or cache file $options=array ('host'=>'host','user'=>'user','password'=> 'password','database'=>' databasename','query'=>'SELECT * FROM users'); $data=readData($options,86400,'cache_file.txt'); // display data foreach($data as $key=>$row){ echo 'First Name :'.$row['firstname'].' Last Name :'.$row ['lastname'].'<br />'; }
Definitiely the code is much more compact and readable. However, as you can see, it maintains the benefits inherent to caching result sets. Notice that the function returns the data either from MySQL or the cache file. In this circumstance, I've chosen to display the result, but it might have been post-processed in some other way before sending it to the browser. As mentioned before, this caching method is more flexible than caching the entire HTML output.
To wrap up
In this first part of the series, I've demonstrated that building a result set caching system is a rather easy process. For developers working with a procedural approach, this solution can be used as a base structure for further improvements. However, there is still a long road in front of us.
In the second article, I'll implement a result set caching script based on a content-change caching trigger, exploring yet another nice possibility offered by this technique. So, get ready for next part!