TOPメッセージ

性能テスト、性能改善に関するお話し

こんにちは、エンジニアの尾形です。

今回は、Webシステムの性能テストについて、テストの実施、改善までの流れを紹介させていただきます。

まずはじめに、システムの性能とは何でしょうか。簡単に言うと「何らかの要求に対する結果を返す力」のことです。

性能に対する考慮が不十分であったり漏れていたりすると、問題が発生する場合があります。
次のようなケースは実際に体験したり聞いたりしたことがあるのではないかと思います。

・チケットの予約サイトに受け付け開始直後にアクセスしたらつながらない
・テレビで放映されたお店のサイトに放映直後にアクセスしたらつながらない

これらの事象は、サイトへのアクセスが殺到したことによりサーバーの限界を超えてしまい、正常に処理が行えなくなったと推測できます。

システムの性能はシステム開発における重要な要素のひとつです。
性能テストって何?といった方や、性能テストを行ないたいが何をすればよいのかわからない、といったような方の参考になれば幸いです。

性能テストとは

ひとくちに性能テストと言っても、目的によってテストの内容は異なってきます。
一般的には次のような定義が多いようです。

・性能テスト/パフォーマンステスト
 システムのパフォーマンスを測定するためのテスト

・負荷テスト/ロードテスト
 ピーク時を想定した負荷をかけたときに、性能要件を達成できているか確認するためのテスト

・ストレステスト
 想定以上の負荷をかけた場合(過負荷)の挙動を確認するためのテスト

今回は、上記をまとめて広義の意味で 性能テスト と呼ぶことにします。
性能テストは一般的には”実際に動くモノ”ができあがってから、開発の最終フェーズで行われることが多いですが、性能に問題があった場合の手戻りも大きくなるため、主要機能などは早い段階でテストしておくのが望ましいです。

性能要件を定める

性能要件には次のようなものがあります。
・ある時間内にサイトに訪れるユーザーの最大数(例:1時間で1万アクセスを処理できるか)
・最大同時アクセスユーザー数(例:同時に1,000アクセスを処理できるか)
・主要なページの平均応答時間(例:3秒以内にページを表示できるか)
など

これらは開発前に要求仕様として明確に定義されているケースもあります。性能要件は性能テストを行なう上で必要となるものなので、定義されていない場合には定義するようにしましょう。

また、データ量についても考慮する必要があります。
例えば、ECサイトで商品を検索するようなシステムの場合、商品数が100件の場合と10万件の場合とでは、性能に影響を与えるであろうことは想像に難くないでしょう。
本番を想定したデータ量のもとでテストすることが重要です。

性能テストで確認すべき指標

性能テストで確認すべき指標には次のようなものがあります。

・同時接続ユーザー数
・スループット
  単位時間あたりの処理件数 (1秒間に10件処理できるのであれば、10件/秒)
・応答時間、レスポンスタイム(最近はレイテンシ(Latency)と呼ばれることも)
  処理の要求を出してから、結果が返ってくるまでの時間
・リソース使用量
 ・CPU
   CPU使用率、実行待ちプロセス数
 ・メモリ
   メモリ使用量、スワップ使用量
 ・ディスク
   ディスクビジー率、ディスク実行待ち数
 ・ネットワーク
   トラフィック量
 →これらは、UNIX系であれば次のようなコマンドで確認することができます。
  top, sar, vmstat, free, iostat, vnstat

性能を意識した設計・コーディング

実際の性能テストの話に入る前に、性能を意識した設計・コーディングについて触れておきたいと思います。

近年のサーバーはスペックも上がってきているため、CPUやメモリなどのサーバーリソースの消費について深く考えることはそれほどないかもしれませんが、サーバーリソースに負荷をかけないように意識してコーディングすることで、性能も変わってきます。

性能は作り込むことができるのです。

設計やコードレビューをしっかりと行なっていれば事前に検出できる問題もあります。次にポイントを記します。これを機に見直してみるのも良いと思います。

データベース関連
一般的に、データベースへの接続の確立作業は、サーバーの負荷となる処理です。そこで、サーバーサイドの常駐プログラムにおいては、一度接続したら接続を保持しておいて、データベースへの接続が必要となった際に、保持している接続を使用するという仕組みがあります。コネクションプールや持続的接続と呼ばれるものです。利用しない手はないでしょう。

通信関連
HTTP(S)などのコネクションについても前述のデータベースと同様の考え方を適用できます。外部のWebサービス(同一サーバーに対するもの)を短時間内で複数呼び出すような場合には、HTTP(S)のコネクションプールが有効です。コネクションプールにはKeep-Aliveの仕組みが利用されます。

