ひげろぐ

技術者として仕事人としての思うところや覚え書きやらです
Home      Profile      Works     
2010-08-10

ウェブアプリの負荷テストについてのメモ

負荷テストってどうやってやったらいいんだろう?
って長年思いつつ、適当にabとかで負荷かけてとりあえずDone、みたいなことでお茶を濁してきたわけだけど、最近のプロジェクトで腰を据えてやる機会があり一定の知見を得たのでメモっておく。

手順と心がけるべきいくつかの事柄について。

手順

  • かける負荷の規模を決める
  • シナリオを作る
  • ツールを準備する
  • 負荷テスト対象のサーバーを準備する
  • 負荷をかける側のマシンを準備する
  • データを準備する
  • 負荷テストを走らせる
  • 計測結果を記録する
かける負荷の規模を決める

並列度とシナリオのループ回数くらいを決めておく。
1000並列で5000ループ、みたいな。

パフォーマンスの目標値があればそれをそのまま使うか、目安にできる。

シナリオを作る

ユーザーの実際の動きを想定した画面遷移のシナリオを作る。
これはURLのリストでよい。(リダイレクトなどは考慮すること)

負荷テストをより意味あるものにするためには、実際のユーザーの動きをより忠実に再現したものであることが望ましい。
サービス開始後ならばログから精度の高いシナリオが作成できる。
サービス開始前でもクローズドベータなどで実際のユーザーにさわってもらう機会があれば、そのログからよいシナリオが作れる。

また思わぬところに潜んでいるボトルネックを洗い出すためにシナリオはすべての処理を網羅するようにする。

ツールを準備する
  • 多数のユーザーが
  • 同時にアクセスして
  • 一連のシナリオ(画面遷移)をたどる

という動作をシミュレートできるツールならばなんでもよい。
これらはアプリの負荷テストには必須。

abは指定できるURLがひとつだけなのでちょっと力不足ということになる。
単一ページのチューニングのお供にはお手軽でよいだろうけど。

このあたりは得意な言語で使い捨てのスクリプトを組んでも良いと思う。
また探せばいいツールがいくつかあるようだ。

ツール選びの基準としては上記の必須項目の他に以下のようなものがあげられる。

  • 動作が軽い
  • 設定が簡単
  • レポーティング機能がすぐれている

動作が軽くないと負荷をかける側がボトルネックになって十分に負荷をかけられない場合がある。
そういう意味でJMeterは重いのでよろしくないと思う。

負荷テストについて考える際にいろいろと参考にした『キャパシティプランニング』ではhttperfとかSiegeをおすすめしている。

テスト対象のサーバーを準備する

本番に近い構成のサーバーを準備する。
そういう意味ではサービスのローンチ直前などは本番環境でのテストがやりやすくていい。

負荷をかける側にもリソースが必要になるので、本番環境の構成の半分とか四分の一とかの構成でテストする場合もある。

ローンチ後の負荷テストに関しては理想的には本番環境と同等のテスト環境があればよいが、そんな恵まれた環境は滅多にないと思うのでいったいどうしたらいいのだろうか。
このあたり答えが出ていない。
環境構築やデプロイの自動化が十分にできていればクラウドを使うという選択もあるのだろうか。

負荷をかける側のマシンを準備する

負荷をかける側のマシンも十分に強くないと、負荷をかける側の限界が先に来てしまって負荷をかけきれないので、こちらもちゃんと準備する。
できれば複数台がよい。

ちょっと前までは負荷テスト用にマシンを複数台確保するというのはなかなか現実的ではなかったけれど、最近はEC2などのクラウドがあるので容易に必要なだけ安価に調達できるようになった。
こういうところはまさにクラウドの使いどころ。

データを準備する

サービス開始当初は問題がなくても、データがたまってきて問題が出てくるケースというのは多い。
なので大量の実データを準備することも必要。

データ投入はSQLで行うのが一番速いが、一方で実際にアプリを運用した場合に比べるとデータの抜けが発生する可能性も高い。
なのでSQLによるバルクインサートなどより時間はかかるが実際にアプリの動作によってデータを作るのが一番確実なデータを準備できると思う。

具体的には負荷テストツールでデータの生成をともなうシナリオを作って走らせる。
一度生成したデータはバックアップしておけば、次回データを準備するのは短時間でできるようになる。

