Web/AP機とDB機の2台構成で、SAStrutsで作ったWebアプリを動かしてます。CentOS,Apache,Tomcat,MySQLです。
起動直後はいい感じですが、使ってるうちに重くなります。重くなると、ごく簡単なページですら表示に数分かかります。
重くなるきっかけは、アプリ内の検索機能で結果が大量(100万件以上)になる検索をされたときです。そのリクエスト自体に時間がかかるのは仕方ないとして、その後の他の関係ないリクエストも含めて全体が重くなってしまいます。その都度Tomcat再起動をすると軽快に戻りますが、現象の再現性は高く、何度でも発生します。
重くなる検索さえなければ、2~3日でも軽快に動作し続けます。またDB管理用にWeb/AP機にphpMyAdminも入れてますが、これはTomcatが重い時でもサクサクです。
現状では、どこが悪いか調べる方法すらわかりません。VisualVMを用意しましたが、値を見てもよくわかりませんでした。。どういう問題が考えられ、どういう調査をすればいいでしょうか。「ここを調べてみて」を教えていたたければ、調べて結果をここに追記します。
よろしくお願いいたします。
※仕様など詳細はコメント欄に書きます。
確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。
これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。
そうなってると思いますよ。
アプリ側に問題があるかどうかは、提示された内容だけだと、ちょっと分からないですけどね。
S2JDBC の iterate で抜いたデータを、アプリ側で List や Map に溜めていると思うのですが、
それをためないようにして試してみたらどうでしょう?
検索結果が常に0件になる、という動作になると思いますが、iterate の側に問題があるか、
アプリ側に問題があるかどうかの、切り分けができるんじゃないでしょうか。
もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。
適切かどうかは、チューニングをした結果として出てきます。
まずは、デフォルトで動作させてみて、性能値を見ながら、適切な値を探る、というふうに決めていきます。
ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。
Perm を大きくしなければいけない場合、というのは、クラスが多い時です。
heap に余裕が無いのに、OutOfMemoryError が出るので、足りない時には、すぐ分かります。
New 領域を増やす、というか、-XX:NewSize で指定する必要があるケースというのは、あまり無いと思います。
デフォルトでは、New と Old のサイズの比率は、動的に変わりますから。
少なくとも、2GB のヒープに対して、256MB の New では小さすぎます。
psコマンドでTomcatプロセスのメモリ使用量を見てください。
軽い検索をやった場合と、思い検索をやったときの違いを見てください。
100万件検索した結果、検索結果をどこかのメモリ領域にいれ、解放し忘れているのではないかと思われます。
で、新しい処理をやろうとして、メモリ不足で、ページングで時間がかかっているのではないでしょうか。
また、100万件の検索をするにしても、SQLでselect * などとするのではなく、カラムを指定する。
検索対象のテーブルをインデックス化する、といったことが必要だと思われます。
ありがとうございます。
psコマンドで見るメモリ不足というのは、OSのメモリのことかと思います。さらにJavaVMにもメモリを割り当てるわけですが、正直、その適切な値の求め方もわからない状況で、とりあえず多めの2GBをヒープサイズ(-Xmx)に割り当ててる、という感じです、現状は。
いただいた回答へ返信しようと、psコマンドでチェックしてみました。たしかに重い検索のときはメモリ使用量が多くなっています。そして、割り当てたメモリを超えてしまう場合に、重い状況になるようです。
プログラム的には、S2JDBCを使って検索をしています。検索結果は一度に取得せず、iterateの仕組みを使っています。
参考 : http://s2container.seasar.org/2.4/ja/s2jdbc_manager_auto.html#イテレーションによる検索
これを使えば、メモリには常に1件分のデータしかため込まないと信じているのですが、それが違うのでしょうか。。。
また、同様に、カラムを指定するのも、S2JDBCのSQL自動生成を使っていると難しそうです。もちろんselectBySqlを使えば可能かと思いますが、検索処理をほとんど作り直しになってしまうため、最後の手段にしたいです。。。
VisualVMでCPUプロファイルをあたってみて下さい。
おそらくCPU時間を極端に消費しているスレッドがあるはずで、そこに何らかの原因が潜んでいます。
ありがとうございます。
重い状況が起きているときにVisualVMでやってみるということでしょうか。今度試してみます。
ところで、CPUの話をすると、重い状況のときはCPU的にはまだまだ余裕があります。
8コアなので最大800%なのですが、100%だけ消費している状況です。
これは、複数のCPUに振り分けできないような処理(Javaなのでsyncronizeされてる処理?)が
1つのCPUを限界まで使っていて、他のCPUはそれが終わるまでヒマ、ということでしょうか。
自分のコードにはsyncronizeはどこにも書いてないので、フレームワーク内ですかね。
DBの接続あたりを見直してみては?
コネクションプールは使っておられるでしょうか?
あとはTomatのログとか
ありがとうございます。
コネクションプールは使っています。10接続だったと思います。
重い状況のときは、show processlistをしても何も出ないことからもわかるように、DBには何の負荷もかかってないようです。
Tomcatのログは、catalina.outのログローテーションは設定しています。
確かにそれでも1ファイルの量は多いですが、重い検索をしたときに全体が重くなって、再起動したら軽くなるという状況の理由としては弱いように感じます。
ぱっと見た感じ、GC で CPU を食いまくってる感じ。
Tomcat のプロセスが CPU を食ってて、物理メモリにも余裕があって、swap はほとんど使ってないのだから、
ページングだとか、java VM 以外のプロセスやスレッドが、どうとか、そんなことは考えなくて良いと思う。
まずは、-verbose:gc を指定するなどして、重たくなったときの GC の挙動を確認した方が良いと思う。
大量検索をしたときの検索結果を、セションに保持しててヒープの空きが無くなってるとか、じゃないかなあ。
後、アプリの作り次第だけど、java の起動パラメータは見直した方が良いと思う。
一番気になるのは、Perm 領域のサイズがでかすぎること。
Action クラス以外に、いくつクラスがあるか知らないけど、デフォルトか、せいぜい 128M もあれば十分。
New領域のサイズ指定も、必要なんかな?
チューニングした結果で決めたサイズじゃないのであれば、指定を無くして、割合は VM に任せるべき。
ありがとうございます。
考えなくていいところを教えていただくと心強いです。
いただいた情報から、さらに調査をしてみました。確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。
これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。
検索結果をセッションに保持するようなコードは書いていません。ヒープの上限に達する前に検索が無事に終わったケースでは、検索が終わった後にVisualVMからGCを実行させると、ストンとグラフが下まで落ちます。検索処理の実行中や、重い状況になってしまったあとにVisualVMからGCを実行すると、何も変化がありません。
つまり、検索処理でものすごいメモリを消費しているということでしょうか。前の方の回答にも書きましたが、S2JDBCのiterateの仕組みを使っているので、メモリは大量に消費しないはずなのですが。。。その考え方が間違っているのか、コードが悪いんですかね。。。
もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。
ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。
「イテレーションによる検索」にあるようなコーディングをしていればメモリ消費は抑制されますが、検索結果を逐次変数や配列に格納しているとしたら、メモリ効率が悪くなる可能性があります。
ご確認下さい。
ありがとうございます。
検索結果は、その一部のレコード(画面表示するための先頭のn件)だけはエンティティをリストに保持し、一部のカラム(IDに相当する値)だけは、あとで今の検索結果に対して一括処理をさせるために、全件の分を別途リストに保存しています。ですので、メモリを食うとしたら後者の全件のIDを持っているリストだと思われます。
ですが、これは単純なList<String>で、IDひとつはせいぜい10桁なので、たとえば100万件だとしても、30MBくらいですよね(1文字3バイトx10文字x100万件)。2GBのヒープがいっぱいになるとは考えにくいと思ってます。違いますでしょうか。
ちなみにJavaVM内のメモリの利用割合って何かで調査できるもんなのでしょうか。
確かに、VisualVMで見ていても、重い検索をするとヒープサイズが増えていき、検索が終わる前にヒープサイズの上限(2GB)に達してしまうと、そこでグラフがギザギザになり、全体的に重くなる現象になってしまいます。最大値に達する前に検索が終わった場合は、全体的に重い状況にはならないようです。
これは、上限に達したから頻繁にGCが行われて、少しだけ解放されたメモリをすぐに消費し、またGCをして。。。ということが起きてるのでしょうか。
そうなってると思いますよ。
アプリ側に問題があるかどうかは、提示された内容だけだと、ちょっと分からないですけどね。
S2JDBC の iterate で抜いたデータを、アプリ側で List や Map に溜めていると思うのですが、
それをためないようにして試してみたらどうでしょう?
検索結果が常に0件になる、という動作になると思いますが、iterate の側に問題があるか、
アプリ側に問題があるかどうかの、切り分けができるんじゃないでしょうか。
もうひとつ、Javaの起動パラメータは、正直言ってよくわからないで設定しています。各領域があるのはわかっているのですが、どれくらいの値が適切かもわからないです。やはりなるべくデフォルト値に任せた方がいいのでしょうか。
適切かどうかは、チューニングをした結果として出てきます。
まずは、デフォルトで動作させてみて、性能値を見ながら、適切な値を探る、というふうに決めていきます。
ヒープは大きくしていいけど、PermやNewはデフォルトに任せた方がおすすめですか?規模にもよるのでしょうが、逆に、PermやNewをデフォルトではない値を指定しなきゃいけないのは、どういうときなのでしょうか。
Perm を大きくしなければいけない場合、というのは、クラスが多い時です。
heap に余裕が無いのに、OutOfMemoryError が出るので、足りない時には、すぐ分かります。
New 領域を増やす、というか、-XX:NewSize で指定する必要があるケースというのは、あまり無いと思います。
デフォルトでは、New と Old のサイズの比率は、動的に変わりますから。
少なくとも、2GB のヒープに対して、256MB の New では小さすぎます。
前の回答にも書きましたが、検索結果のうち、先頭の数レコードのエンティティと、全件のIDの値をそれぞれListに持っています。
これらをListにためないで、つまり空回りさせてみるというテスト、いい調査だと思います。さっそくあとでやってみます。できれば、フレームワークやライブラリではなく、自分のコードに問題があったほうが修正できるので、それを期待します。つまり、空回りさせたら、ヒープは全然増えなかった(増えたとしても、上限に達したときにGCが走ってストンと落ちて、、というのが理想ですね。
それから、起動パラメータ(JVMのチューニング)について、大変参考になりました。見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。
ですが、これは単純なList<String>で、IDひとつはせいぜい10桁なので、たとえば100万件だとしても、30MBくらいですよね(1文字3バイトx10文字x100万件)。2GBのヒープがいっぱいになるとは考えにくいと思ってます。違いますでしょうか。
C言語でコーディングしているわけではないので、そう単純に計算できるものではありません。
そのListに対して頻繁にGCを繰り返してCPUリソースを食い潰している可能性もあります。
ちなみにJavaVM内のメモリの利用割合って何かで調査できるもんなのでしょうか。
MemoryMXBeanクラスを使ってチェックしてみて下さい。
なるほど、じゃあリストあきらめてStringBuilderで文字列結合だけでやるとか、ヘタしたらそれくらいのことが必要なのかも知れないですね。全件のIDを持たなきゃいけないのは要求仕様上どうしてもなので。。。
MemoryMXBeanクラスというのは、見たところVisualVMで見れる情報しか取れそうにないのですが、もっと詳細に取れるのでしょうか?例えば、どの型が何MBとか、そういう割合が見えればだいぶヒントになると思うのですが。。。
見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。
ある程度、分かっているようなので、↓レベルは、既にクリアしてますかね。
http://www.itmedia.co.jp/enterprise/articles/0905/27/news002.htm...
性能チューニングって、プログラムのコードだけで解決できないことも多いので、OS やらミドルウェアやらいろんなことを知って無いと、難しいですよね。
「チューニングガイド」とか、「tuning guide」とかでググると、それなりの情報が手に入りますが、具体的で、問題解決の即決するようなドキュメントには、なかなかお目にかかれません。
ある程度、大きな組織で仕事をやっているのであれば、開発寄りの部署で、もう少し具体的なチューニングガイドや性能改善の事例を持ってたりする(普通は、社外秘)ので、そちらに当たってみるのも手かと。
チューニングのための持ち駒を増やしておいて、後は経験を積むしかないのかなあ、と。
そういう意味では、「今」が良い機会なんじゃないかと思います。
全件のIDを持たなきゃいけないのは要求仕様上どうしてもなので。。。
どういう使い方をするのか分かりませんが、ID のリストをセションなど、寿命が長いインスタンスとして作る必要が無ければ、外部に吐き出しておく手はあります。
ファイルだと、多少性能は落ちるかもしれませんが、ある程度の件数以上だったら、というようなことをすれば、通常の検索による処理時間を大きく変えることにはならないと思います。
前の回答にも書きましたが、検索結果のうち、先頭の数レコードのエンティティと、全件のIDの値をそれぞれListに持っています。
これらをListにためないで、つまり空回りさせてみるというテスト、いい調査だと思います。さっそくあとでやってみます。できれば、フレームワークやライブラリではなく、自分のコードに問題があったほうが修正できるので、それを期待します。つまり、空回りさせたら、ヒープは全然増えなかった(増えたとしても、上限に達したときにGCが走ってストンと落ちて、、というのが理想ですね。
それから、起動パラメータ(JVMのチューニング)について、大変参考になりました。見よう見まねでやっているレベルを早く脱出したいので、この辺ももう一度見直してみます。本やWebの記事を読もうとするのですが、長くて長くて、途中で挫折してしまいます。一目見てサクっと理解できる図でもあればいいのですが、甘い考えですかね。。。