メモリ関連
PHPやNode.js、Javaなどのたいていの言語にはガベージコレクションの機構が備わっています。ガベージコレクションとは、不要と判断されたメモリは自動で解放されるというものです。ですが、明示的にメモリを解放することもできます。PHPであればunset()です。大きなファイルを読み込むなどのメモリ消費の大きい処理を行なった後は、メモリを解放することでリソースを有効活用することができます。
また、メモリの確保というのは基本的に遅い処理と言われています。これはどの言語にも当てはまります。毎回生成する必要がなければ、static(静的)にして同じものを使いまわすといった対応も有効です。

ロジック関連
データベースへのクエリーや、外部のWebサービスの呼び出し回数を物理的に減らすのは非常に有効な手段です。これは当たり前のように思えますが、経験の浅いプログラマーは次のようなミスを犯しがちです。

・最初にSQLでレコードを複数件取得し、取得したレコード数分ループする中でさらにSQLを実行
 →最初に100件ヒットしたら、SQLは計101回発行される!
 →ループの中で発行するのがSELECT文なら、最初のSQLでJOINを使用することにより1回のSQLにまとめられないか?
 →ループの中では処理対象を抽出するにとどめ、ループを抜けてからIN条件にまとめて1回のSQLで実行できないか?

・ループの中で外部WebサービスのAPIを複数回実行
 →ひとつのリクエストにまとめられないか?
  リクエストをJSONでイメージすると次のようになります。
  改善前: { “id”: 1 } { “id”: 2 } { “id”: 3 } … { “id”: 100 } の計100リクエスト
  改善後: { “id”: [1, 2, 3, … , 100] } の1リクエスト

性能テストの流れ

性能テストの流れとしては、次のように徐々に負荷を上げていって確認するのが一般的です。

(1) 負荷がない状態で性能を測定する。
(2) 通常運転を想定した負荷で性能を測定する。
(3) ピーク時を想定した負荷で性能を測定する。
(4) 過負荷の状態で測定する。

負荷がない状態で性能を測定する

まずは、負荷がない状態で確認しましょう。

測定を行なうにあたり、アプリケーションのログレベルは本番と同等の設定にしておきます。開発のデバッグモードなどのままだとログ出力がある分正確な計測ができません。
また、サーバー側でリソースを消費するような余計なプロセスが動いていないことも事前に確認しておきます。

負荷がない状態でのテストは、特別なツールを使わなくても、簡単に確認できます。

ブラウザーのデベロッパーツールで応答時間を確認する
主要なブラウザーにはデベロッパーツール(開発者向けのツール)が付属しており、ページに関する様々な情報を確認することができます。
Chromeのデベロッパーツールであれば、Networkタブでリクエスト毎の処理時間や、時間がかかっている個所(ブラウザー側なのかサーバー側なのか、あるいはコンテンツのダウンロードに時間がかかっているのか)も確認できるので、問題の切り分けにも利用できます。

Webサーバーのアクセスログで確認する
WebサーバーとしてApacheを使用している場合は、httpd.conf の LogFormat に「%D」を追加することで、リクエスト毎の処理時間がマイクロ秒で出力されるようになります。

負荷をかけて性能を測定する

負荷を生み出すには、性能テスト用のツールを利用するのが手っ取り早いです。

次のようなツールが有名です。

Apache Bench
単一のURLへのリクエストを生成するツール。シナリオベースでWebシステムをテストすることには不向き。

Apache JMeter
性能測定および負荷テストを行なうためのツール。初版リリースは2001年と歴史があり、筆者自身も利用経験が長いです。
JMeterではスループットと応答時間を計測できます。また、HTTPプロキシサーバー(HTTP(S) Test Script Recorder)の機能が備わっているため、プロキシサーバーにてブラウザーでの操作を記録することができます。記録した内容(HTTPの通信内容)をテストのシナリオとして利用することできるので、TOPページ表示→ログインページ表示→マイページ表示 のようなシナリオテストを行なう場合に重宝しています。

負荷テストの準備ができたら、サーバーリソースをモニタリングしながらテストを行ないます。

以下、CentOS 6.7 におけるモニタリングのサンプルです。
各コマンドの見方などの詳細な説明は割愛させていただきます。

topコマンド
現在のシステム状況をリアルタイムで確認できます。

