LayerX エンジニアブログ

LayerX の エンジニアブログです。

負荷テストツール「Vegeta」でAnonifyの負荷テストをしていく話

こんにちは!LayerX Labsでエンジニアをやっているきむ(@jkcomment)です。

なぜ負荷テストを行うのか

負荷テストと言われるとシステム開発が終わったタイミングやサービスのリリース前、システムの性能低下が検知されたタイミングで行われることが多く、また、計画から手法、シナリオ策定、ツール選定など時間・金・人がかかる大変な仕事的なイメージが多いのではないでしょうか。我々が手掛けているAnonifyは、現在絶賛開発中のプロジェクトで、従来の負荷テストのような大規模でガッツリした感じではなく、Anonifyを開発していく中で①普通に予定通りの性能が出るか②現時点でのAnonifyのシステム的な限界点を知る③負荷をかけることで想定外のエラーや不具合を検知するという目的で負荷テストをやっています。言い換えると、Anonifyを成長させていくためのプロセスの一つとして負荷テストを行なっているということです。これを可能にしてくれてるのがVegetaという負荷テストツールです。あとで詳しくご紹介しますが、Vegetaはシンプルながらも強力なツールで、気軽に負荷テストを行うことができます。また、LabsではVegetaを用いてどのようにAnonifyの負荷テストをしていくかについてお話ししたいと思います。

Vegetaとは

github.com

Vegetaは、Go言語で開発された負荷テストツールです。インストールも簡単で、CLIとしても、ライブラリとしても利用できます。ちなみにVegetaという名前はドラゴンボールから来ているそうです。
Vegetaの1番の特徴としてシンプルさが挙げられます。vegeta attackコマンドで負荷をかけ、結果をvegeta reportコマンドで確認する。はい、これが全てです。笑

vegeta attackで負荷をかける

echo "GET http://localhost/" | vegeta attack -duration=5s > result.bin

テストシナリオを用いた負荷テストコマンド(-targetsの部分がテストシナリオの指定する部分)

vegeta attack -duration=1s -rate=500 -targets=targets.txt > result.bin

vegeta reportで結果を確認

vegeta report result.bin

他にも、例えばテストシナリオを作成して負荷をかけることもできますし、分散して負荷がかけられる、プロセス間で負荷がかけられるなど基本シンプルさを維持しながらも多様なユースケースに対応できるように様々な機能が備えています。また、負荷テストの結果を確認する機能も充実しており、ターミナルだけでなくブラウザで確認することもできますし、他のツールと組み合わせることでリアルタイムで負荷テストの状況を確認することもできます。

今回のシナリオの想定

github.com

Anonifyリポジトリのexampleディレクトリにあるerc20を前提に、erc20のtransferという機能を前提で進めます。 transferを実行するには下記の手順が必要です。SGX環境の構築手順はこちらをご覧ください。

tech.layerx.co.jp

  1. Anonifyからuser_counterを取得
  2. Anonifyからenclave公開鍵を取得
  3. Enclave上で実行するtransfer実行コマンド生成
  4. 3で生成したコマンドとtransfer処理の引数(recipient, amount)とenclave公開鍵を用いて暗号化し、リクエストデータ作成
  5. 4で生成したリクエストデータを送信

上記の手順について簡単に説明しますと、1のuser_counterは冪等性および順序性の保証に利用するもので、2のenclave暗号鍵はuser_counter取得時にリクエストを暗号化するための公開鍵です。 3と4はEnclave内でtransferを実行するためのデータ生成と暗号化を行います。5は生成されたリクエストデータをAnonifyに送ることでtransferを実行が完了になります。

1と2, 5は別途実行することにし、3,4をRustのコードで表現したのが下になります。

