性能テスト(負荷テスト)と多重度の設定

テストエンジニアテスト手法品質保証QA

性能テストではレスポンスタイムやスループットが測定されますが、これらの測定値と密接に関係するのが同時接続数同時実行数(同時処理数)といった多重度の設定です。
ここでは、これらの多重度についての CentOS や MySQL(MariaDB) まわりの設定について説明したいと思います。

クライアント接続数の設定
まずはサーバがクライアントと同時にたくさん通信できるように OS の設定を行います。
サーバのネットワーク通信では、ソケットと呼ばれるファイルディスクリプタを使用します。
ファイルディスクリプタとはファイルを操作するための識別番号のことですが、ソケット通信にも利用されます。
OS 全体としてのファイルディスクリプタの上限は、以下のコマンドで確認できます。

>cat /proc/sys/fs/file-max
183096


上限値の変更は、sysctl.confを編集して行います。
通常はデフォルトで十分に大きな値が設定されているため、特に変更する必要はないかと思います。

>vim /etc/sysctl.conf
fs.file-max = 300000 # 追記(unsigned long の上限まで設定可能)


次に Web サーバのデーモンプロセスが使用できるファイルディスクリプタの上限を設定します。
/proc/{プロセスID}/limitsを参照することで確認できます。

>cat /proc/`pgrep httpd | head -1`/limits | grep 'open files' # Apache
>cat /proc/`pgrep nginx | head -1`/limits | grep 'open files' # Nginx
Max open files		1024		4096		files


ソフトリミット(現在設定されている FD 数)が 1024、ハードリミット(一般ユーザが設定できる最大 FD 数)が 4096 となっています。
ulimitコマンドでもファイルディスクリプタの上限を設定できますが、Apache の restart や マシンのリブートで元に戻ってしまうので設定を永続化する必要があります。
CentOS 7 以降、サービスの管理はsystemdで行うのが一般的です。

>vim /usr/lib/systemd/system/httpd.service.d/limits.conf # New File (Apache)
>vim /usr/lib/systemd/system/nginx.service.d/limits.conf # New File (Nginx)
[Service]			# Service設定の欄に
LimitNOFILE=65535	# 追記
LimitNPROC=65535	# 追記

>systemctl daemon-reload # systemd の設定を反映する
>systemctl restart httpd # httpd の設定を反映する (Apache)
>systemctl restart nginx # nginx の設定を反映する (Nginx)

/usr/libディレクトリ配下を触ることに気が引ける方は、/etc/systemd/systemに同様のディレクトリおよびファイルを作成しても同じことができます。

CentOS 6 以前では、rc 起動スクリプトにulimitコマンドを記述して設定を永続化させます。

>vim /etc/init.d/httpd # CentOS 6 以前
ulimit -n 65535 # 追記


なお、一部のサイトで紹介されている/etc/security/limits.confに設定する方法は、PAM 認証と無関係なデーモンプロセスには効果がありません

Apache MPM【Multi Processing Module】の設定
Apache の MPM には、prefork、worker、event の3種類のモデルがあります。
実際の設定値については、要求性能やサーバスペックに応じて適切な値を設定してください。

prefork MPM
1つのリクエストに対して1つのデーモンプロセス(httpd)が処理を行います。
最もメモリや CPU 時間を消費するモデルですが、CGI プログラムが別々のプロセスで動作するため、比較的安定した動作を期待できます。
リクエストの数に対して十分な待機プロセスが存在しない場合に応答性能がかなり落ちます。
CentOS 7 までは、Apache の設定はこちらがデフォルトでした。

>vim /etc/httpd/conf.modules.d/00-mpm.conf
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
<IfModule mpm_prefork_module>
	StartServers 50			# Apache 起動時に生成されるプロセス数
	MaxClients 1000			# 同時接続数(同時リクエスト数)
	ServerLimit 1200		# 最大プロセス数(>MaxClients)
	MinSpareServers 50		# 待機プロセスの希望最小個数
	MaxSpareServers 800		# 待機プロセスの希望最大個数
	MaxRequestsPerChild 100	# 当該リクエスト数を処理した後にプロセス再起動(メモリリーク対策)
</IfModule>


worker MPM
マルチプロセスおよびマルチスレッドでリクエストの処理を行います。
event MPM の方が優れているので、ここでは省略いたします。


event MPM
worker と同じく、マルチプロセスおよびマルチスレッドでリクエストの処理を行います。
worker では keep alive によって、次のリクエストを待つスレッドが大量に発生し、スレッド枯渇が起きるという問題がありました。
event では keep alive の設定有無に関係なく、応答後のスレッドがすぐにアイドル状態に戻ることができます。
CentOS 8 からは、Apache の設定はこちらがデフォルトになります。

>vim /etc/httpd/conf.modules.d/00-mpm.conf
LoadModule mpm_event_module modules/mod_mpm_event.so
<IfModule mpm_event_module>
	StartServers 2				# Apache 起動時に生成されるプロセス数
	ThreadPerChild 25			# Apache 起動時に生成される1プロセス当りのスレッド数
	MaxRequestWorkers 1000		# 同時接続数(同時リクエスト数)
	ThreadLimit 50				# 1プロセス当りの最大スレッド数
	MinSpareThreads 50			# 待機スレッドの希望最小個数
	MaxSpareThreads 800			# 待機スレッドの希望最大個数
	MaxConnectionsPerChild 100	# 当該リクエスト数を処理した後にプロセス再起動(メモリリーク対策)
</IfModule>



FastCGIの設定
event MPM を利用する場合、例えば PHP はそのままではマルチスレッドで動作できません(MTアンセーフ)ので、FastCGI との組み合わせになります。
ここでは、FastCGI に php-fpm を使用した場合を例にして設定値の説明をします。

