For this quick article I am only going to quickly cover the A and the M of the LAMP stack.
The Setup
Assumptions: You have root on the server in question or you at least have enough server access to effect the configuration of Apache and MySQL.
The test server setup:
- A Virtual Private server running on Rackspace cloud
- Ubuntu 9.04 (Jaunty Jackalope)
Few quick commands and viola, you have a LAMP server…
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo locale-gen en_US.UTF-8
$ sudo apt-get install apache2-mpm-prefork php5 php5-dev mysql-server-5.0 mysql-client-5.0 php5-mysql
$ sudo a2enmod rewrite
- Installed latest Wordpress and added some test posts
Baseline Performance:
Before you start changing anything be sure to record the baseline performance ot the server. Place the server under load. I used http_load but anything similar can be used
The Test URL’s that http_load will draw from
$ cat pdxphp-test
http://67.23.47.195/
http://67.23.47.195/2010/02/test-post-1/
http://67.23.47.195/2010/02/test-post-2/
http://67.23.47.195/2010/02/test-post-3/
http://67.23.47.195/2010/02/test-post-4/
http://67.23.47.195/2010/02/test-post-5/
Run the test
$ http_load -parallel 5 -seconds 20 pdxphp-test
83 fetches, 5 max parallel, 1.74373e+06 bytes, in 20.0009 seconds
21008.8 mean bytes/connection
4.14982 fetches/sec, 87182.7 bytes/sec
msecs/connect: 80.6013 mean, 99.26 max, 71.974 min
msecs/first-response: 795.394 mean, 5495.6 max, 225.29 min
HTTP response codes:
code 200 -- 82

web server under load
To see how much RAM MySQL actually wants, we restart it with Apache stopped

MySQL running on it's own, gobbles up 21M RAM
Apache
Reduce the number of modules loaded
You can start with this. It will typically not give you incredible results but you will save some resource and it is a good security policy.
You can list your loaded modules with
apache2ctl -t -D DUMP_MODULES
Loaded Modules:
core_module (static)
log_config_module (static)
logio_module (static)
mpm_prefork_module (static)
http_module (static)
so_module (static)
alias_module (shared)
auth_basic_module (shared)
authn_file_module (shared)
authz_default_module (shared)
authz_groupfile_module (shared)
authz_host_module (shared)
authz_user_module (shared)
autoindex_module (shared)
cgi_module (shared)
deflate_module (shared)
dir_module (shared)
env_module (shared)
mime_module (shared)
negotiation_module (shared)
php5_module (shared)
rewrite_module (shared)
setenvif_module (shared)
status_module (shared)
Choosing to unload any of these modules is completely dependent of how you are using the server. But for many PHP developers can safely unload cgi.
$ sudo a2dismod cgi
Module cgi disabled.
Run '/etc/init.d/apache2 restart' to activate new configuration!
sam@pdx-test:/etc/apache2/mods-enabled$ sudo apache2ctl start
Turn off htaccess
Explanation from Apache. So all you need to do is take the contents of the .htaccess file in your site’s webroot and place that in the corresponding vhost. Then turn off htaccess files with AllowOverride None and rm the .htaccess file in the web root after restarting apache.
sudo vi /etc/apache/sites-enabled/wordpress
<Directory /var/www/wordpress>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
AllowOverride None
</Directory>
rm /var/www/wordpress/.htaccess
Now, how to really affect apache
This is all well and good and Apache will be better off for it but if you had a server that was paging and dying under load, after making these changes, that will probably not change.
The bigger issue shown in the top output above is that, for the amount of physical RAM, there are just too many 20+MB processes
the apache conf file controls these settings (/etc/apache2/apache2.conf on Ubuntu)
The stock settings:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxClients 150
MaxRequestsPerChild 0
</IfModule>
Possible more appropriate settings for a little personal blog site. Overall you need to lower MaxClients to the amount you can run without paging. If this is too few, you probably need more RAM.
<IfModule mpm_prefork_module>
StartServers 1
MinSpareServers 1
MaxSpareServers 5
MaxClients 5
MaxRequestsPerChild 1000
</IfModule>
The Results thus far

Top output with Apache Trimmed and under load
MySQL
Now onto MySQL. We saw above that MySQL is staring up and grabbing ~21M of RAM. A low to medium volume LAMP server probably doesn’t need all that but, again every situation is different. Here though are some notes of how to tune the Low Hanging Fruit of MySQL
If you don’t use it, Turn it off
Same as with Apache modules, turn off what you do not need. Storage engines are a great place to start. Very few frameworks (including WP), utilize InnoDB by default. Most use good old MyISAM.
Stopping MySQl from loading the InnoDb engine (and its predecessor:bdb) is quite simple, the lines are in my.cnf, just uncomment them
skip-innodb
skip-bdb
Now there are many, many MySQL config options that affect the amount of resource it uses. I’ll just cover a couple here, the key buffer and the query cache
The stock settings for these are
# used for holding built indexes
key_buffer = 16M
# largest query that is cache-able
query_cache_limit = 1M
# used for caching queries
query_cache_size = 16M
For many types of sites, these can be a bit large, for small sites, some suggested settings might be:
# used for holding built indexes
key_buffer = 4M
# largest query that is cache-able
query_cache_limit = 500K
# used for caching queries
query_cache_size = 8M
To see what the runtime values are concerning the query cache and key buffer use (once server has been under load)
mysql> SHOW STATUS WHERE Variable_name LIKE 'Key_blocks_%' OR Variable_name LIKE 'Qcache_%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Key_blocks_not_flushed | 0 |
| Key_blocks_unused | 13369 |
| Key_blocks_used | 27 |
| Qcache_free_blocks | 4 |
| Qcache_free_memory | 16326632 |
| Qcache_hits | 5969 |
| Qcache_inserts | 95 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 410 |
| Qcache_queries_in_cache | 46 |
| Qcache_total_blocks | 106 |
+-------------------------+----------+
11 rows in set (0.00 sec)
As you reduce key_buffer and query_cache_size, check the output of the SHOW STATUS command shown above while the server is under load to verify that you are not running low on Key_blocks_unused or Qcache_free_memory
The Final result of our tinkering
So now that we have trimmed Apache and MySQL lets see what the top output looks like with the server under load

Our CPU load has reduced (idle is up to ~65%). More importantly we are no longer paging and we still have a bit of RAM left over. MySQL is running with ~8M of RAM, down from ~21M.
Also as seen below, we’ve roughly doubled our performance. Up to 6.85 req/sec and first response in down to 276ms.
$ http_load-parallel 5 -seconds 20 pdxphp-test
137 fetches, 5 max parallel, 4.69493e+06 bytes, in 20 seconds
34269.6 mean bytes/connection
6.85 fetches/sec, 234747 bytes/sec
msecs/connect: 80.6284 mean, 96.353 max, 73.043 min
msecs/first-response: 276.766 mean, 447.707 max, 225.995 min
HTTP response codes:
code 200 -- 137
Links: