dltj.org runs on a relatively tiny box — a Pentium III with 512MB of RAM. I’m running a Gentoo Linux distribution, so I actually have a prayer of getting useful work out of the machine (it server is actually a recycled Windows desktop), but the performance just wasn’t great. As it turns out, there are several easy things one can do to dramatically improve life.
The box is both a mail server (IMAP) and a WordPress server. A rough eyeball at the process accounting on the server shows that it spends about 40% of the time doing mail (mostly taken up by Clamscan virus scanning and spam checking) and another 40% doing MySQL and web stuff. Since there isn’t much dynamic content on the box and nothing else using the database but WordPress, I’m fairly confident that blog traffic is almost all of that 40%. I’m using MySQL 5.0.x, Apache 2.0.x and WordPress 2.0.x with about two dozen plugins.
Taking PHP Up A Notch
PHP is an interpreted programming language, meaning that each time a script runs it needs to be translated into something closer to machine code (called the ‘opcode’). (As opposed to compiler languages like C and Java where you compile the source code into an executable in one step and then run that executable in a second step.) For an application like WordPress, where the source code is not changing, this translation causes a lot of overhead. Fortunately, there is a PHP plug-in called the Alternative PHP Cache that will saved the translated opcode the first time the script runs and use it for subsequent invocations. Getting this set up is pretty easy (these are Gentoo-specific commands, your Linux distribution will vary and I am glossing over a number of distribution-specific details like how to install packages and where the configuration files will reside):
emerge -aDNtuv pecl-apcwill download and install PHP APC and its dependencies (yep — that easy…I love Gentoo)
- Change the configuration defaults in
/etc/php/apache2-php5/ext/apc.ini. I’ve found that one shared segment of 20MB is enough, so I set
apc.shm_size="20". The rest of the settings are as they came in the distribution.
- Restart your web server:
APC comes with a nifty PHP page that will give you cache statistics and details. If you copy
/usr/share/php5/apc/apc.php into your ‘htdocs’ somewhere and execute that page from a browser, you’ll see what I mean. (This is how I learned that 20MB of opcode cache space was fine for my application.)
Kicking MySQL Into Gear
Database tuning focuses a great deal on memory management. Your RAM will always be an order of magnitude faster than reading blocks off a disk. RAM, of course, costs more per MB than disk, though, so you have to select memory management strategies carefully. WordPress is, of course, a read-intensive operation. In other words, the majority of SQL statements are SELECTs rather than INSERTs, UPDATEs, or DELETEs. With that in mind, we tune MySQL with a read-intensive strategy. I found some of the best guidance in Peter Zaitsev’s “What to tune in MySQL Server after installation” and the ez.no documentation on Optimizing for read performance.
The changes I made to my MySQL configuration file, in the
[mysqld] section are:
key_buffer = 6M ; (Actually, a decrease from the default since I didn't seem to need as much)table_cache = 512max_connections = 25thread_cache = 16query_cache_type = 1query_cache_limit = 1Mquery_cache_size = 20M
The 20MB query cache limit seems to be just about the right size for me. I seem to get very close to the edge of that buffer, but never seem to go over.
Finishing Up with a WordPress Plug-in
One more thing is needed to make this all come together: Mark Jaquith’s Post Query Accelerator. As Mark points out on his blog, WordPress “always ask[s] for posts with post_date_gmt <= ‘$now’ where $now is set to the current time, to prevent posts in the future from showing up.” If one turns on cache querying as described above, the “problem with $now is that it changes [with each query], so the query is never exactly the same again and the cache doesn’t help.” Mark’s plug-in “freezes” the value of $now to 15 minute increments or to whenever a post is added/updated, which ever comes first. That makes the query cache useful again and all is well.
Simply download the plug-in from Mark’s page and enable it in WordPress. Note that this plug-in is not needed for WordPress 2.1 and higher as the core developers have solved the “$now” problem with the “future” post status.