WASチューニングの巻（多重度）

今回はWASのチューニング実践として、無意味なアプリケーションを作って多重度パラメタをいじってみます。
たまたまDeveloperエディションが使えるWASを対象にしていますが、どこのJavaEEサーバでも似たようなものかと思います。
某メーカー系のはやたらといろんなところにパラメタを持っていて、カスタマイズの幅が広いのはいいけれども設計する方の身にもなってくれよと常々思いますが。。。

# 前提
+ WEBサーバは経由せずWASのWebコンテナに直接アクセスする
+ WASのバージョンは9.0（Developerライセンス）
+ OSはVMWare上のCentOS Linux release 7.4.1708
+ CPUは2コア、メモリは充分に割当てる（Javaヒープも充分であるとする）
+ 負荷ツールとしてJMeterを使用
+ WASのパフォーマンスモニタリングツールとして標準提供のTPV（Tivoli Performance Viewer）を使用
+ OSのモニタリングはsarコマンドを使用
+ アプリケーションのチューニングは対象外
+ EJBは使わない（話をシンプルにしたいのとJavaEEプログラミングにあまり馴染みがない:x)）
+ DB側の最大同時接続数は充分に確保（DBサイドでは不足なし）
+ 同様にDBのリソースは余裕があるものとする

# ゴール
+ CPU使用率80%程度になる同時実行数を探す

# WASの多重度（キューイングネットワーク）
WASのキューイングアーキテクチャの概要と今回対象とするパラメタは以下のようになります。EJBがないのでシンプルです。WEBコンテナの最大同時スレッド数とDataSourceの最大接続数をチューニング対象とします。
<img width="862" alt="スクリーンショット 2018-03-03 9.15.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/beee9a17-e1e6-7335-1476-0a477235f1fd.png">
（）内の数値はデフォルト（初期値）です。

## WEBコンテナのトランスポートチェーン
具体的なトランスポートチェーン名は、「WCInboundDefault」です。
<img width="1083" alt="スクリーンショット 2018-03-03 9.05.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/c87eede2-ac9b-725a-e006-66e3c35b4661.png">
デフォルトでは20,000アクセスまでキューイングしてくれます（最大のオープン接続数）。このキューからWEBコンテナの最大スレッド数までリクエストをディスパッチすることになります。20,000もあれば充分なのでここはいじりません。タイムアウト値もデフォルトは60秒ですが、おそらく充分でしょう。ここは必要に応じて設定することにします。

## WEBコンテナスレッドプール数
WEBコンテナのスレッド数はそのまんま「WebContainer」です。
<img width="1085" alt="スクリーンショット 2018-03-03 9.08.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/e3d79740-b1f9-193c-2817-155b1a17aec2.png">

## DataSource最大接続数
DataSource最大接続数は、DataSource単位の設定になります。こちらもそのまんま「最大接続数」です。
<img width="1075" alt="スクリーンショット 2018-03-03 9.20.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/0b97f95b-0e11-db2a-b7ad-211e3cf45c79.png">

# アプリケーションの概要
テスト用のアプリケーション概要です。
<img width="798" alt="スクリーンショット 2018-03-03 10.06.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/d7ffe3b9-277a-0fe9-99ec-a4b236451f3e.png">

1. クライアントからリクエスト
2. MainServletが受けてDB参照
3. 何かCPU負荷がかかる処理を実行
4. DBを更新
5. レスポンスを返す

クラスは`MainServlet`だけ作ります。DataSourceの`lookup`はServlet初期化時にやってしまいましょう。あとはクライアントグループ単位に負荷とDBアクセス時間を変更できるように、無駄なsleepや負荷を調整するためのパラメタを随所に仕込んでおきます。

