先週(1/20)に行われたNEM Bootcaptの中の「秒間4,000トランザクション記録にチャレンジ!」をチャレンジしてみた記事です。
結論としては最大瞬間風速的に4,000TPSを達成という感じです。
だいぶ右往左往していてちょっとわかりにくいかもしれませんが、
よりシンプルに、もしくはより複雑に発展させていく参考になれば幸いです。
とにかくブロックに詰め込んでみました。
ネットワーク構成
オリジナルからはちょっとアレンジをしています。
あれこれイジっていてこの構成で達成しました。
APIエンドポイントに投げつけるペイロードのスループットがイマイチだせなくて、
雑ですが単純に数を増やしていったという感じです。
入り口が複数あって、後ろのPeerノードが通信してネットワークを構築しているという状態です。
更に各ノードごとにペイロードを投げつけるJMeterを実行するインスタンスを配置して、
そこからぶつけに行きます。
スペック
アリババクラウドのECSを利用して構築してみました。
-
ecs.se1ne.2xlarge (インスタンスタイプ)
- アジア東北1(東京)
- 8 vCPU
- 64 GiB
- Intel Xeon E5-2682v4 / Intel Xeon(Skylake) Platinum 8163 2.5 GHz
- 2 Gbps1000,000 PPS
- SSD クラウドディスク
- 69.20JPY/時間
- 3台でおよそ 14,9472JPY/月(30日)
-
Catapult Service Bootstrap (Bison
2348d31
がベース)- 44uk/catapult-service-bootstrap at prevent-overwriting-configs
- 起動時に設定ファイルが上書きされないように修正
-
Apache JMeter 5.0
- ペイロードを投げつけるために使用する
このインスタンスタイプを選択したのは、とりあえず使える中で一番いいやつだったからです。
月額課金のやつやエンタープライズを申請したらもっといいのが借りられるっぽいですが
ちょっとよくわかりません。
環境構築
各インスタンスでCatapultがdocker-compose -f docker-compose-with-explorer.yml up
でノードが立ち上がるようにします。
catapult-service-bootstrap のノードを別々のサーバに立ち上げてネットワークを構築する - Qiita
こちらの記事は異なるリージョンに配置していますが、今回は同じリージョン内に配置します。
やはり異なるリージョンをインターネットでつなげると、4,000TPSは無理なんじゃないかと思います。
(がやってみる価値はあるかと)
ノードを相互に接続
各サーバでpeer-0
では7900
を、peer-1
では7910
を晒すようにdocker-compose.yml
を編集します。
peer-node-0:
ports:
- "7900:7900"
peer-node-1:
ports:
- "7910:7900"
異なるインスタンス間のノードをそれぞれ通信するように、Peerノードを追加します。
api-node-0/userconfig/resources/peers-p2p.json
peer-node-0/userconfig/resources/peers-p2p.json
peer-node-1/userconfig/resources/peers-p2p.json
"knownPeers"
に追加していきます。
{
"publicKey": "29E45877930EE3FFB87AA4D2B7FCA062716BFA57029A0EBF14C40736A4305B63",
"endpoint": {
"host": "192.168.0.143",
"port": 7900
},
"metadata": {
"name": "peer-node-2",
"roles": "Peer"
}
},
{
"publicKey": "4843C9559B1EECB8CE5C6ABBBE33B05C6542BBCC967F024B61FA114B55BDB974",
"endpoint": {
"host": "192.168.0.143",
"port": 7910
},
"metadata": {
"name": "peer-node-3",
"roles": "Peer"
}
},
{
"publicKey": "29E45877930EE3FFB87AA4D2B7FCA062716BFA57029A0EBF14C40736A4305B63",
"endpoint": {
"host": "192.168.0.144",
"port": 7900
},
"metadata": {
"name": "peer-node-4",
"roles": "Peer"
}
},
{
"publicKey": "4843C9559B1EECB8CE5C6ABBBE33B05C6542BBCC967F024B61FA114B55BDB974",
"endpoint": {
"host": "192.168.0.144",
"port": 7910
},
"metadata": {
"name": "peer-node-5",
"roles": "Peer"
}
}
プライベートIPアドレスで互いのサーバを追記していきます。
本当は別のアカウントを設定すべきかもしれませんが、手抜きで同じアカウントを使ったので、元からある設定をコピペして、IPアドレスとポートと名前だけ書き換えました。
これで各サーバにてdocker-compose -f docker-compose-with-explorer.yml up
を実行してサーバを立ち上げます。
(何度かやり直したせいでしょうか、たまに同期できなかったので、どうもおかしいと思ったら./clean-data
を実行して、一旦チェーンを削除してやりなおしてみてください)
相互に接続できていれば、エクスプローラで見たときに、各サーバで同じブロックが生成されていることが確認できます。
どのサーバでもいいので、試しにトランザクションを投げてみて、
承認されたブロックのハッシュや内容を他のサーバのエクスプローラから確認できるか確かめるとわかりやすいです。
トランザクションをぶちかます準備
最初はお手本どおり、 @planethouki さんによるトランザクションの生成スクリプトを利用させてもらっていたのですが、
- プライベートチェーンのカタパルトで秒間4000トランザクションを目指してみる - Qiita
- planethouki/yonsen: A transaction making tool for NEM2 catapult to confirm 4,000 tx/s in private network.
ちょっと問題が発生。
- 事前のトランザクションペイロード作成がおわんねぇ…(私のマシンで6,7時間かかった)
- トランザクションの送信がメモリ不足でコケてしまいトランザクションを投げきれない
ので、ここもアレンジしました。以下、下準備です。
以下で作ったファイルはこのリポジトリにコミットしてあります。
事前にペイロードを生成
ProximaXのリポジトリにGo実装のSDKがあったので、これを利用してGoで書きました。
使い方はほぼ同じで、nemesis_addresses
のprivate
(秘密鍵) を集めたjson配列から、
ランダムに送り合うトランザクションを生成します。
$ cat addresses.yaml | yq '[.nemesis_addresses[0:20][].private]' > privateKeys.json
秘密鍵のjsonはyq
コマンドでザクッと作りました。便利ですねこれ。
初期分配は先頭22件に行っているようだったので、先頭20件程度を取ってます。
catapult-service-bootstrap/template_bindings.rb at master · tech-bureau/catapult-service-bootstrap
$ time go run payloadGenerator.go -f ./privateKeys.json -n 10000 > payloads-10k.txt
とりあえず10,000万行生成。コンパイル時間を入れても爆速でした。
(ちゃんと数値で比較してないですが、大量に生成したときにn時間が秒単位になります)
これでカジュアルに数十万単位で生成できるようになったので、
やり直したり、繰り返し大量のトランザクションを用意できるようになりました。
JMeterを設定
負荷テストで鉄板のJMeterを利用することにしました。
CSV Data Set Config
を使うと、ファイルに羅列したデータを次々とリクエストとして投げられそうです。
形式は先に作ったペイロードの羅列を利用できます。区切られてないだけですからね。
リクエストボディとして、{"payload": "<ここにペイロードの内容>"}
が飛ぶように設定。
${payload}
の部分に行が流れ込んでいきます。
JMeter Serverによるクラスタを構築してやってみようとも思ったのですが、いまいちうまく動かなかったので、
愚直に3台用意してそれぞれのAPIへぶつけることに。
スクリプトの作成はGUIで行いますが、実行は後述にあるようにCLIで叩きます。
これはAWSで動かすものですが、参考までにリンクを。
これをうまく使えたらもっと楽かも。
JMeterを叩くインスタンスを作る
更にもう3つインスタンスを立ち上げて、それぞれのインスタンスのAPIサーバにトランザクションを投げ込んでいくようにしました。
ペイロードの生成をサーバ上でできるようにgolang
もインストールしてしまいました。
$ apt update && apt install openjdk-8-jre golang git
$ wget https://www-eu.apache.org/dist/jmeter/binaries/apache-jmeter-5.0.tgz
$ tar zxf apache-jmeter-5.0.tgz
$ cd apache-jmeter-5.0
このサーバへpayloadGenerator.go
もcatapultLoadTest.jmx
もアップロードしてしまいます。
catapultLoadTest.jmx
中のファイルの場所や、APIエンドポイントのIPアドレスを書き換える必要があります。
アップロード前でもサーバ上でもいいので、編集してください。(xmlなのでテキストエディタでもできます)
<stringProp name="HTTPSampler.domain">192.168.0.142</stringProp>
トランザクションをぶちかます
以下のような形でペイロードを流しまくってみます。
まずは各サーバでペイロードを生成。
$ time go run payloadGenerator.go -f ./privateKeys.json -n 480000 > payloads-480k.txt
生成できたら、各サーバ同じタイミングで流しましょう。
$ ./bin/jmeter -n -t ./catapultLoadTest.jmx
流れおわるのをしばし待ちましょう…
集計
コピペでもいいですが、めんどいのでスクリプトで取得。
$ node blocks.js http://xxx.xxx.xxx.xxx:3000 > data.csv
直近100ブロックを拾ってきてCSVな感じで標準出力にだします。
そしてそれをスプレッドシートにて貼り付けて集計しました。
ちょ〜っと(だいぶ)波がありますが、瞬間的には4,000TPSを達成していました!
まとめ
やたら複雑に、3台もインスタンスを使いましたが、1台のサーバに複数のノードがいるという状況なので、
実際の運用とはちょっと違う感じかと思います。(全体ではAPI3つ、Peer6つだけど同居している)
作業中、ちょいちょい不安定でうまく同期しなかったり、漏らすトランザクションがあったので、
若干スペック不足だったかもしれません。(が、従量課金で選べる一番いいやつがそれだった)
値段的にもこのインスタンスの利用において、計算上では15万円程度/月になってしまい、
あまり旨味が無い微妙な結果に…。もうちょっと真面目に構築したら違う結果になるかも。
受け口であるAPIサーバのスペックが低いとそもそもトランザクションを吐ききれなくて落としてしまったりするし、
APIサーバだけ強くてもFailure_Chain_Unconfirmed_Cache_Too_Full
のエラーがでていて、結局トランザクションを処理しきれないなどということがあるみたいです。
(後者はトランザクションが乗り切ればその後はうまいこと行くのだろうか?)
というわけで、何に一番苦労したかというと、
スループットがでなくてブロックに詰め込むのが間に合わないなど、
負荷をかける環境構築のほうが大変だったなというオチでした。
(JMeterには多少詳しくなれた)
JMeterからのリクエストがいまいち投げきれてない部分があったので、
テストのやりかたにボトルネックがあるっぽいです。
JMeterをもっとチューニングしてあげてください…。
まぁ、ネックだった事前トランザクション生成が秒または分程度で作り出せるようになったし、
JMeterのGUIからでもぶちかませるようになったので、こんなにたくさんノードを用意しなくとも、
またはローカル環境でもいいので、大量のトランザクションを投げ込むのを気軽に試してみてください。
追記
トランザクションを受け始めるとCPUが張り付いてLAが6くらいまであがるので
イマイチスループットが出きらないのはノード側のCPU力不足かもしれません…。
んが、今のアカウントではこれ以上いいインスタンスが借りられなかったので、
安定させるためにはもうちょっと性能の良いCPUが必要みたいです。
アリババクラウドでは6つまでしかインスタンスが持てませんでした。
多分申請すればその辺解除されていくんでしょうが、最初からもっと借りられたり、これよりも高性能なインスタンスがつかえるサービスでやったほうが手っ取り早そうです。