BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル ボックス--パフォーマンス・ボトルネックを探し出す近道

ボックス--パフォーマンス・ボトルネックを探し出す近道

ブックマーク
パフォーマンス上の問題が報告される時、防御手段に凝り固まったコメントがついてくることがとても多く、そして、そんなコメントのほとんどは、どこから作業を始めるべきかを理解する上で何の役にも立ちません。このジレンマに直面し、根本的な原因から推量し始めるチームも珍しくありません。ここで「ボックス」の登場です。ボックスはシステム全体を抽象化した小さな図式です。パフォーマンス・ボトルネックの実情を思い出させてくれます。厳密な調査と併用すれば、ボトルネック発見から当て推量を排除するのに役立つでしょう。矛盾と意外性を除外したいのなら、推量の排除は必須です。

システムレイヤのケーキ

図1で描いたボックスには4つのレイヤがあります。レイヤはそれぞれ、人、アプリケーション、Java仮想マシン、ハードウェアと名前がつけられています。このボックスを詳しく見ていくにつれ、システムパフォーマンスに対して各レイヤーが果たす主な役割も見えてきます。パフォーマンス・ボトルネック発見の取り組みを整理する上で、ボックスがどのように役立つかについて考えます。


図1. ボックス

人を変えると、問題も変わってします、これはパフォーマンスについてこれまで言われてきたことです。意味するところは、パフォーマンス・ボトルネックはシステムにかかる負荷に敏感ということです。ボックス内のレイヤを変更すると、最終的には違うシステムができあがるので、ボックスに「人」を入れるのはつじつまが合っています。「人」レイヤが人を表すだけでは不十分です。「人」はさらに、バッチプロセスやその他のシステムなど、システムを動かすあらゆるものを表します。こうしたもののすべてがシステム内のその他のレイヤに要求を出し、要求は他のレイヤによって提供される少ないリソースを順繰りに消費するのです。

このことから、システムを理解するには、まず「人」が何をしているのかを理解しなければならないということが分かります。理解すれば負荷テストの設定に役立ち、この設定がボトルネックを見極める上で非常に重要になるのです。UMLにはアクタと呼ばれるものがあるじゃないか、と思っている方がいらっしゃるかもしれません。アクタの役割はシステム上で正常動作しない力を説明することです。間違ってはいませんが、UMLのアクタはこれから起こることを描写するものであり、負荷テストの設定に必要な他の情報が欠如していることが多いのです。

優れたシミュレーションを創り出すために知っておかなければならないことは、ユーザ数、ユーザが行っていること、行う頻度、行う時期です。次のようなシナリオも検討する必要があります。交替時間(シフト)の最初と最後に行う活動、季節的傾向、特別イベント、毎日行われる午前2時のバックアップアクティビティです。この情報をすべて収集し、スクリプト化して負荷テストツールに入れてしまえば、ボトルネック発見プロセスを開始する準備が整ったことになります。

アプリケーション

「人」のすぐ下のレイヤは「アプリケーション」です。私はこのレイヤーを「人」と「JVM」「ハードウェア」間のマッピングと考えます。これまで如才なくやってきたか、ラッキーであれば、効率よいマッピングをなんとか定義できていることでしょう。効率よいマッピングとは、基本リソースの利用を最小限度にとどめた、「人」の要求を満たすマッピングです。ありふれた言い方をすれば、システムが効率的ということです。本や記事、助言が言うほど、また体全体で感じるほどシステムが効率的でない場合は、調整が必要なのはこのレイヤーです。ですから、ボックスを無視してここから始めたらどうでしょう。

数百人のデベロッパに非常にシンプルなパフォーマンス・チューニングの課題を与えたことがあります。その課題のささやかな目標は、当該メソッドを3倍の速度で動作させることでした。非科学的な観察でしたが、それでも驚くべきものでした。30分後にボトルネックを見極めることができたのは、全参加者の2%未満でした。ボトルネックを見極めた2%の参加者の圧倒的多数は、コードを無視し、ボックス内の下のレイヤが物語っていることに注目したのでした。この非科学的な観察から導いた結論は、ボックス下部のレイヤを確認し終わるまでは、コードを無視せよ、ということです。コードに手を出す時になっても、プロファイラを手引きとして使うべきです。

