このところ、Webアプリやバッチのパフォーマンステストを自動化するために四苦八苦してるので書いてみます。
パフォーマンステストは泥臭い作業です。毎回似たような感じで待ち時間の長い単調作業と、ボトルネックを解析して実装やミドルウェア設定を見直すような神経を使う作業が入り混じって疲れます。このうち前者を自動化してしまえば、本質的な部分に力を注げるだけでなく、夜間や休日を活用して多くのバリエーションを試すことができます。
パフォーマンステストの流れはWebアプリとバッチで以下のように整理できると思います。
- Webアプリ
- バッチ
- デプロイメント
- サーバサイドで必要なデータセットの準備
- アプリケーションの設定
- バッチ実行
- サーバサイドのログ収集
- 分析
必要に応じて、環境リセット(ミドルウェア再起動や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つの軸で考えると以下のようになると思います。
データセットが小さい場合 | データセットが大きい場合 | |
---|---|---|
事前に準備 | SQLやCSVをリポジトリに入れておき、テスト前にチェックアウトしてDBに流す。 | ダンプをDBサーバに配置しておき、テスト前にリストアする。 |
動的に生成 | データ生成アプリをデプロイして実行する。 | ダンプをリストア後、UPDATEを実行するなど。なるべく短時間で終わる方法を検討する。 |
いずれの方法でも人手を介すると人がボトルネックになってしまいます。そのため、スクリプトを流すだけでデータセットが出来上がるようにします。例えば、こんな感じ。
- デプロイ時にSQLファイルも配置する。もしくは、ダンプを用意しておく。
- Jenkinsがリポジトリからパフォーマンステスト用プロジェクトをチェックアウトする。
- チェックアウトしたスクリプトを実行する。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:開発フェーズと運用フェーズが分かれているからこういう発想になっちゃうけど。本来は運用できる仕組みを作るべき。