Context

I'm involved in the deployment of a new website for a local nonprofit, which was developed by local university students as part of their course work. They chose to use the C5 CMS system to build the website (http://www.concrete5.org/)

I was responsible for migrating the development website onto a production system, and tuning performance of the website. In the course of this process, I got a lot of help from the C5 community - in particular, this thread in the forums - http://www.concrete5.org/community/forums/chat/concrete-on-steroids/ )and also a big thanks to Shaun (AKA Phallanx, as per his handle in the thread/forums) who helped me out with final tuning, and getting the performance into great shape. (Please note also that his php-based website optimizer app, MISER, is available on SourceForge at the URL, http://sourceforge.net/projects/miser/ )

A few reference notes about the site I was debugging,

  • The production server is a XenServer based VM hosted with Vexxhost in Montreal (http://vexxhost.com/cloud_hosting ) - I like their business, so I wanted to give them credit here :-). The VM in question is running CentOS 5.6, has stock YUM repos enabled by default, and has system resources of one vCPU (E5620 @ 2.40GHz); 1 gig RAM dedicated; 5 gigs disk (it is a modest sized website, after all) and an appropriate amount of monthly bandwidth - approx cost is $30/mo I think.
  • Out of the box, CentOS is running PHP 5.1.6, which is below rev for 'ideal' operation of C5. I followed the steps outlined here: http://wiki.centos.org/HowTos/PHP_5.1_To_5.2 in order to painlessly get a newer version of PHP installed (5.2.10 - still not the latest and greatest, but a good step up).
  • Otherwise, it is stock install for apache, mysql, gd, pear, etc.

Basic High-Level notes on tuning:

  1. Initial setup gave dreadful performance - initial page load times were horrible (10 seconds or longer). After the first page visit, subsequent pages performed better, but still it was not painless. Additionally, the admin pages (once logged in) all performed quite sluggishly.
  2. MySQL was tuned to reflect settings more consistent with "my.medium" template; and also with some adjustments as per the "SQLTuner" script. However, performance remained relatively unchanged. (see below for copy of the mysql config file)
  3. APC PHP cache was installed, and this helped performance, but things were still not great. APC install was pretty typical (via PECL) - for notes also see below.
  4. I experimented with C5 "cache" settings internally (via the dashboard) but had inconsistent results - ie - things didn't get much better, and sometimes it wasn't clear they were better; and after some posts in the forums suggestsing cache hindered performance - I ended up turning off internal cache settings entirely in C5.
  5. The biggest single performance boost came with turning on "KeepAlives" in the apache config. By default they were turned off. I subsequently adjusted the "KeepAlive Timeout" down from 15seconds to a more reasonable 5 seconds, to help alleviate what I understand to be one of the main concern with KeepAlives - allowing individual clients to monopolize too large a slice of your apache thread pool for tool long.
  6. I also installed the 3rd party add-on, miser, and it gave additional boosts to performance - and no negative impact - so an easy win.
  7. I experimented with "Google PageSpeed" (http://code.google.com/speed/page-speed/) instealled into apache as a module; this did appear to boost performance somewhat but had a few 'wrinkles' - in particular, some internal C5 editing behaviour stopped working properly (adding new block to a page - the spinner would time out // and in the httpd error log there was a line reading, "add_block_popup.php not found". Disabling pagespeed module made this issue disappear, so I have left pagespeed off.

The end-game config I'm using presently is:

  • Keep-Alives enabled, 5 second timeout, other settings defaults
  • APC installed
  • C5 internal cache not enabled at all
  • Miser installed

The result of all this? First-visit page loads have dropped from >10seconds to ~2 seconds or less. Repeat page loads have dropped from 0.85 to 0.5 seconds. Additionally, the admin interface (ie, once logged in to the C5 admin interface) has gone from "arrgh" slow to "perfectly usable and snappy".

Page speed tests for 'benchmarking' shown below were all performed using the website, http://www.webpagetest.org/ using default test site (Dulles, VA, DSL / IE7 client) (Thanks again to Shaun for pointing this one out to me - a great free resource for testing your website and getting solid metrics and good advice on where tuning is needed).

also note, metrics captured below are basic - as per the output from the testing site above; with

  • (A) = doc complete score in seconds
  • (B) = fully loaded score in seconds

Note that the bench scores below progress thus:

  • Fully tuned / fastest
  • gradually turning off tuning features, getting progressively slower
  • then turning them back on, different order, to get better feel for 'relative value' of tune features
  • Endpoint is same as start point, in theory

Note that since scores are slightly different for start,end it also illustrates that .. running the same test 2 times in a row can yield slightly different results .. and this is normal :-)


FASTEST:

Tuned, with miser and APC
-------------------------

             (A)   (B)
First view: 1.67s/2.23s
ReloadView: 0.55s/0.55s

without miser, with APC
-------------------------
             (A)   (B)
First view: 2.31s/2.31s
ReloadView: 0.65s/0.65s

without miser or apc
-------------------------
             (A)   (B)
First view: 3.30/3.30s
ReloadView: 0.85s/0.85s


ABSOLUTE WORST:
without miser,APC and with keepalives turned OFF
-------------------------
             (A)   (B)
First view: 17.85s/17.85s
ReloadView: 0.84s/0.84s

without keepalives or APC, but with Miser turned back on:
-------------------------
             (A)   (B)
First view: 17.09/20.43s
ReloadView: 0.87s/0.87s


without keepalives, but with APC and miser turned back on:
-------------------------
             (A)   (B)
First view: 19.52s/19.52s
ReloadView: 0.62s/0.62s

BACK WHERE WE STARTED:
with  keepalives, APC and miser turned back on:
-------------------------
             (A)   (B)
First view: 1.38s/1.72s
ReloadView: 0.56s/0.56s

OTHER MISC REF INFO POSSIBLY OF INTEREST:

MYSQL: Config used for MySQL is as follows:

[root@VM etc]# cat my.cnf
#
#
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
#
# TDC added config as per my-medium stock my.cnf
# april-18-11
skip-locking 
key_buffer = 16M
max_allowed_packet = 1M
table_cache = 512
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M
skip-bdb
#
## SQL TUNER TWEAKS APR-26-11 TDC
max_connections = 25
query_cache_size = 16M
join_buffer_size = 256K
tmp_table_size = 128M
max_heap_table_size = 128M
thread_cache_size = 8
innodb_buffer_pool_size =16M
#
## END OF TDC PASTE CONFIG
#
old_passwords=1
#
[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
#
#EOF 


In this configuration, MySQL is gobbling up decent amount of ram, but nothing crazy, as illustrated by this TOP capture:

top - 16:20:42 up 7 days, 18:03,  2 users,  load average: 0.00, 0.00, 0.00
Tasks:  81 total,   2 running,  79 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1048752k total,   897932k used,   150820k free,   135712k buffers
Swap:  1048568k total,        0k used,  1048568k free,   586232k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                         
 1356 mysql     15   0  155m  39m 4896 S  0.0  3.8   3:42.05 mysqld   

APACHE Apache is using most of the ram in the VM, but we've still got a decent chunk left unallocated, so we seem to be running smoothly. Rough capture from top is shown below:

top - 16:20:42 up 7 days, 18:03,  2 users,  load average: 0.00, 0.00, 0.00
Tasks:  81 total,   2 running,  79 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1048752k total,   897932k used,   150820k free,   135712k buffers
Swap:  1048568k total,        0k used,  1048568k free,   586232k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                         
 1356 mysql     15   0  155m  39m 4896 S  0.0  3.8   3:42.05 mysqld                                                                                          
18918 apache    15   0 99.6m  24m  18m S  0.0  2.4   0:01.44 httpd                                                                                           
18917 apache    15   0  103m  23m  13m S  0.0  2.3   0:00.87 httpd                                                                                           
18921 apache    15   0 99.9m  22m  16m S  0.0  2.2   0:00.84 httpd                                                                                           
18920 apache    15   0 99.8m  22m  16m S  0.0  2.2   0:00.84 httpd                                                                                           
18924 apache    23   0 99.6m  21m  16m S  0.0  2.1   0:00.97 httpd                                                                                           
18919 apache    15   0 99.6m  21m  16m S  0.0  2.1   0:00.36 httpd                                                                                           
19006 apache    15   0 97.9m  20m  16m S  0.0  2.0   0:00.95 httpd                                                                                           
18922 apache    15   0 99.8m  16m  10m S  0.0  1.6   0:00.35 httpd                                                                                           
18923 apache    15   0 99.8m  16m  10m S  0.0  1.6   0:00.38 httpd                                                                                           
20166 apache    15   0 99.8m  16m  10m S  0.0  1.6   0:00.32 httpd                                                                                           
20167 apache    15   0 99.9m  15m 9.9m S  0.0  1.5   0:00.10 httpd                                                                                           
20165 apache    15   0 98.4m  12m 8200 S  0.0  1.2   0:00.02 httpd                                                                                           
18915 root      18   0 89296 7236 4304 S  0.0  0.7   0:00.01 httpd   

Note the following adjustments that were made which impact apache config:


in /etc/http/conf/httpd.conf:

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5


Other tweaks:

in dir, /etc/httpd/conf.d/

[root@store conf.d]# more sitename.conf 
<Directory "/var/www/html">

# -- Expires Headers --
<ifModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 seconds"
  ExpiresByType text/html "access plus 1 seconds"
  ExpiresByType image/gif "access plus 2592000 seconds"
  ExpiresByType image/jpeg "access plus 2592000 seconds"
  ExpiresByType image/png "access plus 2592000 seconds"
  ExpiresByType text/css "access plus 604800 seconds"
  ExpiresByType text/javascript "access plus 216000 seconds"
  ExpiresByType application/x-javascript "access plus 216000 seconds"
</ifModule>

</Directory>


MOD DEFLATE CONFIG:

[root@store conf.d]# more mod_deflate.conf 
#
# TDC May-5-11
#
# Insert filter
SetOutputFilter DEFLATE

# Netscape 4.x has some problems...
BrowserMatch ^Mozilla/4 gzip-only-text/html

# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip

# MSIE masquerades as Netscape, but it is fine
# BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

# NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
# the above regex won't work. You can use the following
# workaround to get the desired effect:
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

# Don't compress images
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIf Request_URI \.js no-gzip

# Make sure proxies don't deliver the wrong content
<IfModule mod_headers.c>
	Header append Vary User-Agent env=!dont-vary
</IfModule>

Please note, these tweaks - for expires and compress for httpd - are directly copied from other resources found via google; I don't take any credit for them; but alas I'm having trouble finding the exact URLs right now as I wham this little document together; hence I am not putting it here - sorry.

APC NOTES:

Config for APC is tweaked thus:



[root@store conf.d]# cd /etc/php.d
[root@store php.d]# more apc.ini 
extension=apc.so
#apc.shm_size = 64
apc.shm_size=64M
[root@store php.d]# 

Note that install of APC is more or less stock, as per these basic steps:


yum install php-pear httpd-devel php-devel pcre pcre-devel

then

pecl install apc

once done, create a file apc.ini approx similar to the sample provided above.

reload apache

if you want to observe APC cache hit performance, put a copy of apc.php 
into a 'secret dir' only known to you somewhere on your web server; this
 file is available as part of the APC distribution; for example you could
 grab it as follows:

  492  cd /opt/src; mkdir apc
  493  cd apc
  494  wget http://pecl.php.net/get/APC
  497  mv APC apc.tgz
  498  gzip -d apc.tgz 
  499  tar xvf apc.tar 
  500  ls -la
  501  cd APC-3.1.7/
  502  ls -la
  503  cp apc.php /var/www/html/secret-dir-name-only-known-to-you-the-webmaster/

Then point your browser to the URL, 

http://host.name.your.site/secret-dir-name-only-known-to-you-the-webmaster/apc.php

and you will see some happy stats back from APC if it is installed and working.

On the website I've setup, I seem to see >95% cache hit rate with APC after a few visits on the site.


Finally a small footnote and disclaimer: This is here for my reference only; if you do this and your site blows up, or other various and sundry nasty things happen - you have nobody but yourself to blame for working on a production host, live, without backups, and for listening to advice that anyone like me is willing and able to give away for free :-)

Anyhow. I do hope this info helps someone out there, sometime.