tom__bo’s Blog

MySQL!! MySQL!! @tom__bo

Systems Performance 読んでいく (5章 その2)

Systems Performance: Enterprise and Cloudを読んでいく。

前回の続き。
5章後半部分。

5.4 Methodology and Analysis

このセクションでは、アプリケーションの分析とチューニングのための方法論について説明する。
分析に使うツールはここで紹介するか他の章を参照する。このトピックについては表5.2にまとめる。

f:id:tom__bo:20161101225016p:plain

より一般的な方法論については2章を見るように。
システムリソースや可視化についてもこの章で述べている。
これらの方法論は個々にまたは組み合わせて追っていく。筆者の提案はリストになっている順番に試していくことだ。
これらに加えて、開発しているものの言語や特有の分析テクニックを探そう。
そうすることで、すでに知られている問題や簡単な解決策が見つかることも多い。

5.4.1 Thread State Analysis

この手法のゴールは、アプリケーションスレッドが時間を使っている部分を高いレベルで発見することである。
これは問題を即座に解決したり、他の部分の課題を発見することに役立つ。
これは、アプリケーションのスレッドの時間を意味のあるいくつかのステートに分割することでできる。

最小では以下の2つがある。

  • on-CPU : 実行中
  • off-CPU : 実行待ち。I/O・ページング中のもの

もし多くの時間がon-CPUで使われているのであれば、たいていはプロファイリングがすぐに問題を解決してくれる。(後で述べる)
これはたいていパフォーマンス問題なので、他の状態にはそれほど必要ではない。

もしoff-CPUで多くの時間をつかて散るのであれば、さまざまな他の方法を使うことになる。

[6つの状態]

以下にoff-CPUが多い時の開始点となる6つのステートを示す。

  • Executing : on-CPU
  • Runnable : 実行待ちのもの
  • Anonymous paging : 実行可能だが、anonymous pagingのために待機中のもの
  • Sleeping : I/O, ネットワーキング、ブロックなどのために実行待ちのもの
  • Lock : 同期ロック待ち
  • Idle : 負荷(タスク)待ち

これらは有用な最小限のものを列挙しているので、より詳しく分類することもできる。
パフォーマンスを買いs全剃るのは最初の5つで、そうでない場合は、アプリケーションがもっと多くの負荷を受け入れられる状態だろう。

  • Executing

    • ユーザかカーネル空間化を確認し、プロファイリングでその理由を探る。
    • プロファイリングでどの部分がどのくらいCPUを消費しているかを明らかにできるだろう
  • Runnable

    • この状態で時間を使っているということは、アプリケーションがCPUリソースを必要としているということである。
    • システム全体でのCPU負荷を確認し、アプリケーションにCPUリミットをかけていないか確認する
  • Anonymous paging(swapping)

    • メインメモリの不足がanonymous pagingと遅延を引き起こす。
    • システム全体のメモリの使用状況を確認し、その制限がないかを確認する。
    • メモリについては7章で詳しく説明する。
  • Sleeping

    • アプリケーションがブロックしているリソースを確認する。
    • これについては後の5.4.3章を見るように。
  • Lock

    • スレッドが確認しているロックを特定する。
    • そしてなぜそれほど長くロックしているのかを確認する。
    • おそらくロックを待つ処理で遅くなっており、こういった問題にはそれをよく知るデベロッパに聞く。

アプリケーションがどのように負荷に応答するかによって、しばしばSleepingとLockの状況がidleだったということに気づくこともあるだろう。
これは状態変数を待つことによるLock状態だったり、ネットワークI/OによるSleeping状態だったりする。
なので、大きいSleepingやLock状態を確認したらそれが本当はidleでないかを調査しよう。

このあとでは、これらのスレッドの状態をLinuxSolarisでどのようにちょスアするかを説明する(Solarisは省略)

[Linux]

executingを特定することはそれほど難しくない。
これはtop(1)の%CPUを見ればよい。
他のステートについては以下のように調査する。

Runnableについては"/proc/*/schedstat"にあるカーネルのschedstatsで追うことができる。
また、perfschedツールでrunnableとwaitingで使った時間を理解するためのメトリクスを得ることもできる。

annonymous paging(swapping)についてはかーねるのdelay aaccountingで計測することができる。
これはswappingとmemory reclaim(ページ回収)にかかった時間を別々に提供してくれる。

これらの方法は一般的ではないが、カーネルのドキュメントにもこれをするgetdelays.cのデモがある。
別の方法としては、DTraceやsystemtapがある。

Sleeping状態でブロックされているプロセスを大雑把に推測するには、"pidstat -d"を使うことができる。また、ブロッキングI/Oに酔って停止した時が見たければ、iotopコマンドも使える。
こうした問題にもdstatやsystemtapを使う理由は、すでに機能がそろっていることと、追加したい機能は自分で追加できるからである。