```java:MainServlet
package net.mognet.was.tuning;

import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Random;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

/**
 * Servlet implementation class MainServlet
 */
@WebServlet("/*")
public class MainServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private DataSource ds = null;
	private int searchTime = 1;
	private int interval   = 1;
	private int duration   = 1;
	private int bit        = 1024;
	private int updateTime = 1;
	
	public MainServlet() {
		super();
	}

	//サーブレット初期化でDataSourceオブジェクトを取得
	public void init(ServletConfig config) throws ServletException {
		try {
			Context ctx = new InitialContext();
			this.ds = (DataSource) ctx.lookup("jdbc/mysql");
		} catch (NamingException e) {}
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doProcess(request, response);
	}

	//主処理
	protected void doProcess(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		PrintWriter out = response.getWriter();
		response.setContentType("text/html; charset=utf-8");
		
		//負荷パラメタ取得
		try { searchTime = Integer.parseInt(request.getParameter("searchTime")); } catch(Exception e) {}
		try { interval   = Integer.parseInt(request.getParameter("interval")); } catch(Exception e) {}
		try { duration   = Integer.parseInt(request.getParameter("duration"));} catch(Exception e) {}
		try { bit        = Integer.parseInt(request.getParameter("bit"));} catch(Exception e) {}
		try { updateTime = Integer.parseInt(request.getParameter("updatTime"));} catch(Exception e) {}
		
		//総処理時間計測開始
		long startTime = System.currentTimeMillis();
		//DB参照
		doSearch(searchTime);
		//何か重たい処理
		doStress(bit, interval, duration);
		//DB更新
		doUpdate(updateTime);
		//総処理時間計測終了
		long endTime = System.currentTimeMillis();
		long etime = endTime - startTime;
		//レスポンス
		out.println("<pre>");
		out.println("searchTime: " + searchTime + "msec");
		out.println("interval: " + interval + "msec");
		out.println("duration: " + duration + "times");
		out.println("bit: " + bit + "bit");
		out.println("updateTime: " + updateTime + "msec");
		out.println("total elapsed time: " + etime + "msec");
		out.println("</pre>");
		
	}

	protected void doSearch(int searchTime) {
		doDB(searchTime);
	}

	//無駄に重たい処理
	protected void doStress(int bit, int interval, int duration) {
		BigInteger bi1 = BigInteger.probablePrime(bit, new Random());
		BigInteger bi2 = BigInteger.probablePrime(bit, new Random());
		for (int i = 1; i <= duration; i++) {
			bi1.multiply(bi2);
			try { Thread.sleep(interval); } catch (InterruptedException e) {}
		}
	}

	protected void doUpdate(int updateTime) {
		doDB(updateTime);
	}
	
	protected void doDB(int msec) {
		try {
			Connection con = ds.getConnection();
			Thread.sleep(msec);
			con.close();
		} catch (SQLException | InterruptedException e) {}
	}
}
```

DB処理は参照更新ともコネクションを取得して実際にはなにもせず、指定時間眠ってコネクションを破棄します。CPUに負荷をかけるのは`doStress()`で、無駄に２つのおそらくは素数である（`BigInteger.probablePrime()`）`BigInteger`オブジェクトを作成して掛け算します。本当になんの意味もありません。ビット数と掛け算を繰り返す回数、およびインターバルは引数で指定できるようにしておきます。呼び出し側のメイン処理では、HTTPリクエストパラメタからこれらの実行時パラメタを取得して各メソッドを呼び出します。

# 負荷グループの設計
JMeterで負荷をかける上で必要なのがシナリオの作成です。負荷テストするうえで一番大変なのはこのシナリオ作成だったりしますが（特に新規サービスでユーザの実際の動きがほとんど予測できない場合など、もうどうにでもなれと言いたくなります）、今回は話をシンプルにするため、ユーザの動きは１パターンしかありません（全リクエストDBアクセスありというとんでもないパターンです）。実際にこんなシンプルな世界だったらどんなによかったことか。。。

それはともかく、今回は以下のようなグループに分けます（JMeterのスレッドグループに相当します）。

