情報畑でつかまえてロゴ
本サイトは NTTテクノクロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

Hyperledger CaliperでBESUの性能測定

Hyperledger Caliperの使い方について解説をしていきます。

はじめに

  • NTTテクノクロスの深津颯騎です。
  • Hyperledgerプロジェクトの一種であるHyperledger Caliper(以下Caliper)を使って、Hyperledger BESU(以下BESU)の性能測定を実施しました。
  • 検証に使用したソースコードはこちらのGithubリポジトリで公開しています。

Caliperとは

  • Hyperledgerコミュニティが提供しているブロックチェーンの性能測定ツールです。
  • Ethereum, BESU, Fabric, FISCO BCOSを対象に、スマートコントラクトに対するread/writeのリクエストに対して、下記の項目を測定することができます。
    • ・スループット
    • ・レイテンシー
    • ・リソース使用率
  • worker processを増やすことで並列度を上げて負荷を掛けることも可能です。

BESUとは

  • Hyperledgerコミュニティが提供しているJAVA実装版のEnterprise Ethereumです。
  • Enterprise Ethereum Alliance(EEA)の仕様に準拠しており、EVM(Ethereum Virtual Machine)が動作するためEthereumのスマートコントラクトはそのまま移植可能です。
  • また、合意形成アルゴリズムはPoW, PoA, IBFTが選択可能です。
  • Ethereumの場合はブロックチェーンに記録したデータはすべて公開されることが原則でしたが、BESUの場合は指定したメンバー間でしか情報の共有を行わないPrivacy Groupを使用することができるのが特徴です。

Caliperの使い方

検証環境の構成情報

  • OS: Ubuntu20.04
  • 台数: 1台
  • メモリ: 8GB
  • version:
    • Nodejs: v12.16.1
    • npm: 6.13.4
    • docker: Docker version 19.03.12, build 48a66213fe
    • BESU: besu/v21.7.0/linux-x8664/oracleopenjdk-java-11
    • Caliper: 0.4.0

Caliperのインストール

  • Caliper-benchmarksをgit cloneします。

    • Caliperのサンプルコードが含まれています。
      $ cd
      $ git clone https://github.com/hyperledger/caliper-benchmarks.git
      $ cd caliper-benchmarks
  • Caliperが使用するターゲットとSDKのバージョンを指定します。

    • 今回はターゲットにbesu, SDKのバージョンにlatestを指定します。
      測定対象のブロックチェーンを切り替えるには、ターゲットを fabric のように変更すればOKです。
      $ npx caliper bind --caliper-bind-sut besu:latest
  • npmでcaliper-cliをインストールします。

    • $ npm install --only=prod @hyperledger/caliper-cli@0.4.0

測定対象環境の起動

  • 測定対象のDocker Containerは、Caliper実行時に自動的に起動されるので、事前の起動は不要です。
  • 具体的には、 networks/besu/1node-clique/networkconfig.jsoncaliper.command.start caliper.command.end が測定開始前と測定完了後に実行されるため、ここでdocker-composeを立ち上げるようにしておけばOKです。

測定実行

BESU1ノード構成での測定

  • caliper managerによって負荷を掛けます
    $ cd /home/vagrant/caliper-benchmarks
    $ npx caliper launch manager \
    --caliper-bind-sut besu:latest \
    --caliper-benchconfig benchmarks/scenario/simple/config.yaml \
    --caliper-networkconfig networks/besu/1node-clique/networkconfig.json \
    --caliper-workspace .
  • Name Succ Fail Send Rate (TPS) Max Latency (s) Min Latency (s) Avg Latency (s) Throughput (TPS)
    open 1000 0 62.3 29.01 2.18 14.40 22.4
    query 1000 0 100.2 0.02 0.00 0.00 100.2
    transfer 50 0 7.0 5.03 0.04 2.55 5.0
  • 測定結果がhtmlでも出力されています。 htmlの中身はコマンドの実行結果とほぼ同じです。
    $ ls -la
    report.html -rw-rw-r-- 1 vagrant vagrant 11001 Aug 12 09:30 report.html

BESU4台、Tesseraノード4台構成

  • 4台構成にするために、独自のdocker-composeでオールインワン環境を立ち上げるようにしています。
  • caliper managerによって負荷を掛けます

    $ cd /home/vagrant/caliper-benchmarks 
    $ npx caliper launch manager \
    --caliper-bind-sut besu:latest \
    --caliper-benchconfig benchmarks/scenario/simple/config.yaml \
    --caliper-networkconfig networks/besu/besu4node/networkconfig_simple.json \
    --caliper-workspace .

  • Name Succ Fail Send Rate (TPS) Max Latency (s) Min Latency (s) Avg Latency (s) Throughput (TPS)
    open 1000 0 -2217.3 21.85 8.88 16.63 118.3
    query 1000 0 100.4 0.05 0.00 0.00 100.3
    transfer 50 0 261.8 10.05 0.70 5.54 5.2