Java仮想マシン

「アプリケーション」が「人」と「JVM」間のマッピングと解釈できるように、「JVM」は「アプリケーション」と「ハードウェア」間のマッピングと見なすことができます。JVM内のコードを変更するという選択肢は存在しませんが、JVMそのものを変更する選択肢はあるかもしれません。一番起こりそうなことは、多数のコマンド行スイッチの数を設定することにより、JVMをコンフィギュア(チューニング)する作業を行うことです。不適切にコンフィギュアされたJVMはアプリケーションに人為的なリソースの枯渇をもたらし、アプリケーションパフォーマンスに甚大な負の影響を与える可能性が高いでしょう。

ハードウェア

ボックス内の最後のレイヤはハードウェアです。静的なレイヤーであり、能力に限界があります。CPUが1秒間に処理できる命令には限りがあり、メモリに入れられるデータにも、I/Oチャンネルのデータ転送速度にも、ディスク容量にも定められた限界があります。ハードウェアに十分な能力がなければ、アプリケーションパフォーマンスに支障をきたすなど、わざわざ言う必要もないでしょう。ハードウェアがパフォーマンスに与える直接的影響を考えると、すべての調査をハードウェアから始めなければなりません。

カーネルの酷使

システムで4つの主要リソース(CPU、メモリ、ディスク、ネットワークI/O)が不足していないかどうかを調べるツールは多数あります。Windowsシステムでは、図2にあるように、単純にタスクマネージャをオンにすればよいのです。タスクマネージャが常に表示しているのは、アプリケーションがCPUを100%消費しているか、100%消費できないでいるかです。これでは判断の手掛かりにはならないような気がしますが、他のちょっとした情報と組み合わせた場合は特に、実はとても貴重なヒントになるのです。

図2 Windowsタスクマネージャ

図2を見ると、CPUが活発に、実際は非常に活発に動作中であることが分かります。しかし、スパイク波形が時折ある以外は、100%未満の利用で動作しています。CPU利用グラフに見られる緑の線の下にある赤い線にご注目下さい。赤い線は、Windowsカーネルが消費しているCPU量を表します。通常はシステムのCPU利用が全体の20%未満となるようにしたいのです。それ以上になると、本来よりもOSに過酷な動作を強いる原因が、アプリケーションコードのどこかにあることを示しています。何を理解する必要があるのかを知るには、カーネルが何をしようとしていたかを知る必要があります。カーネルが何をしているかを理解すると、コードの中で何に注視すればよいのかを理解する上で非常に役立ちます。

カーネルはコンテクストスイッチング、スレッドスケジューリング、メモリ管理、インタラプト処理などのタスクを行います。こうしたアクティビティのすべてにCPUを利用する必要があります。メモリ管理のタスクを見てみましょう。システムメモリへの要求が増大すると、カーネルはたいてい、メモリからディスクへとページを退避させます。アプリケーションがページを参照した場合、カーネルはページの交換もしなければなりません。ほとんどの場合、退避させるページは、最低使用頻度のページです。そのページを探すには、CPUを使う必要があります。ディスクI/Oチャンネルを使う必要もあります。その後の段階でディスクの読み込みと書き込みが行われると、カーネルが機能停止する原因となります。機能停止したカーネルと機能停止したアプリケーションは、CPUを使いません。しかし、メモリ利用が限界になれば、カーネルはディスクI/Oと突き合わせて、どのページを退避させようかと頻繁に算定するようになります。このスキャニングアクティビティは、システム利用数の原因となっている可能性があります。

図2の下のパネルを見ると、このシステムにはメモリが十分あり、大部分が未使用であることがすぐに分かります。したがって、(ある程度の自信を持って)問題の原因からメモリ欠乏を排除できます。この発見を確証するには、もう少し作業が必要です。ここではそれをせずに、次の候補のコンテクストスイッチングに移ります。