<dl>
<dt>DBヘビーユーザ</dt><dd>DB参照１０秒、無駄な計算１回だけ、DB更新１０秒（ありえないけど）</dd>
<dt>CPUヘビーユーザ</dt><dd>DB参照２０ミリ秒、無駄な計算を５００ミリ秒おきに２回、DB更新２０ミリ秒</dd>
</dl>

まあ、こういうトランザクションミックスなんだと思っておきましょう。いずれのグループも指定時間リクエストを繰り返します（HTTPのKeep-Aliveは無効）。次回リクエストまでの待ち時間はオフセット５００ミリ秒、偏差５００ミリ秒のガウス分布タイマーを使います。

これで負荷シナリオは完成です。JMeterに実装するとこうなります。

```xml:stress.jmx
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="3.2" jmeter="3.3 r1808647">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="テスト計画" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="ユーザー定義変数" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="DBヘビーユーザ" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="ループコントローラ" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <intProp name="LoopController.loops">-1</intProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">${__P(THREAD_NUMBER)}</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <longProp name="ThreadGroup.start_time">1520044481000</longProp>
        <longProp name="ThreadGroup.end_time">1520044481000</longProp>
        <boolProp name="ThreadGroup.scheduler">true</boolProp>
        <stringProp name="ThreadGroup.duration">${__P(SUSTAIN)}</stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="リクエスト" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="ユーザー定義変数" enabled="true">
            <collectionProp name="Arguments.arguments">
              <elementProp name="searchTime" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">10000</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">searchTime</stringProp>
              </elementProp>
              <elementProp name="updateTime" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">10000</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">updateTime</stringProp>
              </elementProp>
              <elementProp name="interval" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">100</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">interval</stringProp>
              </elementProp>
              <elementProp name="duration" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">1</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">duration</stringProp>
              </elementProp>
            </collectionProp>
          </elementProp>
          <stringProp name="HTTPSampler.domain">dev.mognet.net</stringProp>
          <stringProp name="HTTPSampler.port">9080</stringProp>
          <stringProp name="HTTPSampler.protocol">http</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">/stress/hoge</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
        </HTTPSamplerProxy>
        <hashTree>
          <GaussianRandomTimer guiclass="GaussianRandomTimerGui" testclass="GaussianRandomTimer" testname="ガウス乱数タイマ" enabled="true">
            <stringProp name="ConstantTimer.delay">500</stringProp>
            <stringProp name="RandomTimer.range">500.0</stringProp>
          </GaussianRandomTimer>
          <hashTree/>
        </hashTree>
      </hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="CPUヘビーユーザ" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="ループコントローラ" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <intProp name="LoopController.loops">-1</intProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">${__P(THREAD_NUMBER)}</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <longProp name="ThreadGroup.start_time">1520044481000</longProp>
        <longProp name="ThreadGroup.end_time">1520044481000</longProp>
        <boolProp name="ThreadGroup.scheduler">true</boolProp>
        <stringProp name="ThreadGroup.duration">${__P(SUSTAIN)}</stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="リクエスト" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="ユーザー定義変数" enabled="true">
            <collectionProp name="Arguments.arguments">
              <elementProp name="searchTime" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">20</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">searchTime</stringProp>
              </elementProp>
              <elementProp name="updateTime" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">20</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">updateTime</stringProp>
              </elementProp>
              <elementProp name="interval" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">500</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">interval</stringProp>
              </elementProp>
              <elementProp name="duration" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">2</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
                <stringProp name="Argument.name">duration</stringProp>
              </elementProp>
            </collectionProp>
          </elementProp>
          <stringProp name="HTTPSampler.domain">dev.mognet.net</stringProp>
          <stringProp name="HTTPSampler.port">9080</stringProp>
          <stringProp name="HTTPSampler.protocol">http</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">/stress/hoge</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <boolProp name="HTTPSampler.BROWSER_COMPATIBLE_MULTIPART">true</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
        </HTTPSamplerProxy>
        <hashTree>
          <GaussianRandomTimer guiclass="GaussianRandomTimerGui" testclass="GaussianRandomTimer" testname="ガウス乱数タイマ" enabled="true">
            <stringProp name="ConstantTimer.delay">500</stringProp>
            <stringProp name="RandomTimer.range">500</stringProp>
          </GaussianRandomTimer>
          <hashTree/>
        </hashTree>
      </hashTree>
      <ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="結果を表で表示" enabled="true">
        <boolProp name="ResultCollector.error_logging">false</boolProp>
        <objProp>
          <name>saveConfig</name>
          <value class="SampleSaveConfiguration">
            <time>true</time>
            <latency>true</latency>
            <timestamp>true</timestamp>
            <success>true</success>
            <label>true</label>
            <code>true</code>
            <message>true</message>
            <threadName>true</threadName>
            <dataType>true</dataType>
            <encoding>false</encoding>
            <assertions>true</assertions>
            <subresults>true</subresults>
            <responseData>false</responseData>
            <samplerData>false</samplerData>
            <xml>false</xml>
            <fieldNames>true</fieldNames>
            <responseHeaders>false</responseHeaders>
            <requestHeaders>false</requestHeaders>
            <responseDataOnError>false</responseDataOnError>
            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
            <assertionsResultsToSave>0</assertionsResultsToSave>
            <bytes>true</bytes>
            <sentBytes>true</sentBytes>
            <threadCounts>true</threadCounts>
            <idleTime>true</idleTime>
            <connectTime>true</connectTime>
          </value>
        </objProp>
        <stringProp name="filename"></stringProp>
      </ResultCollector>
      <hashTree/>
      <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="結果をツリーで表示" enabled="true">
        <boolProp name="ResultCollector.error_logging">false</boolProp>
        <objProp>
          <name>saveConfig</name>
          <value class="SampleSaveConfiguration">
            <time>true</time>
            <latency>true</latency>
            <timestamp>true</timestamp>
            <success>true</success>
            <label>true</label>
            <code>true</code>
            <message>true</message>
            <threadName>true</threadName>
            <dataType>true</dataType>
            <encoding>false</encoding>
            <assertions>true</assertions>
            <subresults>true</subresults>
            <responseData>false</responseData>
            <samplerData>false</samplerData>
            <xml>false</xml>
            <fieldNames>true</fieldNames>
            <responseHeaders>false</responseHeaders>
            <requestHeaders>false</requestHeaders>
            <responseDataOnError>false</responseDataOnError>
            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
            <assertionsResultsToSave>0</assertionsResultsToSave>
            <bytes>true</bytes>
            <sentBytes>true</sentBytes>
            <threadCounts>true</threadCounts>
            <idleTime>true</idleTime>
            <connectTime>true</connectTime>
          </value>
        </objProp>
        <stringProp name="filename"></stringProp>
      </ResultCollector>
      <hashTree/>
    </hashTree>
    <WorkBench guiclass="WorkBenchGui" testclass="WorkBench" testname="ワークベンチ" enabled="true">
      <boolProp name="WorkBench.save">true</boolProp>
    </WorkBench>
    <hashTree/>
  </hashTree>
</jmeterTestPlan>
```