Ethereum1ノード構成での測定

  • caliper managerによって負荷を掛けます
    変更するのは --caliper-networkconfig だけです。
    ネットワークの構成設定ファイルを変更するだけで、異なる構成に対して簡単に性能を測ることができます。

    $ cd /home/vagrant/caliper-benchmarks
    $ npx caliper launch manager \
    --caliper-bind-sut besu:latest \
    --caliper-benchconfig benchmarks/scenario/simple/config.yaml \
    --caliper-networkconfig networks/ethereum/1node-clique/networkconfig.json \
    --caliper-workspace .

  • Name Succ Fail Send Rate (TPS) Max Latency (s) Min Latency (s) Avg Latency (s) Throughput (TPS)
    open 1000 0 58.2 29.36 2.14 16.38 22.2
    query 1000 0 98.1 0.01 0.00 0.00 98.1
    transfer 50 0 10.1 6.90 2.08 4.48 5.0
  • 測定結果がhtmlでも出力されています。 htmlの中身はコマンドの実行結果とほぼ同じです。

    $ ls -la report.html
    -rw-rw-r-- 1 vagrant vagrant 11001 Aug 12 09:30 report.html



AWS環境上での測定結果

構成情報

    • OS: Ubuntu20.04
    • 台数: 5台(BESU4台+caliper1台)
    • インスタンスタイプ: m5.large
    • ストレージ: gp1, 30GB
    • ネットワークの構成:
      • AZ1: 2台(BESU×2)
      • AZ2: 2台(BESU×1+Caliper×1)
      • AZ3: 1台
    • ブロックチェーンノード
      • マイニング方式: IBFT
      • ブロック生成間隔: 10秒
      • blockGasLimit: 0x1fffffffffffff
      • 台数:
      • besu: 4台
      • orion: 4台
    • 負荷ターゲット: AZ1のbesu1号機
    • version:
      • Nodejs: v12.16.1
      • npm: 6.13.4
      • docker: Docker version 19.03.12, build 48a66213fe
      • BESU: besu/v21.7.0/linux-x8664/oracleopenjdk-java-11
      • Caliper: 0.4.0

測定結果

    • 目標TPS
      • open: 200
      • query: 500
      • transfer: 200
    • JVMのメモリ上限: 6GB
    • 測定結果

      Name Succ Fail Send Rate (TPS) Max Latency (s) Min Latency (s) Avg Latency (s) Throughput (TPS)
      open 4095 0 598.8 44.78 13.56 33.94 90.5
      query 4095 0 500.8 0.01 0.00 0.00 500.7
      transfer 4095 0 602.4 35.71 7.34 26.07 117.6

    • 目標TPS

      • query: 2000
    • JVMのメモリ上限: 6GB
    • 測定結果
      Name Succ Fail Send Rate (TPS) Max Latency (s) Min Latency (s) Avg Latency (s) Throughput (TPS)
      query 100000 0 909.2 0.07 0.00 0.00 909.2

性能測定で得られた知見

    • BESUのpending transactionはMemoryを消費する。pending transactionが溜まれば溜まるほどMemoryを消費し、最終的にはプロセスがハングアップする。
      OOM Killerによるコンテナ停止&再起動ではなく、純粋なるハングアップ(ゾンビ状態)なので、ログからしか検知不可。
      時間経過で復旧することはなく、手動でコンテナを再起動させなければならない。

    • Caliperの性能測定ツールを実装する際には、TX発行者のアドレスを動的に変えることはできない。
      TX発行者のアドレスは networkconfig.jsonfromAddress fromAddressPrivateKey で設定したもので固定される。変更は不可。
      よって、ERC721トークンを複数の人にmintすることはできるが、A→B→Cへ送付のようなTX発行者が変わるような操作はできない。

Caliperの解説

起動オプションの意味

  • オプション 設定値 意味
    --caliper-bind-sut besu:latest 対象の環境とバージョン
    --caliper-benchconfig benchmarks/scenario/simple/config.yaml 測定するシナリオを定義したファイル
    --caliper-networkconfig ./networks/networkconfig.json 測定環境を定義したファイル
    --caliper-workspace . caliperが仕事するときの基点となるパス。

caliper launch managerコマンドの実行した際の大まかな処理の流れ

    • caliper managerを起動
      • docker-composeで環境構築
      • 負荷測定開始
      • 負荷測定終了
      • docker-composeで環境を解体
    • コマンドラインに結果が返る
    • htmlでレポートが作成される