もし、アプリケーションが長いインターバルでSleeping状態で詰まっているとしたら、なぜ詰まっているのかをpstackを使って特定できる。
これはスリープしているスレッドのスナップショットを取ることで、ユーザがスタックを追跡できるようにしてくれるためである。

5.4.2 CPU Profiling

CPUのプロファイリングについては、DTraceとperf(1)とともに、6章で詳細に説明する。
ここではプロファイリングについて、アプリケーションの観点からのまとめを述べる。

CPUプロファイリングの糸は、アプリケーションがなぜCPUリソースを消費しているのかを明らかにすることである。
この効果的な方法は、on-CPUのユーザレベルスタックトレースをサンプリングし、それを結合することである。
スタックトレースによって取られたコードパスはCPUを消費する理由を高レベル、低レベルの両方から明らかにできる。

スタックトレースのサンプリングから離れて、現在実行中の関数をサンプリングするだけで、CPUを消費している部分を特定するには十分なこともある。
その例を6章のDTraceの例から以下に示す。

f:id:tom__bo:20161101225506p:plain

この例では、ut_fold_ulint_pair()関数がon-CPUのうちもっともサンプリングされている。
DTraceを含む、いくつかのプロファイリングソフトウェアでは、現在実行中の関数を呼び出した関数をも簡単に表示することができる。

一方で、インタープリンタやVM上のCPU使用率を知ることは難しい。
どうこれを解決するかは言語環境に依存するが、おそらくデバッグサポートがあるか、サードパーティ製のツールがあるだろう。

例として、DTraceにはustack helpersを使うことで、VMの内部をオリジナルのプログラムのスタックに翻訳できる。
例えば、JavaのonCPUのスタックはDTraceのjstack()で見られる。

5.4.3 Syscall Analysis

前の節では、CPUの状態をon-CPUとoff-CPUで分類して説明したが、これらはシステムコールの実行を元に学んだほうが便利で実践的かも知れない。

syscall時間は、I/O, Lock, または他のsyscallタイプも含んでいる。他の状態である runnable, annonymous pagingはこの単純化に含まないものとする。
もし、このどちらかが現れるのであれば、(つまりCPUのサチュレーションか、メモリのサチュレーションが起きている)それは、システム全体のUSEメソッドを通して特定できる。

システムコールを分析する意図は、どこで、システムコールが時間を使い、それがどのようなタイプで、なぜ粗レが呼ばれているのかを理解することである。

[Breakpoint Tracing]

旧来の分析法は、システムコールの入り口と変えるところにブレークポイントを挟むことだが、これではアプリコーションのパフォーマンスを大きく侵害してしまう。

[strace]

Linuxであれば、システムコールの分析あhstrace(1)コマンドで行える。例えば、

f:id:tom__bo:20161101225630p:plain

straceの特徴はこのように人間が読みやすい形でシステムコールの引数まで表示してくれることである。
-cオプションを使うことで、以下のように集計結果を見ることもできる。

f:id:tom__bo:20161101225647p:plain

[Bufferd Tracing]

buffer tracingを使うことで、実装に関するデータを対象のアプリケーションを止めずにカーネルにバッファすることができるようになる。
これは、都度アプリケーションを止めるbreak point tracingとは大きく異なる。

Dtraceは、buffered tracingもそのオーバーヘッドを軽減した集計も提供し、かつsyscall analysysのために書かれたカスタムなプログラムを用いることも可能にしている。
以降では、LinuxSolarisで使えるDTraceのワンライナーを紹介していく。
さらなるれいについては付録Dをみるように。

次のワンライナーはkill()システムコールによるプロセスのシグナルをトレースしている。
このトレース結果では、bashの"kill -9"とPostgreSQLのシグナルが発見されている。
次の例では、"Postgres"という名前のついたプロセスのシステムコールを集計している。

次の例では、PostgreSQLのread()システムコールにかかった時間を計測している。 このワンライナーで使われているtime stampをvtimestampに変えることで、システムコールによってCPUを使った時間だけを計測することができる。
これはduration timeを比較することで、システムコールカーネルコード内で時間を使ったのか、I/Oに対するブロックで時間を使ったのかを見るために使うことができる。

より高度なDTrace Scriptがsyscall timeを違った方法で計測するために書かれている。

以下に例を示す

  • dtruss
  • execsnoop
  • opensnoop
  • procsystime

これらは様々なパフォーマンスの問題を解決してくれる。また、しばしば高いレベルの問題を変えたり、なくしたりしてくれる。
このような解決方法はworkload characterizationと言い、ここでのworkloadとはアプリケーションのシステムコールである。

5.4.4 I/O Profiling

CPUプロファイリングと同様に、I/Oプロファイリングも何故、どのようにI/O関連のシステムコールが呼ばれているのかを特定することが目的である。
これは、システムコールを知るためにユーザレベルのスタックトレースをDTraceで調べることでできる。

例えば、以下のワンライナーの例では、PostgreSQLのread()システムコールをトレースし、ユーザレベルのスタックトレースを集計している。