同時接続ユーザ数を増やしながらCPU使用率が80%くらいになる落とし所を見つける、という作業になりますので、スレッドグループのスレッド数、つまり同時接続ユーザ数の部分は変数にしておいて、コマンド実行時に引数で渡すようにしてあります（`${__P(VAR_NAME)}`）。

# モニタリング
## OSリソース使用状況
Linuxなのでsarが一番便利なんじゃないでしょうか。ファイルに出力しておいて後で平均値等を出しやすいようにしておきます。

```bash
$ sar -o sar.bin 1   # Ctrl+cで終了
$ sar -f sar.bin -s 11:00:00 -e 11:05:00 # 当該時間帯のCPU使用率
$ sar -r -f sar.bin -s 11:00:00 -e 11:05:00 # 当該時間帯のメモリ使用率
```

## WASリソース使用状況
Tivoli Performance Viewer（以下、TPV）を使用します。WAS管理コンソールに実装されています。モニタリング可能な項目はカスタマイズ可能ですが、今回は基本セットでいきます。
<img width="1088" alt="スクリーンショット 2018-03-03 12.20.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/b4762f39-14f3-5ec5-d355-ac632a67b39a.png">
TPVの画面を開いて対象サーバを選択後、「モニターの開始」を押します。
<img width="1081" alt="スクリーンショット 2018-03-03 12.28.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/67372e56-73a0-ff10-a204-bdda06245037.png">
今回見ておきたいのは、DataSourceのコネクションプール数と、WebContainerのスレッドプール数なので、２箇所チェックを入れて「モジュールの表示」を押せば、グラフと表データとしてモニタリング可能です。
<img width="1352" alt="スクリーンショット 2018-03-03 12.30.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/6679e767-a656-b6ef-30c5-da7ccf38ee9d.png">
特に注目しておくべき項目は、DataSourceの"WaitingThreadCount"と"WaitTime"です。今回はDB側リソースが充分という前提なので、基本的にJDBCコネクションの待ちが発生しないようにします。なので、この２項目は常に「０」を目指します。
「ロギング開始」を押すと、レコードをファイルに記録して後から再生できるようにしてくれますが、かなりファイルサイズが大きくなるので、注意が必要です。「設定」からサイズ指定可能ですが、基本セットを取っているだけでも３０分くらいで５ GBほどになります。その後圧縮されるのでだいぶ小さくなりますが、とにかくいつの間にかロギング止まっているという状況になりがちです。基本的にはその場で確認するためのツールという印象ですね。

