WordPress の負荷テストと環境改善

「サイボウズ・アドベントカレンダー2012」も、残すは今日と明日のみです(これまでの記事一覧)。


こんにちは、社内インフラ担当の稲生です。今回はWordPressサーバの簡単な改善方法と負荷テストによる比較のお話です。
一般の商用サイトでも利用可能な「WordPress」。サイボウズのWebサイトでも CMS で使ったり、有料 Blog サービスから移行したりと、その恩恵に与っています。そんな WordPress 環境ですが、最近サーバへの負荷が高い? という連絡を受け、調査がてら負荷テストと環境改善に取り組んだ時のことを紹介します。

※ 因みにサーバ高負荷の原因は WordPress と全然関係ありませんでした。詳細は本稿の主旨と異なるため省きますが、怪我の功名ということで。WordPress は良い子ですよ。

テスト環境を用意する

テスト用に以下の仮想環境を用意しました。

CPU Xeon E5640 x4
MEM 4 GB
HDD 40 GB
OS CentOS 6.3 x64
Apache 2.2.15
PHP 5.3.3
MySQL 5.1.66

ここでは PHP には remi パッケージ を利用しています。後述しますが、 php-fpm のパッケージが標準リポジトリに無いためです。 WordPress のインストール方法は WordPress Codex 日本語版 に記載の通りの手順です。このうち MySQL については環境に合った my.ini 設定を流用し、初期化後 InnoDB に変更します。 my.ini の設定サンプルは /usr/share/doc/mysql-server-5.5.28/ 以下に幾つかありますが、ここでは my-huge.cnf を /etc/my.ini としてコピーし、InnoDB 関連の設定を有効にして用います。初期化後の InnoDB 変換は、以下の要領で全ての table に対し実施します。

mysql -u root -p wordpress
    Password:
    mysql> SHOW TABLES;
    +-----------------------+
    | Tables_in_tech        |
    +-----------------------+
    | wp_commentmeta        | 
    | wp_comments           | 
    | wp_links              | 
    ...
    mysql> ALTER TABLE wp_commentmeta ENGINE = InnoDB;

変換後の確認は以下のコマンドで確認できます。

mysql> SHOW TABLE STATUS;

負荷テストツールを利用してみる

負荷テストツールは、Web負荷テストで検索 すれば色々と見付かります。どれを利用するかについては、当然ながらテストケースに応じて選択すべきですが、本稿では Apache Bench を使って素早くお手軽なテストを実施してみます。このコマンドは、CentOSでは httpd-tools パッケージに含まれています。
使い方の簡単な説明は以下の通り。