f:id:tom__bo:20161101230135p:plain

この出力はユーザレベルのスタックトレースとその発生回数を示している。 あなたはおそらくソースコードを理解する必要があるだろうが、ある程度はその名前から推測ができるだろう。
XLogReadはDBのログ関連だろうし、PgstatCollectorMain.israはモニタリング関連だろう。

スタックトレースはなぜシステムコールが呼ばれたのかを示してくれる。
これは、2章でも紹介したworkload characterization法にも有用である。

  • Who : プロセスID、ユーザ名
  • What : I/O syscall ターゲット、I/Oサイズ、IOPS, スループット
  • How : 実行全体でのIOPS

5.4.5 Workload Characterization

アプリケーションはCPU, Memory, file system, disk, networkと行ったシステムリソースを使って負荷を処理する。
こういったもの全てはworkload Characterizationをつかって理解することができる。
これは2章でも紹介し、後の章でもする。

加えて、アプリケーションに負荷をかけている側の理解も必要である。
これは、アプリケーションが受けている処理に注目するが、たいていパフォーマンスのキーメトリクスであったり、キャパシティプランニングに使われているだろう。  

5.4.6 USE Method

2章で紹介したように、USE Methodは、utilization, saturation, and errors of all hardware resoursesを確認する。
多くの場合は、これらを確認することで、ボトルネックがわかる。
ソフトウェアに置いても、それぞれのコンポーネントで適したU/S/Eを確認できればこれを適用できる。
例えば、リクエストを処理するためのワーカースレッドがプールされていて、リクエストがそこにキューイングされているシステムの場合、以下のようにメトリクスが定義できるだろう。
- U - 全スレッドのうち、一定のインターバルでリクエストを処理中だったスレッドの割合 - S - 一定期間でキューに溜まっていたリクエストの数 - E - 拒否されたり失敗したエラーの数

あなたのタスクは、こういったメトリクスをどのようにすれば計測可能かを見つけることである。

次の例として、ファイルディスクリプタについて考えてみる。
以下の3つが考えられる。

  • U
  • S
    • OSの挙動によるが、ファイルディスクリプタのためにスレッドをブロックするようなシステムの場合、ブロックされているスレッドの数
  • E

この方式は短いチェックリストを創りのと、他の方法に入る前のヘルスチェックとして有効である。

5.4.7 Drill-Down Analysis

drill-down分析あhアプリケーションがどのような処理をしているかに始まり、それをどのように行っているかを掘り下げていく方法である。
I/Oに対しては、システムライブラリ -> システムコール -> カーネルというように掘り下げていく。
これらはDTrace, SystemTap, perf(1)で行うことができ、ライブラリコールに対してはltrace(1) (Linux)やapptrace(1) (Solaris-based)などがある。

5.4.8 Lock Analysis

マルチスレッドアプリケーションでは、並列化かスケーラビリティに対してロックがボトルネックとなる。
これらは以下によって分析ができる。

  • 競合(Contention)の確認
  • excessive hold time

1つめのcontentionを確認することに酔って、今、問題があるかを特定できる。
excessive old timeは必ずしも問題とは限らない。
このどちらに置いても、ロックの名前を特定し、どこでそれを取得しているのか確認しよう。
ロックの分析には特別なツールがある一方で、プロファイリングで解決できることもある。
スピンロックは比較的簡単に見つかる一方で、adaptive mutex lockの場合は、プロファイリングでは一部しか見られない可能性があることに注意する。
特別なロックの分析ツールとしてSolaris-basedなシステムでは、plockstatとlockstatがある。 (これら2つの紹介は省略)

5.4.9 Static Performance Tuning

静的パフォーマンスチューニングでは、環境設定に関する問題に注目する。アプリケーションのパフォーマンスの問題であれば、以下のような側面から設定することが考えられる。

  • アプリケーションのバージョンは何か、それに関する既知のバグ情報はあるか?
  • 既知のパフォーマンス上の問題はアプリケーションにあるか?それらに関するバグ情報はあるか?
  • アプリケーションはどのように設定されているか?
  • デフォルトの設定と異なる設定やチューニングがされている場合はその理由は何か?
  • アプリケーションはキャッシュを行っているか?そのサイズはどの程度か?
  • アプリケーションは並列化されているか?どのように設定されているか?(プーリングを行っているかなど)
  • 特別なモードで実行されていないか?(デバッグモードなど)
  • アプリケーションはシステムライブラリを使っているか?そのバージョンは何か?
  • どのメモリアロケータを使っているか?そのバージョンは何か?
  • ヒープのためにラージページを利用しているか?
  • アプリケーションはコンパイル言語を利用しているか?コンパイラのバージョンや最適化に問題はないか?
  • 現在エラーはないか?エラーのせいで性能が落ちていないか?
  • CPUやメモリ、ファイルシステム、ネットワークにシステム上の上限はないか?

これらの質問によって、設定項目を俯瞰して確認することが出来る。