mijinを安定稼働するための指標を探るべく負荷テストを実施してみた

# 目標

現状のmijin Catapult F5でどの程度の性能があるか検証する。
テストは定番のJmeterを使用してmijinに負荷テストを実施する。
安定してmijinが稼働できる指標を目指す。

※ 瞬間的な最大トランザクション承認数の調査ではない
(1ブロック6000txが捌けることは確認しているため)

## 構成

* Jmeterネットワーク
* mijinネットワーク

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135107/5e63f4fb-1257-f125-537a-e0171aec4d93.png)

## mijin構成

ネットワーク及びインスタンスなどの基盤構築はCloudformationで行い、mijinはansibleで構築

* ALB(ELB) x 1(SSL化や入れ替え用)
* APIノード  x 1
* PEERノード x 2

### APIノード

mongoがある分、PEERノードよりメモリを多くしている

|項目|値|
|---|---|
|スペック|m5.large(2CPU 8GB)|
|mongo disk|EBS disk(io1 1000IOPS)|
|block disk|EBS disk(io1 300IOPS)|

### PEERノード

|項目|値|
|---|---|
|スペック|c5.large(2CPU 4GB)|
|block disk|EBS disk(io1 300IOPS)|

## mijin設定

主な変更点のみ

### rest.json

Throttlingの設定をし、上限を超えない程度に制限する。

```json
  "throttling": {
    "burst": 120,
    "rate": 100
  },
```

Defaultだと設定がなく `5` で制限されている。
制限を緩和しないと以下のようなエラーがでてしまう。
`0` にすると無制限にできるが、mijin側でエラーが多発するためおすすめしない。

```json

{"statusCode":429,"errorDetails":{"statusCode":429,"statusMessage":"Too Many Requests"},"body":{"code":"TooManyRequests","message":"You have exceeded your request rate of 5 r/s."}}
```

### config-network.properties

5秒でブロック生成する。(Default 15s)
この値はnemsis block作成時から変更は不可なので変更する場合は、最初からmijinを作り直す必要あり

```
blockGenerationTargetTime = 5s
```

### docker-composeのmongo設定

mongoはメモリを多く消費するため、wiredTigerCacheSizeGBにて制限をかけておく


```yml
services:
  db:
    image: mongo:4.2.5
    command: bash -c "mongod --dbpath=/dbdata --bind_ip=db --wiredTigerCacheSizeGB 3.0"
```

## テスト内容

事前に用意したMosaicをランダムで作成したアドレスにTransferTransactionで送信する。
抜粋すると以下のような方法で作成

```js

    // 適用なデータを作成
    const hash = CryptoJS.SHA512(Crypto.randomBytes(5).toString());
    const hashConvert = Convert.hexToUint8(hash.toString(CryptoJS.enc.Hex));
    // 署名
    const hashSig = KeyPair.sign(keyPair, hashConvert);
    // プライベートキーを作成
    const toPrivateKey = Convert.uint8ToHex(hashSig.slice(0, 32));

    const toAccount = Account.createFromPrivateKey(toPrivateKey, networktype);

    // トランザクションを作成するための内容を作る
    const transferTransaction = TransferTransaction.create(
      Deadline.create(23), // 23時間
      toAccount.address,
      [new Mosaic( new MosaicId(mosaicId), UInt64.fromUint(1))],
      PlainMessage.create('Stees Test Count' + count),
      networktype
    ).setMaxFee(100);

    // 作成したトランザクションに署名する
    const signedTransaction = account.sign(transferTransaction, ghash);

　　// signedTransaction.payload をjmeterで使用する
```


## テスト実行方法

Jmeter Masterからjmxファイルを実行し、jmeter Slaveをリモート実行する。
構成では記載していないが、トランザクションデータはpayloadに変換し、redisに格納されている。
(redisデータを毎回取りに行くオーバヘッドはあるが、いちいちトランザクションファイルを作成すると時間がかかるのでBeanShellでredisから値を取得するようにして無限ループできるようにしている)

```shell

$ /opt/apache-jmeter-5.2.1/bin/jmeter -n -t ./jmeter_stress_test_redis.jmx -r \
  -Gthrednum=1 \　スレッド数1
  -Grampup=1 \
  -Gredis_host=redis.mijin.internal \
  -Gredis_key=transfer \
  -Gredis_cmd=/usr/bin/redis-cli \
  -Gloop=-1  // 無限ループ
```

## 実行結果

３時間で200万トランザクションを投げ、25%ほどエラーになった。
このエラーは、上記でも記載したとおり、 `throttling` の制限によりトランザクションを受け付けれなかったエラーとなる。

```
summary = 2003403 in 03:01:09 =  184.3/s Avg:     2 Min:     0 Max:   473 Err: 517193 (25.82%)
```

