We recently migrated all of our websites to some new servers, and for the first time we have some dedicated servers for our PHP CMS platform. This gave me the opportunity to fine tune configuration specifically for PHP, and I was surprised at how much difference could be made without even touching the application code.
The setup includes two web servers and a dedicated MySQL server, all with 1G of RAM, running CentOS. I was using Apache bench for the tests, with the parameters ab -c 10 -n 500 http://www.example.com/ (10 concurrent requests, 500 in total). The starting point was a standard yum-installed LAMP setup except for the addition of the Remi repositories for more recent PHP packages (PHP 5.2.9, MySQL 5.0.45, Apache 2.2.3).
If you're not familiar with Apache bench, the 'requests per second' stat is the thing to look out for in the results, higher is better. Each test was run after 'warming up' the caches, and there weren't any failed requests.
Firstly the baseline test (fresh after installation):
Requests per second: 13.31 [#/sec] (mean)
Time per request: 751.477 [ms] (mean)
Time per request: 75.148 [ms] (mean, across all concurrent requests)
Transfer rate: 43.27 [Kbytes/sec] received
Then after installing APC:
Requests per second: 18.52 [#/sec] (mean)
Time per request: 539.833 [ms] (mean)
Time per request: 53.983 [ms] (mean, across all concurrent requests)
Transfer rate: 60.24 [Kbytes/sec] received
Then started the tweaking. The Remi php-apc package has apc.shm_size set to 32M by default (slightly higher than the PHP default of 30M). As I understand it, this variable controls how much RAM is reserved by APC. If it is set too low, APC won't be able to cache all of your commonly included PHP files; but as you increase it, once you pass the point at which your whole app can be cached you'll start to see a reduction in performance, since you're reducing the amount of memory that can be used by Apache. After some experimentation I kept this at 32M.
Next I turned off apc.stat. With this option enabled APC checks the last modified time of each included PHP script on each request to see if it needs to be recompiled. With it off you save quite a lot of file system calls, especially if you have a lot of file includes, but it means you need to restart Apache after any code changes. Results:
Requests per second: 18.89 [#/sec] (mean)
Time per request: 529.346 [ms] (mean)
Time per request: 52.935 [ms] (mean, across all concurrent requests)
Transfer rate: 61.43 [Kbytes/sec] received
Not much difference! The application code in this case is stored on a shared NFS drive, so perhaps the local NFS client is caching some of the file information.
Next I enabled apc.include_once_override, which gets around the problem APC used to have caching files included using include_once or require_once:
Requests per second: 23.00 [#/sec] (mean)
Time per request: 434.698 [ms] (mean)
Time per request: 43.470 [ms] (mean, across all concurrent requests)
Transfer rate: 74.81 [Kbytes/sec] received
If your application uses Zend Framework you'll probably see even more benefit here, since ZF uses include_once extensively.
Next came the MySQL tweaking. Although MySQL comes with some suggested configuration files for different setups, the defaults are quite conservative. If you're not familiar with the different MySQL config options, I'd suggest installing MySQLTuner, a free script you can run which will analyse your installation and suggest some changes.
By far the biggest improvements you can make are enabling the query cache and increasing key_buffer_size. If you're running a dedicated server for MySQL you can assign a decent chunk of RAM to these two. The settings I used:
key_buffer_size=128M
query_cache_size=256M
query_cache_limit=16M
thread_cache_size=4
table_cache=128
Results:
Requests per second: 61.60 [#/sec] (mean)
Time per request: 162.339 [ms] (mean)
Time per request: 16.234 [ms] (mean, across all concurrent requests)
Transfer rate: 199.42 [Kbytes/sec] received
Big improvement!
Obviously there are a number of application-level things that can be done to improve performance further (page/fragment caching, caching objects in APC/memcache), but this shows how much improvement you can gain just by tweaking some config files.
Comments
14th Jul, 2010
Thank you for your insights, very nice article!
One detail: apc.shmsize apparently refers to the maximum segement size of RAM you kernel can handle. You can find this out using "cat /proc/sys/kernel/shmmax".
You could try as well, to tell APC to use more than 1 segment, e.g. "apc.shmsegments=3".
Would be interesting to see, if this has any effects on performace.
16th Oct, 2012
Thanks,DAVID.Good work!But I tried the test myself and get a ooitsppe result. I doubt why I think I have something wrong.My os is winxp 32bit, so I install fuel Manually:1.download the zip,unzip it2.I don't change the directory at all.3.ab -kc 10 -t 30 to get the testThe result is: 197 to 203 Requests per second.But I do the helloworld test for f3, the result is 300+!I think this is because I didn't optimize the code ,so I tried1.modified public/index.php,comment the profile code2.modiied app/config/config.php,change environment to Fuel::PRODUCTION ,I even tried turn cache to true.But the result seemed to be the same I don't know why btw, my english is poor , and also my php. I hope get your help. thanks!
Add Comment