ab [ -n requests ] [ -c concurrency ] [http[s]://]hostname[:port]/path
-n: 総リクエスト数 -c: 同時リクエスト数

ab は「指定したリクエスト数に達するまでの処理時間の計測」を行なうテストツールです。特定の URL に対してひたすら同時アクセスを実施する単純なものですが、Web サイトへ負荷をかける目的なら十分です。但し、サーバの負荷状況を確認するにはこれだけでは役者不足。サーバの負荷を見るなら、LoadAverage や Swap の情報くらいは見ておきたいところ。
というわけで、総リクエスト数を増やしながらサーバの情報を収集する bash スクリプトを簡単に書きました。

#!/bin/bash

C_REQ=50  ## concurrency
SVR="wpserver"
URL="http://wordpress.cybozu.co.jp/"

for req in `seq 50 50 1000`; do
    flg=1
    cnt=0

    while [ ${flg} -eq 1 ]; do
        if [ ${cnt} -ge 5 ]; then
            echo "Over 5 continued." 2>&1
            exit 1
        fi

        logfile="`printf %04d ${req}`_${C_REQ}.log"
        ssh ${SVR} w > ${logfile}
        ssh ${SVR} free >> ${logfile}

        ab -n ${req} -c ${C_REQ} ${URL} >> ${logfile}

        ssh ${SVR} w >> ${logfile}
        ssh ${SVR} free >> ${logfile}

        grep "Failed requests:        0" ${logfile}
        if [ $? -eq 0 ]; then
            flg=0
        fi

        sleep 90  ## Interval
        cnt=$(($cnt+1))
    done
done

exit 0

本格的な検査ではないので、ab に設定するオプション値はテキトウです。している事は簡単で、総リクエスト数を50〜1000で50ずつ増やしながら ab を実行、その前後で ssh 越しに w コマンドと free コマンドでリソース情報を収集しています。収集したログファイル名は、例えば "0050_50.log" のような感じ。負荷の掛け方によっては途中でリクエスト処理に失敗する事があります。スクリプトでは逐次失敗を確認し、最大5回まで再試行し、それでも駄目なら終了します。

テスト結果から欲しい情報を抽出する

上記スクリプトで収集した結果のサンプルです。単純に「w」「free」「ab」「w」「free」の順で実行した結果の羅列になります。

09:49:15 up 13:45,  1 user,  load average: 0.79, 9.06, 13.03
USER     TTY      FROM            LOGIN@   IDLE   JCPU   PCPU WHAT
hoge     pts/0    10.X.XXX.XX      Wed20   13.00s  0.06s  0.02s sshd: hoge [pri
             total       used       free     shared    buffers     cached
Mem:       3924804     755980    3168824          0     132176     104112
-/+ buffers/cache:     519692    3405112
Swap:      2047992          0    2047992
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking wordpress.cybozu.co.jp (be patient).....done

Server Software:        Apache
Server Hostname:        wordpress.cybozu.co.jp
Server Port:            80

Document Path:          /
Document Length:        55719 bytes

Concurrency Level:      50
Time taken for tests:   7.349 seconds
Complete requests:      50
Failed requests:        0
Write errors:           0
Total transferred:      2796600 bytes
HTML transferred:       2785950 bytes
Requests per second:    6.80 [#/sec] (mean)
Time per request:       7348.846 [ms] (mean)
Time per request:       146.977 [ms] (mean, across all concurrent requests)
Transfer rate:          371.63 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        4    6   0.9      6       7
Processing:  1344 5434 2042.5   6396    7342
Waiting:      967 4710 2050.5   5628    7153
Total:       1348 5440 2043.3   6402    7348

Percentage of the requests served within a certain time (ms)
  50%   6402
  66%   7097
  75%   7117
  80%   7165
  90%   7314
  95%   7338
  98%   7348
  99%   7348
 100%   7348 (longest request)

09:49:22 up 13:45,  1 user,  load average: 3.32, 9.33, 13.07
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
hoge     pts/0    10.X.XXX.XX      Wed20   20.00s  0.06s  0.02s sshd: hoge [pri
             total       used       free     shared    buffers     cached
Mem:       3924804    1690408    2234396          0     132260     104132
-/+ buffers/cache:    1454016    2470788
Swap:      2047992          0    2047992

出力結果の詳細な説明は各種解説サイトをご参照頂く事として、ここから負荷状況を見るために、以下の情報を抽出します。

  • リクエスト数
  • 計測時間(s)
  • 秒間処理数(req/s)
  • 平均処理時間(ms/req)
  • Load Average(1m)
  • Swap (Byte)

そしてこちらが簡単に書いてみた情報抽出&CSV化スクリプト。

# !/bin/bash

C_REQ=50 ## 同時リクエスト数
URL="http://wordpress.cybozu.co.jp/"

IFS="
"

dir="`dirname ${1}`/`basename ${1}`"
if [ ! -d ${dir} ]; then
    echo "No such directory." 2&>1 exit 1
fi

for req in `seq 50 50 1000`; do
    logfile="`printf %04d ${req}`\_${C\_REQ}.log"
    grep "Failed requests:        0" ${dir}/${logfile} 2>&1 >/dev/null
    if [ $? -ne 0 ]; then
        break
    fi

    ## Get time(s)
    dat=(`grep '[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}' ${dir}/${logfile} | awk '{print $1;}'`)
    pre=(`echo ${dat[0]} | awk -F : '{printf("%d\n%d\n%d", $1, $2, $3);}'`)
    post=(`echo ${dat[1]} | awk -F : '{printf("%d\n%d\n%d", $1, $2, $3);}'`)

    time=`echo "(${post[0]}*60*60)+(${post[1]}*60)+(${post[2]})-(${pre[0]}*60*60)-(${pre[1]}*60)-(${pre[2]})" | bc`

    ## Get LoadAverage(1m)
    dat=(`grep 'load average:' ${dir}/${logfile} | sed -e 's/.*\(load average:.*\)/\1/'`)
    pre="`echo ${dat[0]} | awk '{print substr($3,0,length($3));}'`"
    post="`echo ${dat[1]} | awk '{print substr($3,0,length($3));}'`"
    load_average=`echo "${post} - ${pre}" | bc`

    ## Get Req/s
    req_per_s=`grep 'Requests per second:' ${dir}/${logfile} | awk '{print $4;}'`

    ## Get Req time
    ms_per_req=`grep 'Time per request:.*(mean)' ${dir}/${logfile} | awk '{print $4;}'`

    ## Get Swap
    mem=(`grep '\-/+ buffers/cache' ${dir}/${logfile} | awk '{print $3;}'`)
    swp=(`grep 'Swap:' ${dir}/${logfile} | awk '{print $3;}'`)
    swapping=`echo "(${mem[1]}-${mem[0]})+(${swp[1]}-${swp[0]})" | bc`

    echo "${req}","${time}","${req_per_s}","${ms_per_req}","${load_average}","${swapping}"

done

exit 0

「計測時間(s)」は ab 実行前後の w コマンドの時刻を秒数に換算した差分です。「Load Average(1m)」「Swap(Byte)」も、ab 実行前後の値の差分で出しています。得られた結果は、Excel でグラフ化すると判り易くなります。以下は素の設定での計測結果です。

グラフサンプル

計測時間の増加、Load Averageの増加具合を見る限り、それっぽい情報が得られています。同時接続数は50固定なので、Swapもこんなものでしょう。

環境を変えてテストしてみる

負荷テストの方針が決まったら、今度は環境改善を図ります。本稿では環境改善に効果のあるツールの導入有無でテスト結果の比較をしました。
導入したツールは以下の通りです。

  • APC
  • FastCGI
  • NGiNX+php-fpm

APC

PHPの高速化には「Alternative PHP Cache」というのがあります。こちらはPHPの中間コードのキャッシュや最適化をしてくれるもので、サーバに仕込むだけでスクリプトの変更も必要なく高速化されるスグレモノです。
CentOSでの設定手順は以下の通り。

# yum install pcre-devel
# pear install pecl/APC
...
# vi /etc/php.d/apc.ini

----------
[apc]
extension=apc.so
----------

# service httpd restart

導入確認は以下の通り。

# php -i | grep apc
...
apc.enabled => On => On
...

一時的にAPCを外したい場合は、/etc/php.d/apc.ini を何処かに退避して Apache 再起動でOK。

FastCGI

CGI はリクエスト毎にプロセスの生成と破棄が行なわれますが、あらかじめメモリ上で待ち受けて、このオーバーヘッドを軽減する仕組みが FastCGI です。
CentOSでの設定手順は以下の通り。EPELパッケージ を利用できるよう、yumリポジトリを追加しておいてください。

# yum install --enablerepo=epel fcgi mod_fcgid
# vi /etc/httpd/conf.d/fcgid.conf

----------
AddType text/html .php
DirectoryIndex index.php

FcgidMaxRequestLen 134217728
----------

# mv /etc/httpd/conf.d/php.conf /etc/httpd/conf.d/php.conf.disable
# vi /etc/httpd/conf/httpd.conf

----------
...
<VirtualHost *:80>
    ...
    <Directory "/var/www/html/wordpress.cybozu.co.jp">
        <IfModule mod_fcgid.c>
            Options FollowSymLinks Includes ExecCGI
            AddHandler fcgid-script .php
            FCGIWrapper /usr/bin/php-cgi .php
        </IfModule>
        ...
    </Directory>
</VirtualHost>
----------

# service httpd restart

導入確認は以下の通り。

# ps -ef | grep php
apache   19643 19339  3 19:03 ?        00:01:30 /usr/bin/php-cgi
...

一時的に FastCGI を外したい場合は fcgid.conf を fcgid.conf.disable などに改名して Apache 再起動でOK。

NGiNX + php-fpm

少し毛色を変えまして、Web サーバを Apache から NGiNX に変更してみました。こちらは Apache よりも軽量かつ必要十分な機能を揃えた Web サーバで、WordPress の動作事例も豊富です。
CentOSでの設定手順は以下の通り。

#vi /etc/yum.repos.d/

----------
[nginx]
name=nginx
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=0
----------

# vi install --enablerepo=nginx

これだけではPHPが動作しないので、バックエンドCGIとしてremiパッケージから php-fpm をインストールします。

# yum install --enablerepo=remi php-fpm

php-fpm は良しとして、NGiNX は WordPress 向けに設定を行なう必要があります。こちらは長くなりますので、詳しくは WordPress Codex 日本語版 をご参照ください。/etc/nginx/conf.d/ 以下に restrictions.conf および wordpress.conf を保存して、/etc/nginx/sites-enabled/ 以下にサイトドメイン毎の conf ファイルを用意、その server ディレクティブ内で /etc/nginx/conf.d/ 以下の conf ファイルを読み込むようにします。

最後にサービスを起動します。もし Apache が動いている場合は、必ず停止してください。

# service php-fpm start
# service nginx start

また、こちらの環境でも APC が利用可能です。前述同様 /etc/php.d/apc.ini の有無で設定し、反映後は php-fpm サービスの再起動を行ないます。

テスト結果

Apache 環境に APC を導入した場合と、FastCGI を導入した場合での比較をしてみました。

APC、FastCGI 比較

APC導入により、Load Averageと処理時間が下がっています。リクエストの平均処理時間など、1000リクエストでの結果を見ると2秒近く差が出ました。PHPの中間コードキャッシュは高速化と負荷軽減の両方に有効だと解ります。 FastCGI 導入でも、処理速度の改善に若干の効果があるようです。Swapが下がっているのが少し不思議ですが、テスト内容が同一リクエストの繰り返しである事と、メモリ共有が効いているのかも知れません。

次に Apache 環境と NGiNX 環境との比較をしてみました。

Apache、NGiNX 比較

NGiNX 環境は +php-fpm なので、似た環境として Apache + FastCGI 環境と比較しています(厳密には異なります)。ついでに APC の有無でも比較してみました。Swap を別にすると、結果としてはあまり差が無いような……。ここから先は設定のチューニングが必要になりそうです。

最後に

本稿では、WordPress の環境改善にあたり、その性能差を簡単な負荷テストにて比較しました。ここに挙げた以外にも設定全体のチューニング、ETag やテキスト圧縮などの通信量削減、WordPress プラグインでのキャッシュ設定など改善や高速化はまだまだ沢山ありますが、長くなりますので割愛。

本稿のテスト方法では実際のアクセスを想定した試験や、同時アクセスの限界値を図る事は出来ません。ですが、あるツールや設定に対し「効果があるかどうか」検証する程度には有用で、取捨選択の指針とするには十分です。
煩雑な設定は取り敢えず、導入するだけでも効果のあるツールは多々あります。簡単で効果の高いものをお手軽な方法で検証し取捨選択するのが、サーバ環境改善の効率的な方法だと思います。


明日は「サイボウズ Office on cybozu.com でシンタックスハイライトを実現する方法」をお送りします。お楽しみに。