スレッドはレギュラーベースで、CPUに出たり入ったりと交換が行われます。このアクティビティのお陰で、同時に多数のプロセスを動作させることができるのです。各スレッドに与えられるタイムクォンタムは固定されています。あるスレッドがタイムクォンタムを使い切ってしまうと、別のスレッドが取って代わります。このアクティビティのすべてを管理しているのが、カーネルの中で動作しているスレッドスケジューラです。この作業を行うには、カーネルはCPUを使わなければなりません。普通の条件下であれば、スレッドのリスケジュールに関係した作業はほとんど注意を引かないようなものです。ところが、条件によっては、タイムクォンタムが終了する前にスレッドがCPUから削除されることもあります。よく起こるのは、I/Oでブロックされたり、ロックを待たされたりすることです。CPUから繰り返しスレッドが削除されると、スケジューラーの作業が大変になります。スレッド削除がとても頻繁に起これば、カーネルのCPU利用頻度が高くなり、アプリケーションパフォーマンスへ影響を与える原因となります。

図2のグラフの場合、CPUでコンテクストスイッチングを早期かつ頻繁に行ったことが、CPU高利用の原因となった可能性が大きいのです。このことを理解しておけば、問題のさらなる調査と特徴付けを行う上で、よい一歩を踏み出すことができます。ここまでで、動作の特徴付けを行い、こうしたタイプの問題はどのタイプのコードが原因で起きているか、何らかの見当がつけられるようになりました。現時点でコードの中身を調べ、早すぎるコンテクストスイッチの原因を探すことはできますが、このほかにも可能性は非常にたくさんあります。コードを探り始める前に、問題をさらに特徴付けるためにボックスを手引きとして使った方がよさそうです。たとえば、モニタリングの照準を再度調整し、ネットワークやディスクアクティビティを調べたり、あるいは、ロックに圧力がかかっているかを調べたり、などです。ここでも、カーネルが保持しているカウンタを読み取るツールを使うか(WindowsとUnixの両方)、VTune(Intel)、CodeAnalyst(AMD)などのツールに頼ることができます。

CPU利用が問題となる場合もありますが、可能性としては、I/Oもしくはロックで束縛されたアプリケーションが非常に強いCPU回避を見せる方が大きいのです。この回避が余りにも強いと、負荷が増すにつれてCPU利用状況が実際は減少してしまうことになります。しかし、カーネルのCPU利用がCPU全体の利用でかなりの部分を占めていることがお分かりになるでしょう。

ゴミ出し

ハードウェアの調査に加えて、JVMがもたらす影響も検討する必要があります。JVMが提供する主なリソースは、スレッドとメモリ(Javaヒープスペース)です。ヒープが大きければ、アプリケーションを長時間「機能停止」させる原因となります。小さなヒープですと、短い機能停止が頻繁に起こります。どちらのケースも、メモリ管理、ガーベジコレクション(不要部分整理)のプロセスにはCPUを非常に多く使います。端的に言うと、不適切なサイズのヒープにより、JVMは通常必要とされるよりずっと多くの作業を行わなければならなくなり、作業中のJVMはアプリケーションからCPUサイクルを奪い取ります。システムが報告するところによると、JVMのCPU利用はカーネルのCPU利用とは異なり、ガーベジコレクションに費やす時間とアプリケーション動作に費やす時間に分割されるわけではありません。

ガーベジコレクションの効率(もしくは非効率)を計測するには、ガーベジコレクションアクティビティをモニタする必要があります。モニタするには、コマンド行にXverbose:gcフラグを設定します。こうすれば、ガーベジコレクション・アクティビティのサマリが標準出力に記録されます。GCログにある数字を使えば、GC処理能力(GC効率)を計算できます。

GC効率は、ガーベジコレクションに費やした時間をアプリケーションの動作時間で割ったものと定義されます。アプリケーション・ランタイムの通常過程では、GCは何千回も働きますから、TagtramのGCViewerもしくはHPのJTune(両方ともフリーソフト)といったツールを使って計測するのが一番よいでしょう。GC効率が10%を超えていたら、それはJVMヒープにチューニングが必要という兆候です。CPU利用が高く、GCの動作が好調なら、アルゴリズム上の非能率に問題がある可能性が大です。その可能性の診断は、実行プロファイラにお願いしましょう。ここでも、プロファイラに頼る前に、ボックスを調べて問題の絞り込みを試みるのが最善の策でしょう。