設定ファイルの解説

    • フォルダ構成

      ./caliper
      ├ benchmarks // ベンチマークを測定する負荷ツール
      ├ networks // 測定対象の環境。docker-composeで作る。
      └ src // 測定対象のスマートコントラクト. abiファイルを格納

    • networks

    • src

      • 測定対象のスマートコントラクトのapiファイルを格納する。
      • このapiファイルは networks/.../networkconfig.jsonethereum.contracts.simple.path で指定する。
    • benchmarks/scenario/simple/

      • ベンチマーク測定用の負荷ツールの置き場。スマートコントラクトに合わせて自作する必要あり。
      • benchmarks/scenario/simple/config.yaml
      • benchmarks/scenario/simple/open.js, query.js, transfer.js

        • 測定対象のプログラム毎にカスタマイズする
        • 負荷を掛ける実行プログラムの本体
        • createWorkloadModule, createSimpleState, submitTransaction の3つの関数から成り立つ。
          • createWorkloadModule
            • Caliperによって呼び出される関数
            • クラス名くらいを変える程度でOK
          • createSimpleState
            • caliperから渡される変数
            • config.yamltest.rounds.workload.arguments で定義した変数を使うことができるようになる。
          • submitTransaction
            • caliperがBCに負荷を掛ける時に呼び出す関数
            • 負荷を掛けるプログラム毎にSCの呼び出し関数が変わる
            • this.createConnectorRequest('SCの関数名', 関数に渡す引数のオブジェクト)
      • benchmarks/scenario/simple/utils/operation-base.js
        • いじる必要なし。
        • 負荷を掛けるプログラムが継承する母体

      • benchmarks/scenario/simple/utils/simple-state.js

        • SC毎に変更の必要あり
        • SCの関数に渡す引数を調整するプログラム
        • ランダムな値が設定された引数をいい感じに作るところ。

