You are here

GIS Developing Follow Me with PHP-- Use Cache to Speed up your Web Map Server or other WebServers

Blog Terms: 
Are you managing or taking care of a Web Map Server, Web Feature Server or other Web Servers? Have you been puzzled by the high traffic and insufferable speed of your server? Have your custumers always complained that  they must wait  one one year to get the map when they sent their queries to the server?
The following solutions could help you to sovle your problems.
1. Client Cache: Sets the expired time of pages in browser in the client side
2. GZIP: Uses gzip when transfering the data to accelerate
3. PHP Cache: Uses Zend Performance Suit or APC to store the PHP scripts in the memory to speed up.
4. HTTP Query Cache: Stores the commonly used http queries, for WMS, the outtputed map image or XMl documents. (Other Cahce: Pear::Cache, Page Cache and Function Cache)
5. Database Query Cache: Store the oftenly used database query results, release the pressure of database
6. Proxy and BigIP, F5 or others.....

Ok, it is time for you to try to use Cache in your WMS, WFS or other servers.

1. Client Cache

The browser doesn't go to fetch the page from the remote server everytimes, it will cahe the Html file somewhere on the local computer so that, should the page be requested again, afer performing a quick with the server to ensure the page hadn't been updated and the browser could display the locally cached version. If not, it will connect the server again to fetch the file.
HTTP1.0 caching headers has: Expires, Last-Modified, and If-Modified- Since, as well as HTTP status code 304 (Not Modified). We could write one function to tell  the client browser which page has expired and when will it expire.

* Sends the Expires HTTP 1.0 header.
* @param int number of seconds from now when page expires
*/
function setExpires($expires)
{
header('Expires: ' .
gmdate('D, d M Y H:i:s', time() + $expires) . 'GMT');
}
// Set the Expires header
setExpires(10);


A more useful approach is to make use of the Last-Modified and If-Modified-Since headers, both of which are available in HTTP 1.0. Using this approach, you need to send a Last-Modified header every time your PHP script is accessed. The next time the browser requests the page, it sends an If-Modified-Since header containing a time; your script can then identify whether the page has been updated since the time provided. If it hasn’t, your script sends an HTTP 304 status code to indicate that the page has not been modified, and exits before sending the body of the page.

// Get the time the cache file was last modified
$lastModified = filemtime($cache->_file);