# top
top - 18:20:23 up  7:57,  4 users,  load average: 1.84, 0.60, 0.37
Tasks: 180 total,   5 running, 175 sleeping,   0 stopped,   0 zombie
Cpu(s):  2.7%us, 75.7%sy,  0.0%ni,  0.0%id,  0.0%wa, 21.6%hi,  0.0%si,  0.0%st
Mem:   1020164k total,   468408k used,   551756k free,    22024k buffers
Swap:  2064380k total,   256308k used,  1808072k free,    88704k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
12395 apache    20   0  336m  14m 4644 D 18.6  1.4   0:00.72 httpd
11561 apache    20   0  338m  16m 5448 R 16.0  1.7   0:05.00 httpd
11997 apache    20   0  336m  14m 5420 R 16.0  1.5   0:02.96 httpd
12391 apache    20   0  334m  13m 4596 R 16.0  1.4   0:00.70 httpd
12392 apache    20   0  336m  14m 4648 R 16.0  1.5   0:00.75 httpd
12394 apache    20   0  334m  13m 4596 D 16.0  1.4   0:00.74 httpd

sarコマンド
CPUやメモリ、ディスクI/Oなどの様々な情報を確認できます。
以下はCPU情報(-uオプション)を5秒間隔で表示しています。
それ以外にもメモリ情報の-rオプションやディスクI/O情報の-bオプションなどがあります。

# sar -u 5
Linux 2.6.32-573.12.1.el6.x86_64 (xxxxxxxxxx)     2017年10月11日  _x86_64_        (1 CPU)

18時23分30秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
18時23分35秒     all      0.40      0.00      0.40      0.00      0.00     99.20
18時23分40秒     all      1.22      0.00     10.59      0.00      0.00     88.19
18時23分45秒     all     16.94      0.00     78.72      0.00      0.00      4.34
18時23分50秒     all      3.11      0.00     96.07      0.00      0.00      0.83
18時23分55秒     all     15.88      0.00     83.71      0.00      0.00      0.41
18時24分00秒     all     16.03      0.00     82.26      0.00      0.00      1.71

vmstatコマンド
メモリやCPUの情報を確認できます。
以下は5秒間隔で表示しています。

# vmstat 5
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0 256004 588740  22508  89016   73  110   114   134  933 1319  2  4 94  0  0
 0  0 256004 583900  22508  89020    0    0     0    35 4564 7160 10 16 73  0  0
12  0 256000 569724  22516  89032    0    0     0    28 12137 13299 21 73  6  0  0
 6  0 256000 566340  22524  89040    0    0     0    20 18306 18044 13 87  1  0  0
15  0 255996 571408  22532  89052    0    0     0    14 20136 20084 10 89  1  0  0

freeコマンド
メモリの使用状況を確認できます。

# free
             total       used       free     shared    buffers     cached
Mem:       1020164     417676     602488        304      22600      89116
-/+ buffers/cache:     305960     714204
Swap:      2064380     255976    1808404

iostatコマンド
I/Oデバイスの統計情報を確認できます。
以下はsdaデバイスの情報を5秒間隔で表示しています。
avgqu-sz: 平均I/Oキュー数(高い場合は注意)
%util: デバイスの使用率(100に近いほど限界)

# iostat -x -t sda 5
Linux 2.6.32-573.12.1.el6.x86_64 (xxxxxxxxxx)     2017年10月11日  _x86_64_        (1 CPU)

2017年10月11日 18時27分52秒
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           2.24    0.12    3.95    0.20    0.00   93.49

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda              17.32    30.38    6.15    3.18   227.43   267.15    52.99     0.02    1.91   0.48   0.45

2017年10月11日 18時27分57秒
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.34    0.00   36.19    0.00    0.00   51.46

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00     2.72    0.00    1.26     0.00    30.13    24.00     0.00    2.67   2.17   0.27

2017年10月11日 18時28分02秒
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.85    0.00   80.53    0.00    0.00    0.61

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00     0.82    0.00    0.82     0.00    11.48    14.00     0.00    2.75   2.75   0.23

2017年10月11日 18時28分07秒
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           6.15    0.00   93.03    0.00    0.00    0.82

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               0.00     2.25    0.00    6.35     0.00    67.21    10.58     0.00    0.42   0.32   0.20

vnstatコマンド
ネットワークのトラフィックをモニタリングできます。
標準コマンドではないためインストールが必要です。
以下はeth1インタフェースの情報を表示しています。
rx: 受信
tx: 送信

# sudo yum install --enablerepo=epel vnstat -y
# vnstat -i eth1 -l
Monitoring eth1...    (press CTRL-C to stop)

   rx:       72 kbit/s    30 p/s          tx:      148 kbit/s    35 p/s


 eth1  /  traffic statistics

                           rx         |       tx
--------------------------------------+------------------
  bytes                      852 KiB  |       27.63 MiB