独自のスマートコントラクトに対して負荷を掛ける時のやり方

    • スマートコントラクトをtruffleでコンパイルする
    • コンパイルされた結果のファイル(build/contracts/**.json)を参考にし、下記の構成で caliper/src/ethereum/<任意のフォルダ名>/<任意のファイル名>.json に保存する
        "name": "コントラクト名",
        "abi": [
          ABI情報
        ],
        "bytecode": スマートコントラクトの実行コード,
        "gas": TX発行時のGas量
    • 性能測定をするインフラ構成のファイルを作成する

      • networks/besu/1node-clique/geth1node を参考にし、同じような感じで作る。
    • 上記フォルダ内の networkconfig.json を編集する

      • caliper.command.start: 開始時の処理。docker-compose.ymlのパスを変える
      • caliper.command.end: 終了時の処理。docker-compose.ymlのパスを変える. ゴミ削除ツールがあるならそれも動かす
      • ethereum.contracts."コントラクト名"に変更する
      • ethereum.contracts."コントラクト名".path: src/.../<任意のフォルダ名>/<任意のファイル名>.json に作ったjsonファイルを指定する
      • ethereum.contracts."コントラクト名".gas: 関数毎のgas量を指定する
    • ベンチマーク測定用実行プログラムを作成する

      • benchmarks/scenario/simple/ をコピーし、 benchmarks/scenario/MyNFT とする
      • benchmarks/scenario/MyNFT/config.yaml を編集する
      • simpleArgs: 実行プログラムのプログラムに対して引数を渡す場合には、その引数と値を宣言する
      • test.name: コントラクト名
      • test.rounds: 測定対象の関数の分だけ用意する
      • test.rounds.labels: 任意の名前. 測定対象の関数名と同じにするのが分かりやすい
      • test.rounds.workload.module: 実行対象のプログラムのパスを指定する

    • initializeWorkloadModule を編集する

      • initializeWorkloadModule 内の this. は、benchmarks/scenario/MyNFT/config.yamlsimpleArgs に合わせること
      • _createEthereumConnectorRequestcontract は 測定対象のコントラクト名を指定すること
    • benchmarks/scenario/MyNFT/utils/simple-state.js を編集する
      • コントラクトの関数で指定するパラメータを動的に変えたいなら、ここで引数部分をいい感じに作るようにする。
      • benchmarks/scenario/MyNFT/config.yaml で指定した引数を使う場合は、 constructor 部分で変数に格納する

OpenZeppelinのERC721に対して測定するときのサンプルコード

    • OpenZeppelinから提供されているERC721の実装に対してCaliperを動作させるサンプルコードの例を紹介します。
    • 独自アプリへ適用する際の参考にしていただければと思います。

    • フォルダ構成

      benchmarks/MyNFT
        ├── config.yaml
        ├── mint.js
        └── utils
            ├── operation-base.js
      └── simple-state.js

    • config.yaml

        # 実行プログラム(nodejs)に渡すときの変数
        simpleArgs: &simple-args
          numberOfTokens: &number-of-tokens 1000
        test:
          name: MyNFT
          description: >-
            This is an example benchmark for Caliper, to test the backend DLT's
            performance with NTFToken mint and transfer.
          workers:
            type: local # worker種別
            number: 1   # worker台数
          rounds:
            - label: mint
              description: >-
                create token with mint function.
              txNumber: *number-of-tokens  # 測定1ラウンドのTX数
              rateControl:
                type: fixed-rate
                opts:
                  tps: 50 # 負荷の目標TPS
              workload:
                module: benchmarks/MyNFT/mint.js # 実行するプログラム
                arguments: *simple-args

    • utils/operation-base.js
      サンプルコードとの変更点のみ抜粋

      async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
          await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);
          // config.yamlの引数に合わせて変更する箇所
          this.assertConnectorType();
          this.assertSetting('numberOfTokens');
          this.numberOfTokens = this.roundArguments.numberOfTokens;
          this.simpleState = this.createSimpleState();
        }
        _createEthereumConnectorRequest(operation, args) {
          const query = operation === 'query';
          return {
            contract: 'MyNFT',  // コントラクト名に合わせて変える
            verb: operation,
            args: Object.keys(args).map(k => args[k]),
            readOnly: query
          };
        }

    • utils/simple-state.js

        'use strict';
        const HDWallet = require('ethereum-hdwallet')
        const Web3 = require('web3');
        const Dictionary = 'abcdefghijklmnopqrstuvwxyz';
        // HDウォレットのシード値
        const seed = Buffer.from('efea201152e37883bdabf10b28fdac9c146f80d2e161a544a7079d2ecc4e65948a0d74e47e924f26bf35aaee72b24eb210386bcb1deda70ded202a2b7d1a8c2e', 'hex')
        // HDウォレットのインスタンス化
        const hdwallet= HDWallet.fromSeed(seed)
        const web3 = new Web3(new Web3.providers.HttpProvider("ws://localhost:8546"));
        class SimpleState {
            constructor(workerIndex, sutContext, accounts = 0) {
                this.accountsGenerated = accounts;
                this.sutContext = sutContext
                this.accountPrefix = this._get26Num(workerIndex);
                this.transferCount = 1  //transfer用試験で使うカウンター
                this.ownerOfCount = 1   //ownerOf用試験で使うカウンター
            }
            _get26Num(number){
                let result = '';
                while(number > 0) {
                    result += Dictionary.charAt(number % Dictionary.length);
                    number = parseInt(number / Dictionary.length);
                }
                return result;
            }
            _getAccountKey(index) {
                return "0x"+hdwallet.derive(`m/44'/60'/${index}'/0/0`).getAddress().toString('hex')
            }
            _getRandomAccount() {
                // choose a random TX/account index based on the existing range, and restore the account name from the fragments
                const index = Math.ceil(Math.random() * this.accountsGenerated);
                this.accountsGenerated++;
                return this._getAccountKey(index);
            }
            getMintArguments() {
                let _to = this._getRandomAccount()
                return {
                    // 変数名をSCの引数名に合わせること
                    _to: _to,
                    tokenURI: ""
                };
            }
        }
        module.exports = SimpleState;

    • mint.js

        'use strict';
        const OperationBase = require('./utils/operation-base');
        const SimpleState = require('./utils/simple-state');
        class Mint extends OperationBase {
            constructor() {
                super();
            }
            createSimpleState() {
                return new SimpleState(this.workerIndex);
            }
            /**
            * 測定用の関数実行
            */
            async submitTransaction() {
                let mintArgs = this.simpleState.getMintArguments();
                //console.log("mintArgs",mintArgs)
                await this.sutAdapter.sendRequests(this.createConnectorRequest('mint', mintArgs));
            }
        }
        function createWorkloadModule() {
            return new Mint();
        }
        module.exports.createWorkloadModule = createWorkloadModule;

まとめ

    • Hyperledger Caliperを使用すると、複数のブロックチェーンネットワーク構成、異なるマシンスペックの環境に対して、統一的な方法で性能を測定することができるようになるのが特徴です。
      測定できるのはEnd to Endではなく、ブロックチェーンのスマートコントラクトに対する読み書きであるため、ブロックチェーン層の純粋なポテンシャルを探ることができ、アプリケーション層との性能のボトルネックを調査するときにも使用することができるようになります。 今までは弊社ではJmeterを使ってアプリ層を通してブロックチェーンの性能測定をしていましたが、ブロックチェーン層のみの性能が把握できるのはとても魅力的でした。 このおかげでトランザクションのキャパシティー設計がやりやすくなりました。
    • また、現在運用中の環境に対しても既存のソースコードに手を加えることなく測定が可能なため、導入のハードルが低く済むのも魅力です。
連載シリーズ
テクノロジーコラム
著者プロフィール
深津颯騎
深津颯騎

デジタルトランスフォーメーション事業部
第一事業ユニット

ブロックチェーン関連のPoC, R&Dを中心に活動中