>vim /etc/php-fpm.d/www.conf
[www]						# pool name
pm = static					# プロセスの制御方式 static / dynamic / ondemand
pm.start_servers = 2		# php-fpm 起動時に生成されるプロセス数
pm.max_children = 2			# 同時接続数(最大プロセス起動数)
pm.min_spare_servers = 2	# 待機プロセスの希望最小個数
pm.max_spare_servers = 5	# 待機プロセスの希望最大個数
pm.max_requests = 100		# 当該リクエスト数を処理した後にプロセス再起動(メモリリーク対策)

pm
プロセスの制御方式です。
static、dynamic、ondemand の3種類あります。

staticプロセス数が固定(= pm.max_children)。プロセス生成のオーバーヘッドがない。
ondemand       リクエストがあるとプロセスを起動し(= pm.start_servers)、処理が完了するとプロセスを終了する(メモリ効率対策)。
dynamicはじめに、pm.start_servers 数のプロセスを起動し、最大で pm.max_children 数のプロセスが常駐する。負荷に応じて pm.min_spare_serverspm.max_spare_servers 数のプロセスが待機する。

pm.max_children
起動されるプロセスの最大数です。
pm が static の場合は常にこの個数分のプロセスが常駐します。
dynamic の場合は負荷に応じてこの個数以下のプロセスが常駐します。

pm.start_servers
pm が dynamic の場合にはじめに起動されるプロセス数です。

pm.min_spare_serverspm.max_spare_servers
pm が dynamic の場合に、負荷に応じて min ~ max の範囲で常駐プロセス数が変わります。


さて、FastCGI のこれらの設定は、Apache MPM の設定内容と非常に似通っていますが、はたして同じようにすべきなのでしょうか。

ブラウザは web ページを描画するために CGI プログラムを呼び出すほか、css ファイル、JavaScript ファイル、画像といった静的コンテンツをリクエストします。
CGI プログラムは CPU を消費しますので、物理的には CPU コア数以上の並列処理はできません。
プロセス数をあまり増やしてもコンテキストスイッチのオーバーヘッドによりたいして性能が出ないことでしょう。
もちろん一般的な HDD が並列アクセスできるわけではありませんが、静的コンテンツはキャッシュの機構があったり、SSD や RAID を組むことによりマルチタスクで実行するメリットがでてきます。
したがって、pm.max_children の設定値は、CPU コア数から大きくかけ離れない数で良いかと思います。
また、CPU コア数が1~2程度である場合、pm は static にしておいても問題ないかと思います。


MySQL(MariaDB) の設定
MySQL における多重度の設定には、以下のようなパラメタが存在します。

max_connections
クライアント同時接続数。
デフォルト値は 151 です。

open_files_limit
mysqld で使用可能なファイルディスクリプタの数。
デフォルト値は 5000 ですが、max_connections や table_open_cache の値によって変化します。

それぞれ、mysqlコマンドで確認ができます。

>mysql -u root -p
mysql> show global variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+

mysql> show global variables like 'open_files_limit';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| open_files_limit | 4185  |
+------------------+-------+


max_connections を設定する場合は、MySQL の設定ファイルを編集します。

>vim /etc/my.cnf # MySQL
>vim /etc/my.cnf.d/mariadb-server.cnf # MariaDB
max_connections=65535

>systemctl restart mysqld # mysqld の設定を反映する


ファイルディスクリプタ数の上限も変更しておく必要があります。

>cat /proc/`pidof mysqld`/limits | grep 'open files'
Max open files		4185		4185		files

>vim /usr/lib/systemd/system/mysqld.service # MySQL
>vim /usr/lib/systemd/system/mariadb.service # MariaDB
[Service]			# Service設定の欄に
LimitNOFILE=65535	# 追記
LimitNPROC=65535	# 追記

>systemctl daemon-reload # systemd の設定を反映する
>systemctl restart mysqld # mysqld の設定を反映する


リクエスト送信側の設定
性能テストでは、JMeter 等のツールを利用して1台の端末からリクエストの送信を多重化し、サーバに負荷を与えます。
この際、送信用のポートが枯渇するとリクエストの送信に支障をきたすため、かけようとする負荷に十分に対応できるポートを用意する必要があります。
送信用のポートは、エフェメラルポート【Ephemeral port】と呼ばれる自由に利用可能なポートから自動で選ばれます。
エフェメラルポートの範囲は、以下のコマンドで確認できます。

>cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999


上記の場合、32768 ~ 60999 の約 28000 個のポートが使用できる設定になっています。
ポート範囲は、sysctl.confファイルに以下を追記することで変更できます。

>vim /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10240 65535 # 10240 ~ 65535
>sysctl -p
>reboot


送信負荷をかける際、ソケットのTIME_WAIT状態はかなりのボトルネックとなります。
net.ipv4.tcp_tw_reuseTIME_WAITのソケットをすぐに新しい接続に使いまわすという設定で、送信性能に大きく影響するため必ず設定しましょう。
なお、似たような設定にnet.ipv4.tcp_tw_recycleというものがありますが、こちらは危険な挙動をするため現在では廃止されています。
最後に設定を反映させるために OS をリブートします。

 

ハードウェアのリソースを効率よく使用することができず、パフォーマンスが低下してしまうという C10K 問題について、Nginx や Node.js では性能向上のために多重度を増やすという従来の考え方とは異なり、シングルプロセス・シングルスレッド・非同期 I/O といった非同期型のイベント駆動モデルへのアプローチを試みています。
このアーキテクチャは多くの C10K 問題を解決しますが、以下のデメリットを考慮して採用を決めましょう。

・プログラミングが難しく、バグを混入しやすい
・CPU リソースを多く消費する処理は早くならない

参考: Node.js のパッケージ管理ツール npm とは

コメント