負荷テストを走らせる

いろいろと準備ができたので負荷テストを実行する。
最初は少ない負荷をかけてアプリが正しく動いているかどうかを確認する。

これだけ負荷をかけているのにスゴいパフォーマンスだ!
とか思ったら実はほとんどのページがエラーになっていて処理をきちんと実行していなかったなんてこともあり得るので。

一連のシナリオが正しく動いていることが確認できたら本腰を入れて負荷をかける。

負荷をかける側のリソース状況を見て、そちらがいっぱいいっぱいになってないことを確認する。
負荷をかける側がいっぱいいっぱいになっていたら十分に負荷をかけ切れてない可能性があるので、マシンの追加などを検討する。

計測結果を記録する

実行条件とともにしっかり記録しておく。
この結果がパフォーマンスチューニングやキャパシティプランニングの指標になる。

  • 負荷テストツールのレポート結果
  • 負荷をかけている間のサーバーのモニタリング結果
  • httpdのログ
  • スロークエリログ

クライアントから見たパフォーマンスとサーバー側のリソースの変化の両面から。
モニタリングに関してはMRTG、Munin、ZABBIXなどのツールで負荷がかかっている間のグラフが見られるようにしておくとよい。

httpdのログにはレスポンスタイムを含めるようにしておく。(ApacheならばLogFormatに%Dを加える)
そうすれば後で重いページを探し出すことができる。

スロークエリは0.1秒以上かかったらNGとか条件を厳しめにしておくと問題を効果的にあぶり出せる。

心がけるべきいくつかの事柄

負荷テストは負荷をかける側も痛みを伴う

負荷をかける側のマシンリソースが十分でないと負荷をかけきれないことのほか、ウェブアプリの負荷テストはネットワーク帯域を圧迫する。
ネットワーク帯域は意外と盲点なので注意。

パフォーマンスチューニングと負荷テストは別

テスト駆動開発で実装の帽子とリファクタリングの帽子を交互にかぶり直すように、チューニングの帽子と負荷テストの帽子も交互にかぶり直すようにする。
つまりふたつを一緒くたにして同時にやらないように。

これらを同時にやり始めると、ちょっとパラメータを変更しては負荷テストを繰り返すといったスパイラルに陥ってしまいがち。
このスパイラルはとても楽しいが、かかる時間の割に得られるものはそれほど多くない。

負荷テストの第一の目的はパフォーマンスの計測であって、パフォーマンスの向上はまた別の話。
そのあたりの線引きを意識しておかないと無駄に負荷テストを繰り返して時間を浪費してしまいかねない。

スロークエリはダメ、絶対

ごくまれにしかアクセスされないページにひとつスロークエリが潜んでいるだけで一気にパフォーマンスが半減したりする。(実のところ本当に50%減とかする)
高負荷時にはひとつの小さな傷が致命傷になりかねないという話。

負荷テストを要するようなシステムではスロークエリは致命的なバグと同列と言っていい。

また、そうした重箱の隅に潜んでいるスロークエリをあぶり出すためにもシナリオはすべての処理を網羅している必要がある。

追記

- 手順の項にかける負荷の規模についてなど追記。

ほか何か思いついたら適宜追記していくかも。

2009-06-25

:selectで取得するカラムを絞ったらパフォーマンスが倍に

最近管理しているDBサーバで継続的にスロークエリが出るようになったので、チューニングしてみたら気持ちの良い結果が出た。
結論から言うとカラム数が多いテーブルに対しては:selectで取得するカラムを絞るのがかなり有効かと思う。

現状把握

今回スロークエリの発生していたテーブルの状況を整理したのが以下。

  • レコード件数は110万件くらい
  • カラム数は30程度
  • インデックスは効いている(explainで確認済み)
  • 処理の性質的にキャッシュは使えない

スロークエリになっているのはもっぱら以下のクエリ。

select * from pages order by updated_at limit 100;

Railsのコードで見るとこんなかんじ。

Page.all(:order => 'updated_at', :limit => 100)

こんな単純なクエリが実行に2秒から10秒程度もかかってスロークエリとして記録されているのは切ない。
インデックスは効いているので問題解決には他のアプローチが必要になる。

