After LAMP setup and Apache and PHP configuration, everything works as expected. Your site is becoming more and more popular. Popularity means more traffic or we can say more requests per second. Did you ever measure how many requests per second your Web server can handle and how to improve it? This article should give the answer and show how to increase quickness of Web applications like WordPress, phpBB, Joomla ...
1. Benchmarking tool
The most common benchmarking tool in LAMP World is ab - Apache HTTP server benchmarking tool. It is a command line utility and it comes in httpd-tools rpm package. Open terminal, write ab and press enter. You should get the following usage help:
Usage: ab [options] [http[s]://]hostname[:port]/path Options are: -n requests Number of requests to perform -c concurrency Number of multiple requests to make -P attribute Add Basic Proxy Authentication, the attributes are a colon separated username and password. -X proxy:port Proxyserver and port number to use -V Print version number and exit ... ...
Now it's time to test your Web application. Write ab with URL of your Web site. Don't forget to add slash at the end because utility expects URL:
Without slash in first case (only Web site name), you will get an error. Without slash in second case you will get document length 0 and wrong statistics. Why wrong statistics? Because without slash, ab will try to retrieve new-to-wordpress file and the Web server will respond with 301 (permanent redirect) to the directory. Instead of redirection, ab will display statistics of received HTTP headers. On the other hand, browser will access pages (directories) without slash because he "understands" redirection. If you are sitting behind the firewall or you have to use proxy, please add -P or -X options.
[dbunic ~]$ ab www.redips.net/wordpress/new-to-wordpress/ Server Software: Apache/x.y.z Server Hostname: www.redips.net Server Port: 80 Document Path: /wordpress/new-to-wordpress/ Document Length: 21144 bytes Concurrency Level: 1 Time taken for tests: 0.332835 seconds Complete requests: 1 Failed requests: 0 Write errors: 0 Total transferred: 21341 bytes HTML transferred: 21144 bytes Requests per second: 3.00 [num/sec] (mean) Time per request: 332.835 [ms] (mean) Time per request: 332.835 [ms] (mean, across all concurrent requests) Transfer rate: 60.09 [Kbytes/sec] received
Without any switch, ab will make 1 contact to Web server. Let's make scenario more realistic. I will rise request number to 100 and concurrency to 10. Web server will feel load of 10 clients concurrently requesting the same page. Each client will send 10 requests.
[dbunic ~]$ ab -n100 -c10 www.redips.net/wordpress/new-to-wordpress/ Concurrency Level: 10 Time taken for tests: 29.35572 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 2134100 bytes HTML transferred: 2114400 bytes Requests per second: 3.44 [num/sec] (mean) Time per request: 2903.557 [ms] (mean) Time per request: 290.356 [ms] (mean, across all concurrent requests) Transfer rate: 71.77 [Kbytes/sec] received
You can see how WordPress Web application can give only 3.44 requests per second and time taken for test is about 30 seconds. With some easy magic, LAMP server can be 150% faster
2. MySQL caching
MySQL is simple and quick database but it can be even faster. By default, caching is turned off. To check MySQL caching, please log in to MySQL console and list Qcache variables:
mysql> show status like 'Qcache%'; +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | Qcache_free_blocks | 0 | | Qcache_free_memory | 0 | | Qcache_hits | 0 | | Qcache_inserts | 0 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 0 | | Qcache_queries_in_cache | 0 | | Qcache_total_blocks | 0 | +-------------------------+-------+
To turn on MySQL query cache, open /etc/my.cnf file and add query-cache-type and query-cache-size options in [mysqld] section. After file is saved, restart MySQL server.
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql # Default to using old password format for compatibility with mysql 3.x # clients (those using the mysqlclient10 compatibility package). old_passwords=1 # turn on query cache query-cache-type = 1 query-cache-size = 32M [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid
query-cache-type variable has three options:
- 0 or OFF
- 1 or ON
- 2 or DEMAND
query-cache-size defines size of cache and 32Mb is enough for some average Web application. After enabling MySQL cache, query cache variables should look:
mysql> show status like 'Qcache%'; +-------------------------+----------+ | Variable_name | Value | +-------------------------+----------+ | Qcache_free_blocks | 1 | | Qcache_free_memory | 33545600 | | Qcache_hits | 0 | | Qcache_inserts | 0 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 1 | | Qcache_queries_in_cache | 0 | | Qcache_total_blocks | 1 | +-------------------------+----------+
Please see how ab statistics looks after enabling MySQL cache for WordPress:
Concurrency Level: 10 Time taken for tests: 25.884421 seconds Complete requests: 100 Requests per second: 3.86 [num/sec] (mean) Time per request: 2588.442 [ms] (mean) Time per request: 258.844 [ms] (mean, across all concurrent requests) Transfer rate: 80.51 [Kbytes/sec] received
Hm, there is a small improvement for WordPress - from 3.44 to 3.86 requests per second. I expected better acceleration and the reason for a such score can be:
- empty / small database (my test database contains only 5 articles)
- small number of database queries on / per page
- tested WordPress version doesn't have any additional modules
- small number of tables in database (WordPress has only 10 tables)
- optimized database queries
MySQL query cache settings will certainly give more speed for larger databases like phpBB and Joomla.
3. PHP caching
As you know, for every HTTP request PHP engine interprets (run time compiles) requested page. Can you imagine amount of executed overhead by requesting the same PHP page? Cache engine prevents overhead and this is the key for speeding up. After first request, PHP page is compiled to opcode and stored to the cache. For every other request, PHP page is not compiled again, but pulled from cache and proceeded normally from that point. If you dig more about cache engines, you will find: testing original file for a modification, user variables, exclude from caching, garbage collector, time to live ... You can find several main PHP caching engines:
My favourite is APC. Why APC? Because I listened Rasmus Lerdorf's presentation Architecture and Performance and few chapters of presentation were related to APC. Second and more important reason is that APC will be included in core of PHP.
In RedHat Linux family, APC installation goes very simply with yum utility.
# search RPM packages yum search apc # install APC package yum install php-pecl-apc # install gd package to have graphs in APC dashboard (optional) yum install php-gd
After installation and httpd restart, APC should run immediately. This is small package and you can see all files contained in package by rpm -ql command.
[dbunic ~]$ rpm -ql php-pecl-apc /etc/php.d/apc.ini /usr/lib/php/modules/apc.so /usr/share/doc/php-pecl-apc-3.0.19 /usr/share/doc/php-pecl-apc-3.0.19/CHANGELOG /usr/share/doc/php-pecl-apc-3.0.19/INSTALL /usr/share/doc/php-pecl-apc-3.0.19/LICENSE /usr/share/doc/php-pecl-apc-3.0.19/NOTICE /usr/share/doc/php-pecl-apc-3.0.19/TECHNOTES.txt /usr/share/doc/php-pecl-apc-3.0.19/TODO /usr/share/doc/php-pecl-apc-3.0.19/apc.php /usr/share/pear/.pkgxml/APC.xml
The three main files in package are:
- apc.ini - configuration file
- apc.so - apc module loaded by PHP
- apc.php - APC monitor page
Configuration file apc.ini is located in /etc directory and default APC cache size is fitted to 32MB. To monitor APC module, you can copy apc.php file from installed directory to the document root. First you will have to define user name and password or you can disable authentication. Just open apc.php end edit credentials. Next, point browser to apc.php and you will get a nice APC dashboard. If you haven't install php-gd package, then dashboard will be without graphs. After APC installation and httpd restart, ab statistics finally looks:
Concurrency Level: 10 Time taken for tests: 11.653682 seconds Complete requests: 100 Total transferred: 2134100 bytes HTML transferred: 2114400 bytes Requests per second: 8.58 [num/sec] (mean) Time per request: 1165.368 [ms] (mean) Time per request: 116.537 [ms] (mean, across all concurrent requests) Transfer rate: 178.83 [Kbytes/sec] received
If you compare 3.44 at the beginning and 8.58 requests per second at the end, gain of about 150% is very impressive. Steps were minimal configuration of MySQL and simple APC installation - easy to follow. To get more speed from WordPress, you will have to install some WordPress cache modules like: WP Super Cache or Hyper Cache. Measured results are amazing and you can read it in my post Make WordPress fly. Anyway, the most important message of this post is to understand how to measure speed of Web application. Next step is to make it faster.