```
infra@master-srv:~$ /opt/apache-jmeter-5.2.1/bin/jmeter -n  -t /home/infra/jmeter_stress_test_redis.jmx -r  -l test/log_${DATE}.csv  -Gthrednum=1  -Grampup=1  -Gredis_host=redis.mijin.internal  -Gredis_key=transfer  -Gredis_cmd=/usr/bin/redis-cli  -Gloop=-1
Creating summariser <summary>
Created the tree successfully using /home/infra/jmeter_stress_test_redis.jmx
Configuring remote engine: 172.17.2.212:1099
Configuring remote engine: 172.17.2.70:1099
Starting distributed test with remote engines: [172.17.2.70:1099, 172.17.2.212:1099] @ Tue Apr 14 12:10:19 JST 2020 (1586833819480)
Warning: Nashorn engine is planned to be removed from a future JDK release
Remote engines have been started:[172.17.2.70:1099, 172.17.2.212:1099]
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary +   1003 in 00:00:09 =  109.9/s Avg:     4 Min:     1 Max:   180 Err:     0 (0.00%) Active: 2 Started: 2 Finished: 0
summary +   5400 in 00:00:30 =  177.8/s Avg:     3 Min:     0 Max:    25 Err:    58 (1.07%) Active: 2 Started: 2 Finished: 0
summary =   6403 in 00:00:40 =  162.1/s Avg:     3 Min:     0 Max:   180 Err:    58 (0.91%)
summary +   5100 in 00:00:30 =  170.6/s Avg:     3 Min:     0 Max:    65 Err:   284 (5.57%) Active: 2 Started: 2 Finished: 0
summary =  11503 in 00:01:09 =  165.8/s Avg:     3 Min:     0 Max:   180 Err:   342 (2.97%)
~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~
summary +   5500 in 00:00:30 =  183.7/s Avg:     3 Min:     0 Max:    22 Err:   589 (10.71%) Active: 2 Started: 2 Finished: 0
summary = 1976803 in 02:58:39 =  184.4/s Avg:     2 Min:     0 Max:   473 Err: 510585 (25.83%)
summary +   5400 in 00:00:31 =  176.4/s Avg:     3 Min:     0 Max:    70 Err:   529 (9.80%) Active: 2 Started: 2 Finished: 0
summary = 1982203 in 02:59:10 =  184.4/s Avg:     2 Min:     0 Max:   473 Err: 511114 (25.79%)
summary +   5900 in 00:00:30 =  199.6/s Avg:     2 Min:     0 Max:    45 Err:  1353 (22.93%) Active: 2 Started: 2 Finished: 0
summary = 1988103 in 02:59:39 =  184.4/s Avg:     2 Min:     0 Max:   473 Err: 512467 (25.78%)
summary +   5900 in 00:00:30 =  197.0/s Avg:     1 Min:     0 Max:    91 Err:  2057 (34.86%) Active: 2 Started: 2 Finished: 0
summary = 1994003 in 03:00:09 =  184.5/s Avg:     2 Min:     0 Max:   473 Err: 514524 (25.80%)
summary +   5100 in 00:00:30 =  170.8/s Avg:     3 Min:     0 Max:    22 Err:  1417 (27.78%) Active: 2 Started: 2 Finished: 0
summary = 1999103 in 03:00:39 =  184.4/s Avg:     2 Min:     0 Max:   473 Err: 515941 (25.81%)
summary +   4300 in 00:00:30 =  141.5/s Avg:     5 Min:     5 Max:    69 Err:  1252 (29.12%) Active: 2 Started: 2 Finished: 0
summary = 2003403 in 03:01:09 =  184.3/s Avg:     2 Min:     0 Max:   473 Err: 517193 (25.82%)
```

## 負荷の状況

負荷の状況はCloudwatchにてモニタリングしている。
CPU使用率も安定し、ディスクのIOPSも制限以下で遷移した。

Dropping Transaction Countは `catapult_serverxxxx.log` をCloudwatch Logsに送信し、メトリクスフィルタでdropしたトランザクションを集計している。
Reject Error Countは `catapult_brokerxxxx.log` をCloudwatch Logsに送信し、メトリクスフィルタでbrokerがrejectしたトランザクションを集計している。
上記２つのカウントが上昇した場合、ほぼトランザクションが滞留し、ブロック生成に遅延が発生及びトランザクションが破棄されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135107/fc74bfab-4d58-b29c-7bd6-3fbfd6b74985.png)


## 考察

結果を考えると、今回のスペックでは 大体 `136tx/s` を連続してトランザクションを発行しても安定して稼働できると考える。(少なからず３時間は)
ただ、長時間続くとキャッシュがたまり、 `Failure_Chain_Unconfirmed_Cache_Too_Full` がでて、トランザクションがrejectされる可能性もあるので、今回程度の最低推奨スペック程度ではなく、もう少しスペックを上げて調査したい。

```
summary = 2003403 in 03:01:09 =  184.3/s Avg:     2 Min:     0 Max:   473 Err: 517193 (25.82%)

テスト数 2,003,403トランザクション
負荷時間 03:01:09
平均処理 184tx/s
最小レスポンス時間 2 (ms)
最大レスポンス時間 473 (ms)
エラーテスト数  517193 (25.82%
—————————————————————
成功平均処理 184 * 0.7418 ≒ 136tx/s(TPS)
```