考えるに対象は30以上カラムがあってレコードサイズもそこそこ大きいテーブル。
そこで取得するカラムを絞って余計なカラムを取得しないようにしてみたらどうかと思った。

というかクエリが単純すぎてまずはそれくらいしか浮かばなかったわけだけど。

ベンチマークとチューニング

計測なくしてチューニングなしということでベンチマークで使ったのはmybench。

ベンチマークとチューニングは手元の開発環境で実行した。
こちらレコード件数は3万件程度。本番環境より大幅に少ないが十分だろう。たぶん。

全カラム取得とカラムを絞った結果の比較が以下。
10クライアントから100回ずつ、計1000回のリクエストを送るというのを試行回数3回ずつ行った結果。
serialは経過時間(秒)です。

まずは全部まるごと取得している現状のクエリ。

select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)
  serial  : 29.173278
  serial  : 29.433684
  serial  : 30.258237

これを取得カラムを絞ったものにしてみると。

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

  serial  : 16.422306
  serial  : 17.562543
  serial  : 16.070013

倍近く速くなった。
うつくしい。

いやぁ、チューニングって本当に気持ちがいいものですね。
これを本番環境にアップしたらスロークエリもパッタリなくなり幸せになれました。

以下詳しく見たい人向け
select * from pages order by updated_at limit 100;
# Page.all(:order => 'updated_at', :limit => 100)

test: 1000 0.001631 0.060372 0.029173278 29.173278 342.779443571614
  clients : 10
  queries : 1000
  fastest : 0.001631
  slowest : 0.060372
  average : 0.029173278
  serial  : 29.173278
  q/sec   : 342.779443571614

test: 1000 0.001535 0.06981 0.029433684 29.433684 339.746801657584
  clients : 10
  queries : 1000
  fastest : 0.001535
  slowest : 0.06981
  average : 0.029433684
  serial  : 29.433684
  q/sec   : 339.746801657584

test: 1000 0.00298 0.065291 0.030258237 30.258237 330.488521191767
  clients : 10
  queries : 1000
  fastest : 0.00298
  slowest : 0.065291
  average : 0.030258237
  serial  : 30.258237
  q/sec   : 330.488521191767

select id, updated_at from pages order by updated_at limit 100;
# Page.all(:select => 'id, updated_at', :order => 'updated_at', :limit => 100)

test: 1000 0.000327 0.037233 0.016422306 16.422306 608.927881382797
  clients : 10
  queries : 1000
  fastest : 0.000327
  slowest : 0.037233
  average : 0.016422306
  serial  : 16.422306
  q/sec   : 608.927881382797

test: 1000 0.001182 0.050836 0.017562543 17.562543 569.393623691057
  clients : 10
  queries : 1000
  fastest : 0.001182
  slowest : 0.050836
  average : 0.017562543
  serial  : 17.562543
  q/sec   : 569.393623691057

test: 1000 0.000301 0.04706 0.016070013 16.070013 622.277032383234
  clients : 10
  queries : 1000
  fastest : 0.000301
  slowest : 0.04706
  average : 0.016070013
  serial  : 16.070013
  q/sec   : 622.277032383234
2008-03-19

Rails2.0と1.2のパフォーマンスの比較

Rails2.0と1.2のパフォーマンスの比較に関する記事が上がっていた。

Riding Rails: Comparing Rails 2.0 to 1.2 for speed

30%から50%くらい2.0の方が速いらしい。
記事をたどってみると簡単なscaffoldを作ってmongrelで動かし、localhostのabでベンチ。DBはSQLiteとのこと。

赖洪礼的 blog ≫ Performance comparison: Rails 1.2.6 vs 2.0.2

非常にシンプルなベンチなので実際のアプリでこれだけの差が出るわけではないだろうが参考にはなる。

ちなみにセッションを切ると2.0では15%程度、1.2は35%程度のスピードうp。
Rails1.2のデフォルトのセッションストアは遅いと言われてるが1.2の方が上がり幅が大きいのでやっぱ遅いということなんだろうな。

2.0と1.2の比較とは関係ないけどセッション不要なアプリではきちんとセッションをオフにしておくとよさそうだ。

追記:メモリ消費量も少ないらしい

赖洪礼的 blog » Memory usage comparison: Rails 1.2.6 vs 2.0.2

二割以上2.0の方が少ないね。

copyright brass.to | powered by WordPress ME