--------------------------------------+------------------
          max             732 kbit/s  |    34.60 Mbit/s
      average          179.37 kbit/s  |     5.96 Mbit/s
          min               0 kbit/s  |        0 kbit/s
--------------------------------------+------------------
  packets                      10161  |           19744
--------------------------------------+------------------
          max               1480 p/s  |        2938 p/s
      average                267 p/s  |         519 p/s
          min                  0 p/s  |           0 p/s
--------------------------------------+------------------
  time                    38 seconds

問題点の調査、改善

モニタリングしたサーバーリソースを確認します。
次のような問題が発生することがあります。

・CPU使用率が100%に張り付いてしまう
・メモリのスワップが多発している
・I/O待ちにより応答時間が劣化
など

経験上、性能問題の大半はデータベースの処理に関するものです。まず最初に疑うべきでしょう。
データベースにはたいてい備わっているスロークエリーログを使って遅いクエリーを確認できますので、INDEXが効いていないクエリーが発行されていないか確認します。
適切なINDEXを設定したり、実行計画によるSQL自体のチューニングを行なうことにより性能が改善するケースがあります。

SQLのパフォーマンスチューニングについては、弊社の別のエンジニアが記事を書いていますので、そちらも合わせて確認してみてください。

「INDEXによる高速化」は本当なのか!?PostgreSQLでパフォーマンスチューニングしてみた

次に、サーバー側プログラムのどの処理が遅いのかを確認しましょう。
手っ取り早いのは、プロファイラー(profiler)を使うことです。プロファイラーとは、性能解析ツールのことで、プログラム実行時の各種情報を収集し、処理にかかった時間などを計測できます。たいていのプログラム言語にはプロファイラーがあります。

PHPであればXhprofが有名ですが、CakePHPのようなフレームワークには標準機能やオプションとして用意されている場合が多いです。社内ではXhprofを使用しています。

以下はPHPのXhprofの簡単なサンプルです。

<?php
require_once('autoload.php');

xhprof_enable(
    XHPROF_FLAGS_NO_BUILTINS | // すべての組み込み関数 (内部関数) をスキップします。
    XHPROF_FLAGS_CPU |         // CPU のプロファイル情報を出力に含めます。
    XHPROF_FLAGS_MEMORY);      // メモリのプロファイル情報を出力に含めます。

register_shutdown_function('__xhprof_save', 'xhprof-test');

for ($i = 0; $i < 5; $i++) {
    fastEcho($i);
    slowEcho($i);
}

function fastEcho($i) {
    sleep(1);
    echo "i: $i\n";
}

function slowEcho($i) {
    sleep(2);
    echo "i: $i\n";
}

function __xhprof_save($type) {
    $data = xhprof_disable();
    $runs = new XHProfRuns_Default();
    $runs->save_run($data, $type);
}

Xhprofによるレポートは次のようになります。slowEcho()の関数に問題があることをレポートしてくれています。

このように、問題の原因を特定し、改善を繰り返すことで、性能の向上を行なっていきます。

まとめと予告

今回は、性能テストの流れについて紹介させていただきました。
改善までの話で留まりましたが、それ以外にも、限界に近づくアクセスが発生した場合に、サーバーをスケールアウトするなどの対策が必要なシステムもあるでしょう。

スケールアウトとは、サーバーの台数を増やして性能を高める方法のことです。(その逆でサーバーの台数を減らすことをスケールインという)
スケールアップとは、CPUやメモリなどのサーバースペックを増強して性能を向上させる方法。

ひと昔前はサーバーの増台などは一苦労でしたが、近年はクラウドの普及により状況は変わってきています。
AWSなどのクラウドサーバーにはこういったスケーリングの仕組みが提供されています。
たとえば、CPU使用率が75%を上回るとサービスのスケールアウトが行われ、逆に25%を下回るとスケールインが行われるといった感じです。
これらのトリガー(しきい値)は柔軟に設定できますが、このトリガーを定義するためにも性能テストは重要となります。


次回は別のエンジニアが「スタートアップ系システム開発にもとめられること~営業系エンジニアのすすめ」をご紹介させていただく予定です。乞うご期待ください!

LINEで送る
Pocket

この記事を書いた人・プロフィール
オガティー
ニックネーム: オガティー

2014年3月から現職。当社のベンチャービジネスに心惹かれて入社。
それ以前はソフトハウス(2社)に在籍。
エンジニア歴はもう少しで20年。通信キャリアーのシステム開発、サーバーサイドプログラミングの経験が長い。
エンジニアとしてのベースはJava屋。Androidアプリ開発も。サーバーサイドはPHP, Node.jsが多い。
フロントエンドエンジニアとしては駆け出し。
趣味:ギター、ドラム、フットサル、お酒