スレッドおよびスレッドプーリング

全要求から新しいスレッドを発生させるがままに放っておくと、動作良好のシステムが不安定化することは、アプリケーションサーバのベンダはずっと初期の頃から知っていました。解決策はスレッドプーリングの導入でした。スレッドプーリングは、処理できる要求数を制限することにより、アプリケーション内のアクティビティレベルを制限するよう働きます。スレッドプーリングの利点は、負荷のかかっているシステムが最大限の処理能力を維持ことです。負荷が高い場合、サービスが提供されるまで長時間待たなければならない要求もあります。スレッドプールのサイズは調整可能ですが、パフォーマンスに多大な影響を与えます。プールが大きすぎると利点が無効になってしまいます。小さすぎると、処理できたはずの要求も処理されなくなります。そうすると、応答時間が長くなります。適切なバランスで設定できたかどうかを確認する唯一の方法は、アクティブなスレッドの数、応答時間、重要なシステムリソースの利用レベルをモニタすることです。

小さすぎるスレッドプールを推断する方法はいくつかあります。たいていの方法では、所定の要求に要する完全往復時間を、サーバーの内部応答時間と比較します。ネットワーク待ち時間では説明しきれない大きな差異があれば、スレッドプールが小さすぎる可能性が大きいでしょう。反対に、説明できない大きな差異があり、かつプールが大きすぎるとハードウェアが示している場合は、所有しているハードウェアが単に十分でないか、非能率的なアルゴリズムによって損失を被っている可能性があります。どちらにしても、次回どこを調べたらよいか、価値ある見識を身につけたことになります。

ハードウェアを除外し、JVMのコンフィギュレーションも適切と判断したなら、検討場所として残っているのはロックコンテンションと、外部システムとのインタラクションのみです。どちらのケースも、スレッドが機能停止し、その結果「有益作業」が行えないという特徴を持っています。「有益」とカギ括弧でくくった理由は、クレジットカードサービスとのインタラクションは、有益な作業に違いないからです。とにかく、長時間の機能停止が物語っているのは、トランザクションは非同期で処理すべきであり、そうすればスレッドを解放して他の有益なタスクを実行できる、ということなのかもしれません。

コード回避

この下から上への調査プロセスでは、コードに目を通す前に、できる限りたくさんのボトルネックの原因を取り除くことを目標としていることが分かります。問題にプロファイリングツールを適用し始める際に、システムのモニタリングから得た手掛かりを使って、調査対象の絞り込みにも役立てたいのです。ほとんどの場合、最も実行したくないのはコードの検査です。実際、もしコード周辺の洗い出しが必要と感じるようなら、十中八九、厳密な調査場所を指し示してくれる適切な測定結果を入手していないのでしょう。確かに、コードの中をくまなく探さなければならないこともありますが、それは確実に避けたい行動です。なぜなら、コード内を調査すれば推量が生まれ、推量こそが全パフォーマンス・チューニング・アクティビティの災いのもとだからです。

結論

現実のアプリケーションは大量のデータを生み出し、そのデータを隅から隅まで探してボトルネックの可能性を見つけなければなりません。ツールが生み出す膨大なデータ量を考えると、近道して潜在的な問題を推量しようとするチームがあっても不思議ではありません。そうした当て推量が正しいこともありますが、同程度の頻度で間違いが判明することもあるのです。推量の難点は、結果に一貫性がないことです。ボックスの目的は当て推量を排除することですが、排除を実現するために、どのような流れで調査を行うかを示します。また、システム内の各主要構成要素で何が重要かを理解する上でも役立ちます。このボックスを使えば、パフォーマンス・ボトルネックを発見して排除するチームの能力を向上できるはずです。

著者について

Kirk Pepperdine氏はJavaのパフォーマンス・チューニング・スペシャリストです。アプリケーションのチューニングをしていないときは、パフォーマンス・チューニングについて教えているか、今回のような記事を執筆しています。

原文はこちらです:http://www.infoq.com/articles/the-box

この記事に星をつける

おすすめ度
スタイル

BT