GeekFactory

int128.hatenablog.com

パフォーマンステスト自動化の取り組み

このところ、Webアプリやバッチのパフォーマンステストを自動化するために四苦八苦してるので書いてみます。

パフォーマンステストは泥臭い作業です。毎回似たような感じで待ち時間の長い単調作業と、ボトルネックを解析して実装やミドルウェア設定を見直すような神経を使う作業が入り混じって疲れます。このうち前者を自動化してしまえば、本質的な部分に力を注げるだけでなく、夜間や休日を活用して多くのバリエーションを試すことができます。

パフォーマンステストの流れはWebアプリとバッチで以下のように整理できると思います。

  • Webアプリ
    1. デプロイメント
    2. クライアントサイド(負荷生成側)で必要なデータセットの準備
    3. サーバサイドで必要なデータセットの準備
    4. アプリケーションの設定
    5. 負荷生成
    6. クライアントサイドのログ収集
    7. サーバサイドのログ収集
    8. 分析
  • バッチ
    1. デプロイメント
    2. サーバサイドで必要なデータセットの準備
    3. アプリケーションの設定
    4. バッチ実行
    5. サーバサイドのログ収集
    6. 分析

必要に応じて、環境リセット(ミドルウェア再起動やDB統計情報)や整合性チェック(事前条件や事後条件を満たしているか)を入れるといいと思います。

デプロイメント

もちろんワンクリックだよね(キリッ

というわけでデプロイメントも自動化しましょう。ビルドプロセスからデプロイプロセスに成果物を渡す部分はリポジトリ経由にして分離します。こうすることでビルドが不安定になってもデプロイが影響を受けなくなります。いざという時もリポジトリから過去の成果物を取り出せるので安心です。

MavenやIvyで成果物を管理しているのならAntをおすすめします。Antの場合はsshexecタスクやscpタスクを使います。

<!-- リポジトリからzipを取得した後の例 -->
<scp todir="${deploy.username}:${deploy.passwd}@${deploy.host}:/hoge/dist">
  <fileset dir="dist" />
</scp>
<property name="cmd.1">
rm -vfr /hoge/webapp;
mkdir -vp /hoge/webapp;
unzip -d /hoge/webapp /hoge/dist/hoge-r777.zip;
sudo service tomcat start;
</property>
<sshexec host="${deploy.host}" username="${deploy.username}" password="${deploy.passwd}" command="${cmd.1}" />

ミドルウェア設定のうちパフォーマンステストで調整することの多いもの(Web/APサーバのワーカー数など)はリポジトリに入れて自動デプロイすると便利です*1

データセットの準備

データセットの準備は時間がかかるため、パフォーマンステストを進める上でのボトルネックになりがちです。データセットの大きさや生成方法によって最適解は変わります。2つの軸で考えると以下のようになると思います。

データセットが小さい場合 データセットが大きい場合
事前に準備 SQLCSVリポジトリに入れておき、テスト前にチェックアウトしてDBに流す。 ダンプをDBサーバに配置しておき、テスト前にリストアする。
動的に生成 データ生成アプリをデプロイして実行する。 ダンプをリストア後、UPDATEを実行するなど。なるべく短時間で終わる方法を検討する。

いずれの方法でも人手を介すると人がボトルネックになってしまいます。そのため、スクリプトを流すだけでデータセットが出来上がるようにします。例えば、こんな感じ。

  1. デプロイ時にSQLファイルも配置する。もしくは、ダンプを用意しておく。
  2. Jenkinsがリポジトリからパフォーマンステスト用プロジェクトをチェックアウトする。
  3. チェックアウトしたスクリプトを実行する。Capistranoならこんな感じ:
task :initialize_dataset, :roles => db_server do
  run <<-EOF
    mysql -u apuser hogedb < /hoge/testdata/truncate.sql;
    mysql -u apuser hogedb < /hoge/testdata/masterdata.sql;
    mysql -u apuser hogedb < /hoge/testdata/pattern-01.sql;
    # analyzeも忘れずに
  EOF
end

アプリケーションの設定

テスト条件に合わせてアプリやミドルウェアの設定ファイルを変更します。サーバの状態に依存しないよう、設定ファイルを置き換えてしまう方がよいでしょう。こんな感じ:

def configure_app (seconds, concurrency)
  put <<EOF, '/hoge/batch/resource/foge.properties', :roles => ap_servers
execution_time=#{seconds * 1000}
execution_concurrency=#{concurrency}
EOF
end

Javaの話ですが、デプロイ後に編集する必要のある設定ファイルはJARに含めずにクラスパスに配置します。開発環境では意識しなくていいので結構忘れられるんですけどね。

このあとの負荷生成やログ収集は一般的な話なので割愛します。

バリエーション

パフォーマンステストではテスト条件を変えながら測定を繰り返し、スループットやリソース状況の変化を観察します。

テスト条件を変えながら実行する部分もスクリプト化しておくと、夜間や休日を活用して多くのテスト条件を試せるようになります。自然言語っぽく書いておけば知らない人が読んでも大丈夫でしょう。テスト条件の設計はExcelを使うかもしれないけど、実施するのはExcel+人力じゃなくてスクリプトの方がいいよね。

$execution_time = 300
$test_interval = 60

task :main do
  for scenario in ['scenarioA', 'scenarioB']
    for concurrency in [25, 50, 100]
      initialize_dataset
      restore_dataset scenario
      configure_app $execution_time, concurrency
      record_history "concurrency=#{concurrency}, scenario=#{scenario}" {
        execute_batch
      }
      sleep $test_interval
    end
  end
end

後から分析できるよう、テストの開始日時と終了日時を記録しておきます。上記スクリプトではrecord_historyメソッドを呼び出して開始日時、終了日時、テスト条件を(テスト管理DBの)実行履歴テーブルに追加しています。record_historyメソッドの中身は省略しましたが、テスト管理DBサーバにSSHしてSQLを実行するとかでもいいです。

テストスクリプトの動作確認時は環境変数で実行時間を設定するとかしてさくっと確認できるようにしておきましょう。バッチが終わるのを待っていたら朝になってしまいます。

まとめ

パフォーマンステストを自動化することで、分析や改善という本質的な部分にパワーを集中できます。また、夜間や休日を使って多くの条件でテストできるようになります。Jenkins執事は単調作業も間違いなくこなしてくれるし、必ずログを残してくれるので安心です。

終電まで仕込みを頑張って翌朝に一喜一憂する生活にようこそ。

*1:開発フェーズと運用フェーズが分かれているからこういう発想になっちゃうけど。本来は運用できる仕組みを作るべき。