I recently started on a new site, php-development-guide.com, and I decided to use wordpress (though i altered it to act as an article cms)… and as usual found that my shared host has terrible database performance.
Well, I found the best way to curb this issue is to actually not do any database calls whatsoever. The issue with plugins like w3 total cache and others is that they exists at the plugin level, so database connections and calls are made even with their caching attempts. However, if you put your caching code at the beginning of all code execution, you can get around that.
For wordpress, all pages are loaded via the main index.php (NOT the template file!). The main index.php is contained at the root of your wordpress install. For me, the index.php file only contained these lines:
[php]
// comments
define(‘WP_USE_THEMES’, true);
/** Loads the WordPress Environment and Template */
require(‘./wp-blog-header.php’);
[/php]
Here is the caching code I came up with:
[php]
<?php // comments from wordpress... // since this is before any wordpress debugging // if you want error reporting for tests, uncomment this line //error_reporting(E_ALL); // Grab page url to make a cache file name $getPageName=str_replace('/','_',$_SERVER['REQUEST_URI']); // use whitelist regex for extra security, otherwise you can do things like directory traversal (aka, .../) $getPageName=str_replace('_.','.',preg_replace('/[^ 0-9A-Za-z\_]/', '',$getPageName).'.txt'); // If requesting home page there will be no file name, so set one if($getPageName=='.txt') { $getPageName='homepage.txt'; } // Directory where you cache files will be stored. Directory should have write permissions for everyone $cacheDirectory='cache/'; // Set expire time, text is based on what strtotime() php function supports $expireTime='5 days'; $cacheFileExists = FALSE; // Check if cache file already exists // if so, get last modified date and compare it to current date (to see if it's expired) if(file_exists($cacheDirectory.$getPageName)) { $cacheFileExists = TRUE; $cacheFileModifyDate = date ("r", filemtime($cacheDirectory.$getPageName)); $currentCacheDate = strtotime(date("r")); $cacheExpirationDate =strtotime($cacheFileModifyDate."+".$expireTime); } else { // otherwise, set all info to '' $cacheFileModifyDate = ''; $currentCacheDate = ''; $currentCacheDate = ''; $cacheExpirationDate =''; } // If cache file expired, or if cache file doesn't exist, generate a new one if($currentCacheDate >= $cacheExpirationDate || $cacheFileExists == FALSE) { // Use output buffer to grab contents generated by wordpress ob_start(); define('WP_USE_THEMES', true); /** Loads the WordPress Environment and Template */ require('./wp-blog-header.php'); // Grab output buffer contents $cacheFileContents=ob_get_contents(); // Flush to browser before recording cached contents ob_end_flush(); // Open file for writing $cacheFileOpen = fopen($cacheDirectory.$getPageName, 'w'); // Write cache contents to file and close file fwrite($cacheFileOpen, $cacheFileContents); fclose($cacheFileOpen); }else{ // If file is cached and not expired, grab and echo contents echo file_get_contents($cacheDirectory.$getPageName); } ?>[/php]
This drastically cut down on the time to first byte and brought me from an F to an A. When the cache is on, the only thing php has to do is check if the cache file exists and is not expired, if it passes, it loads and outputs text straight from the cache file to screen. No database calls or anything is done and so backend processing is almost non-existant.
The Results
No Cache, TTFB= 3.074 seconds:
http://www.webpagetest.org/result/120131_4D_32BRA/1/details/
With Cache, TTFB= 0.414 seconds:
http://www.webpagetest.org/result/120131_A7_32BRK/1/details/
Some things to note:
First I designed this for my site, where I turned wordpress isn’t an article cms, and did so with no user comments. So for me, as it is now, it’s fine. However, if you have users placing comments via wordpresses internal comment system (as opposed to something like disqus), you will need to program something to invalidate the cache (like delete the cache file) if a new comment is made. This should be easy enough, you just have to edit the wp-comments-post.php file, grab and generate the current url and then delete the cache file. Or, the other option is to deal with the comments not showing by just setting a lower cache time (like 3 hours). But it may seem odd to the user if the comments don’t show right away, so i’d still recommend invalidating the cache.
Secondly, since this is editing the core wordpress files (as it has to), it will get overridden when a wordpress update is done. But these additions are so generic you can easily re-apply them with each update.