pub(crate) fn create_transfer_req<CR: RngCore + CryptoRng>(
    state_runtime_url: String,
    recipient: AccountId,
    amount: u64,
    enckey: SodiumPubKey,
    user_counter: u32,
    csprng: &mut CR,
) -> Result<()> {
    // Enclave上で実行するtransfer実行コマンド生成
    let req = json!({
        "access_policy": NoAuth::new(AccountId::from_array(DUMMY_ACCOUNT_ID)),
        "runtime_params": {
            "amount": amount,
            "recipient": recipient,
        },
        "cmd_name": "transfer",
        "counter": user_counter,
    });
    
    // 3で生成したコマンドとtransfer処理の引数(recipient, amount)とenclave公開鍵を用いて暗号化
    let ciphertext = SodiumCiphertext::encrypt(
        csprng,
        &enckey,
        &serde_json::to_vec(&req).unwrap(),
    )
    .map_err(|e| anyhow!("{:?}", e))?;
    let req = json!(ciphertext);

    let out = stdout();
    let mut out = BufWriter::new(out.lock());
    write!(
        out,
        "{}",
        base64::encode(&serde_json::to_vec(&req).unwrap())
    )
    .unwrap();

    Ok(())
}

このコードがテストシナリオになるわけですが、そのままVegetaのテストシナリオ機能を使うには工夫が必要になってくるため、生成されたリクエストデータを標準出力としてvegeta attackコマンドに渡すようにします。上記の1~5の手順をスクリプトファイルに書くと下記になります。

#!/bin/bash
set -e

# user_counter
i=0

# Anonifyからenclave公開鍵を取得
PUBKEY=$(curl http://localhost/api/v1/enclave_encryption_key -k -s -X GET -H "Content-Type: application/json" -d '')
SCRIPTS_DIR_PATH=$(cd $(dirname $0) && pwd)

while true
do
    # 上記のtransfer.rsのtransferを実行し、リクエストデータを取得(下記はコードは例です)
    REQ=$(create_transfer_req -recipient 0x1234 -amount 1000 -usercounter $i -c 1 -k "$PUBKEY")
    jq -ncM "{method: \"POST\", url: \"localhost/api/v1/state\", body: \"$REQ\", header: {\"Content-Type\": [\"application/json\"]}}" >&1
    # user_counter更新
    ((i=i+1))
done

負荷テストの時は便宜上、enclave公開鍵は1度だけ取得して使い回す、user_counterはローカルで生成した値を処理時に使用し、処理が終わったらuser_counterをインクリメントして次の処理に使うようにします。

Vegeta Attack!

vegeta attackコマンドはたくさんのオプションが用意されていますが、最低限のものとして下記のオプションを使うと無難にvegeta attackができます。

-duration: 負荷をかける時間。0の場合、無制限(例: -duration=60s)
-rate: 秒間リクエスト数(例: -rate=30)
-format: 実行結果をデコードするためのファイル形式を指定(例: -format=json)

下記のコマンドを実行することで今回のシナリオの想定のところでお話ししたシナリオ通り負荷テストが始まります(秒間700リクエストを永遠に送る)。

generate-transfer-req.sh | vegeta attack -duration=0 -rate=700 -lazy -format=json > result.bin

負荷テストが終わったら、vegeta reportコマンドで結果を確認します。

vegeta report result.bin

結果

Requests      [total, rate, throughput]  38961, 208.43, 190.88
Duration      [total, attack, wait]      3m24.103060884s, 3m6.923253452s, 17.179807432s
Latencies     [mean, 50, 95, 99, max]    1m14.405205472s, 1m14.869557049s, 2m21.081362339s, 2m26.972727327s, 2m28.454031212s
Bytes In      [total, mean]              3116983, 80.00
Bytes Out     [total, mean]              33783415, 867.11
Success       [ratio]                    100.00%
Status Codes  [code:count]               202:38961
Error Set:

最後に

今回はLabsで負荷テストを行なっている理由と、Vegetaの紹介、負荷テストを実施方法についてご紹介しました。普段から負荷テストを行なっている方にはご存知の内容ばかりだったかもしれませんが、使い始めの方だったり、これから負荷テストを検討される方にお役に立てれば幸いです。

絶賛エンジニア採用中ですので、少しでも興味のある方は一度お話をさせていただければと思います! herp.careers