If you ask me if I am against doing everything yourself or using the available solutions, my answer usually is going to be that it is best to use what’s available and save yourself the time and efforts to do (and maintain) a solution of your own. „Usually“. Here’s a cautionary tale of how tried to go by that rule and did the opposite.
I have this pet project which is 10 WordPress sites on a 512MB VPS. Each of those sites has several thousand posts and few thousand tags/categories. It used to be no problem for WordPress to handle such sites, but with each new version WordPress is getting slower and slower. Last configuration was on a 512MB slice from Slicehost, which at the time (late 2008) seemed like the best solution.
To make it run as fast as possible I deployed APC, although any other opcode cache would do fine. The „project“ is a set of 10 WordPress sites. Unfortunately this made it run slower than originally. Why ? Imagine having 2 sites, abc.com and xyz.net, both running the latest WordPress, so almost everything inside them is the same – everything but their own unique themes and their own set of plugins. Now, when those run on the same machine APC will have to store the files for both of them, e.g.
/var/www/xyz.net/html/wp-login.php – and this is the same for every other file from the WordPress distribution. At first it is obvious that this is just waste of space since those are identical. At second look however you see the worse downside: these identical files are going to take twice as much space. This will cause the APC memory to deplete really fast and because of this decreasing the performance boost we are expecting to get from having a opcode cache. Now instead of having just 2 sites imagine having 10 sites: the cached copies inside APC were cleaned up before even being hit, because the space in the bytecode cache was not enough.
Using APC with symlinked WordPress.
The solution for the problem above was to symlink everything for each site except the configuration from
wp-config.php – in this way APC will not have to deal with duplicate copies of the same files. The plugins and themes are also placed with one another. Here’s what a
ls -la looks like:
lrwxrwxrwx 1 www-data root 27 Feb 18 16:36 crossdomain.xml -> /var/www/wp/crossdomain.xml-rw-r--r-- 1 www-data 1000 246 Dec 4 2007 favicon.icolrwxrwxrwx 1 www-data root 21 Feb 18 16:36 index.php -> /var/www/wp/index.phplrwxrwxrwx 1 www-data root 23 Feb 18 16:36 license.txt -> /var/www/wp/license.txtlrwxrwxrwx 1 www-data root 23 Feb 18 16:36 readme.html -> /var/www/wp/readme.htmllrwxrwxrwx 1 www-data root 22 Feb 18 16:36 robots.txt -> /var/www/wp/robots.txtlrwxrwxrwx 1 www-data root 27 Feb 18 16:36 wp-activate.php -> /var/www/wp/wp-activate.phplrwxrwxrwx 1 www-data root 20 Feb 18 16:36 wp-admin -> /var/www/wp/wp-adminlrwxrwxrwx 1 www-data root 22 Feb 18 16:36 wp-app.php -> /var/www/wp/wp-app.phplrwxrwxrwx 1 www-data root 23 Feb 18 16:36 wp-atom.php -> /var/www/wp/wp-atom.phplrwxrwxrwx 1 www-data root 30 Feb 18 16:36 wp-blog-header.php -> /var/www/wp/wp-blog-header.phplrwxrwxrwx 1 www-data root 32 Feb 18 16:36 wp-comments-post.php -> /var/www/wp/wp-comments-post.phplrwxrwxrwx 1 www-data root 31 Feb 18 16:36 wp-commentsrss2.php -> /var/www/wp/wp-commentsrss2.php-rw-r--r-- 1 www-data 1000 1048 Feb 23 20:08 wp-config.phplrwxrwxrwx 1 www-data root 22 Feb 18 16:36 wp-content -> /var/www/wp/wp-contentlrwxrwxrwx 1 www-data root 23 Feb 18 16:36 wp-cron.php -> /var/www/wp/wp-cron.php...
How is that going to help? Very simple: there are no more duplicates and APC only works with one set of files. A little tweaking was required to make wp-super-cache plugin work, but for a while everything was OK.
The new setup.
Recently I moved the project from Slicehost to Linode: it is a lot more affordable – 32bit setup for almost half the money (for 512MB). For the new setup I decided to ditch Apache and try something new – nginx + phpfpm. That was not enough and decided to ditch wp-super-cache and try w3-total-cache with all the different layers of performance boost that it offers. Linode makes very easy to deploy a LEMP setup, and with some help from a friend of mine I was ready to go. Then I installed w3-total-cache and the problems started:
- you got to do your own rewrite rules for nginx
- the „page cache“ was not using the domain name for the site, so the wrong cached pages popped up at the wrong places, e.g. abc.com/2008/10/ opened the page from xyz.net/2008/10/
- the performance was degraded, not improved
I spend the best of the last 2 days to try and find a way around this issue. It seems that W3TC prepends the domain name only when the WordPress is a WPMU/Multisite installation. I do not wanted to dive into that, so after 30 mins ot thinking I decided to ditch W3TC and do a „page cache“ (funky caching really) on my own. After several hours I was ready, and it works like a charm. It pretty much works like wp-super-cache, but without all the settings and admin pages – I know how my projects behaves so I didn’t need all the stuff for purging cache on adding comments, changing statuses, writing new posts or whatever. I customized part of W3TC for cleaning up the static files on disk, but instead of relying on WordPress’ pseudo-cron I decided to use the VPS crontab instead: and it is really easy – you just have to call the script ;)
The script and how to set it up yourself ?
First you got to enable
WP_CACHE in your
wp-config.php file, because this is the requirement to make WordPress include our script. The script is called
advanced-cache.php (WordPress named it like that) and it has to be placed inside your
wp-content/ folder. You can find the rewrite rules for nginx inside the script as a comment (if you are wondering what HB stands for, that’s an abbreviation for the pet project).
So, fuck you W3TC ;)
PS. It turns out somebody else has that symlink solution figured out long time before myself, and it is actually documented on WordPress’ Codex:
This system is based on Allan Mertner’s original symbolic link hack.