## レスポンス・スループット
負荷ツールにJMeterを使うので、JMeterが出力するレポートを見るのが手軽です。グラフ化してくれたりもするのでありがたい存在です。実際に負荷をかけるコマンドは以下のようになります。

```bash
$ jmeter -n -t stress.jmx -JTHREAD_NUMBER=1 -JSUSTAIN=300 -l shot1.jtl -e -o ./shot1
```

`-J`でテスト計画内で参照している変数を定義します。`-o`でレポート（HTML形式）出力先ディレクトリを指定します。
なお、分散環境（JMeterサーバ）を使用して負荷をかける場合は以下のようになります。

```bash
$ jmeter -n -t stress.jmx -r -GTHREAD_NUMBER=1 -GSUSTAIN=300 -l shot1.jtl -e -o ./shot1
```

JMeterサーバを複数使用する場合、`jmeter.properties`に`remote_hosts`を定義しておきます。

```properties:jmeter.properties（抜粋）
remote_hosts=10.1.1.50:1099,10.1.1.51:1099,10.1.1.52:1099,10.1.1.53:1099
```

面白いというか注意しなければいけないのが、ここで`THREAD_NUMBER`として指定した値が、テスト計画中のスレッドグループのスレッド数として使用されますが、リモート実行の場合、全サーバが同じシナリオを実行するので、サーバ台数分の同時アクセス数になるという点でしょうか。