// Issue an HTTP last modified header
header('Last-Modified: ' .gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');

/ /Get client headers - Apache only

$request = getallheaders();

if (isset($request['If-Modified-Since'])) {

// Split the If-Modified-Since (Netscape < v6 gets this wrong)

$modifiedSince = explode(';', $request['If-Modified-Since']);

// Turn the client request If-Modified-Since into a timestamp

$modifiedSince = strtotime($modifiedSince[0]);

} else {

// Set modified since to 0

$modifiedSince = 0;

}

// Compare the time the content was last modified with cache

if ($lastModified <= $modifiedSince) {

// Save on some bandwidth!

header('HTTP/1.1 304 Not Modified');

exit();

}

4. HTTP Query Cache

4.1 Output Buffer

 Look at PHP’s in-built caching mechanism, the output buffer, which can be used with whatever page rendering system you prefer (templates or no templates). Consider a situation in which your script displays results using, for example, echo or print, rather than sending the data directly to the browser. In these cases, you can use PHP’s output control functions to store the data in an in-memory buffer, which your PHP script has both access to and control over.

<?php
// Start buffering the output
ob_start();
// Echo some text (which is stored in the buffer);
echo '<?xml version="1.0" encoding="utf-8"?><GetCapabilities version="1.0">';

// Get the contents of
$buffer = ob_get_contents();

// Stop buffering and clean out the buffer
ob_end_clean();

// Echo some text normally
echo 'GetCapabilities:<br />';

// Echo the contents from the buffer
echo $buffer;
?>

The buffer itself stores the output as a string. The The above script displays:
GetCapabilities:
<?xml version="1.0" encoding="utf-8"?><GetCapabilities version="1.0">


This type of cache could be used when creatint the GetCapabilities request, GetFeature in WFS or other requests.

4.2 Save Cache in file

You can also save the buffering in a file. Every file in server has its birthday : filetime.  When the php script meet  a new request, and the request has been createn as file, we should check the birthday of the cache file, if it has long life(maybe 3600 second, one hour), don't use it and create new data to user and save the data to the old one to replace it. Here is one complete class:

<?php
class cache{
/*
Class Name: cache
Des cription: control to cache data,$cache_out_time is a array to save cache date time out.
Version: 1.0
Author: cjjer
Last modify:2006-2-26
Author URL:
http://www.cjjer.com
*/

private $cache_dir;
private $expireTime=180;

function __construct($cache_dirname){
if(!@is_dir($cache_dirname)){
if(!@mkdir($cache_dirname,0777)){
$this->warn('cache does not exist!');
return false;
}
}
$this->cache_dir = $cache_dirname;
}
function __destruct(){
echo 'Cache class bye.';
}
 
//get the current URL string
function get_url() {
if (!isset($_SERVER['REQUEST_URI'])) {
$url = $_SERVER['REQUEST_URI'];
}else{
$url = $_SERVER['s cript_NAME'];
$url .= (!empty($_SERVER['QUERY_STRING'])) ? '?' . $_SERVER['QUERY_STRING'] : '';
}
 
return $url;
}
 
function warn($errorstring){
echo "<b><font color='red'>Error:<pre>".$errorstring."</pre></font></b>";
}
 
function cache_page($pageurl,$pagedata){
if(!$fso=fopen($pageurl,'w')){
$this->warns('Can not open Cache file');//trigger_error
return false;
}
if(!flock($fso,LOCK_EX)){//LOCK_NB
$this->warns('Can not lock cache file');//trigger_error
return false;
}
if(!fwrite($fso,$pagedata)){
$this->warns('Can not write cache file');//trigger_error
return false;
}
flock($fso,LOCK_UN);//release locking
fclose($fso);
return true;
}
 
function display_cache($cacheFile){
if(!file_exists($cacheFile)){
$this->warn('Can not read cache file');//trigger_error
return false;
}
echo 'Read Cache file:'.$cacheFile;
//return unserialize(file_get_contents($cacheFile));
$fso = fopen($cacheFile, 'r');
$data = fread($fso, filesize($cacheFile));
fclose($fso);
return $data;
}
 
function readData($cacheFile='default_cache.txt'){
$cacheFile = $this->cache_dir."/".$cacheFile;
if(file_exists($cacheFile)&&filemtime($cacheFile)>(time()-$this->expireTime)){//compare the cache file date and current date
$data=$this->display_cache($cacheFile);
}else{
$data="from here we can get it from mysql database,update time is <b>".date('l dS \of F Y h:i:s A')."</b>,Expired time is:".date('l dS \of F Y h:i:s A',time()+$this->expireTime)."----------";
$this->cache_page($cacheFile,$data);
}
return $data;
}
 
}
?>

This class will store the page as one cache file, we could use it to store map cache in server side with GetMap request. Using Getmap with different parameters, bbox,layers and srs you can get different area of map, and these areas of map could be stored as different cache files if users query them oftenly. md5($url) function will turn each getmap url to a unique string, the other steps are the same.

5. Database Query Cache

Step 1, Submit a SQL query. Before submitting, check if such file exists (named after that sql query) in “cache” folder.
Step 2, If is does exist, check how old the file is.
- Over X days? Load the data from mysql, and update the file.
- Under X days? Open the file, and print it so we don’t need to load the server with a new query.
Step 3, All you have to do to implement this: put your queries in a function. And create a directory “cache” that is writable (777).  Load the mysql results and write them to a file. For a webserver opening a file (the cache) is faster then having to connect to mysql, and calculating the query.

<?

function mysqlGetNewList($sql){

    
ob_start();
    
    if((
$result=mysql_query($sql))){ 
     
        while((
$rs=mysql_fetch_array($result))){
            echo 
$rs[0];
            
//your results printed
    
        

    }
    /*
    *Or you can store the SQL result as file directly without
ob_get_contents().
    *
$output = rs;
    */
    
    
$output ob_get_contents(); 
    
ob_end_flush();
    
//all "echo" and "print" are beeing send to var "$output"
    // next time we have a file ready instead of having to query mysql
        
    
$file = @fopen("cache/".$sql.".txt""w+"); 
    
//we write it to a new file named after the $sql query
    
@fputs($file$output); 


}

//we start here
$sql "select * from database order by id desc  LIMIT 0, 50";

//we try to open a file named after the sql query
$fp = @fopen("cache/".$sql.".txt","r");

if (
$fp)
    {
    
    
$filemod filemtime("cache/".$sql.".txt");
    
//date of the file
    
$filemodtime date("Ymd"$filemod);
    
//what date is it now
    
$now time();
    
$now strtotime($time);
    
$nowtime date("Ymd"$now);
    
$diverence $nowtime $filemodtime;
    
    if(
$diverence >= 1){
        
//older then 1 day? get new
        
print "<!-- non cached  $filemodtime - $nowtime = $diverence -->";
        
mysqlGetNew($sql);
    }
    else
    {
        
//get it from cache!
        
print "<!-- cached  $filemodtime - $nowtime = $diverence -->";
        
$buff fread($fp,1000000);
        print 
$buff;
    }
}
else

    
//file does not exist. so we try to create the file by starting the function
    
print "<!-- error non cached  $filemodtime - $nowtime = $diverence -->";
    
mysqlGetNew($sql);
}

?>

















Ref: Harry,  The PHP Anthology, Cache
http://www.sitepoint.com/books/phpant1/phpant1-sample.zip
http://www.21andy.com/blog/20060527/326.html
http://blog.kylemulka.com/?p=293
http://www.yvoschaap.com/index.php/weblog/use_cache_to_speed_up_webserver/