# 結果
やる前から分かりきっていることですが、このアプリケーション（笑）はWebコンテナのスレッド：DB接続が１：１です。初期設定で５０：１０ですから同時アクセスユーザ数が１０を超えると、DBアクセス待ちが発生します。
<img width="1352" alt="スクリーンショット 2018-03-03 13.43.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/540a85d3-8790-00c7-9e56-4e27d16b6793.png">
ユーザ数２５（WebContanerのActiveCountが２５）のときに、DBコネクション取得待ちのスレッドが１４、時間にして平均で約２．５秒待たされていることがわかります。
というわけで、DataSourceのプール数も５０に設定します。
<img width="1084" alt="スクリーンショット 2018-03-03 13.45.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/6dc56473-c3a9-7e30-1417-0d63a7bb2d29.png">
これで接続数を少しずつ増やしながらCPU使用率を調べると以下のような結果となりました。
<img width="921" alt="スクリーンショット 2018-03-03 17.36.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/2bf21e54-efef-4907-5694-6756bbdc42c7.png">
グラフから同時接続数４０程度が限界（CPU使用率80%未満に収まる範囲）であることがわかります（X軸が同時アクセスユーザ数）。
一般的にCPU使用率100%までスループットはリニアに増加するものの、その後は徐々に増加率が低減し、さらに同時アクセス数が増えるとスループットが落ちる（飽和点）という特徴があります。一方で、CPU使用率が80%を超えると、システムの動作が不安定になるという神話のような言い伝えがありますので、大抵のケースではここをポイントにして流量制御することがおおいかと思われます。

# 流量制御
というわけで、多重度を以下のように４０までにして、同時接続数を６０とした場合にどうなるかを見てみます。
<img width="1085" alt="スクリーンショット 2018-03-03 18.27.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/1f4777ed-06cc-e4e3-c5d8-6379480a64e7.png">
<img width="1087" alt="スクリーンショット 2018-03-03 18.28.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/6dd4e524-972d-b01e-febb-5001aaa48549.png">
なお、WASのWebコンテナスレッド数には「最大スレッド・サイズを超えたスレッド割り振りを許可」という恐ろしいオプションが存在します。負荷に応じてよろしくやってくれるのかもしれませんが、ちょっとおっかないですね。。。ここではOFFにしてありますので、Webコンテナが捌き切れないリクエストはTCPインバウンドチャネルにキューイングされるはずです（TPVで見えればいいんですが残念ながら当該項目は見つけられませんでした）。

<img width="1351" alt="スクリーンショット 2018-03-03 18.45.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/005f9ad4-bad5-e4ec-a5c9-1072e7c519e1.png">
Webコンテナのスレッド、DataSourceの接続プール（ともに４０）を使い切っていますが、JDBCコネクションの取得待ちをしているスレッドはありません。想定通りの結果となりました。最初の図を使えば、インバウンドチャネルのところで２０ユーザが待機させられていたということになります。なお、この間のCPU使用率は72.53%でした。
<img width="836" alt="スクリーンショット 2018-03-03 18.49.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/c52f9523-2b15-a1a1-b9a2-e99186ee992b.png">

というわけで、TPVの使い方をまとめておきました、というお話でした。

# おまけ（WASのインバウンドキューを少なくすると）
こうなると、じゃあこのインバウンドチャネルにあるキューがなかったらどうなるのか気になるところです。この仕組みを備えた上で、さらに`int listen(int sockfd, int backlog)`でリッスンバックログを確保するとも思えないので、おそらくクライアントから見るとソケットエラーになるのではと推測されます。とりあえず４０にしてみます。
<img width="1577" alt="スクリーンショット 2018-03-03 21.25.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/a1ad3cc0-cf71-5709-8f5a-2564922cc428.png">
同じく６０多重アクセスを５分間で、約７割がエラーになりました（Non HTTP）。
TCPレイヤのバックログ（"/proc/sys/net/ipv4/tcp_max_syn_backlog = 512"）もありますがこれをあてにするわけにもいきませんね。
さらに、オンプレ環境だとこういった設定を施すのがよくあるデザインパターンでしたが、クラウドでAWSのAuto-Scalingを使ったりするなら、このような流量制御はしないほうがいいですね。もう少し多重度をあげて、飽和点（CPU使用率が100%を超えてさらにスループットが少しずつ増加していってついに低下し始めるポイント）とまでいかなくとも、CPU使用率100%までの前提にしてもいいかもしれません。
で、CPU使用率（ロードアベレージ）が80%を超えたらスケールアウトすると。そう考えれば、余計なことはせずに目一杯開けておくというのも手かもしれません。その方が楽だし。。。