2015/09/27

Hadoop : CPU system 使用率高騰 "zone_reclaim_mode = 1" 編

はじめに


 会社で PB 級の Hadoop クラスタを運用していますが、ある日から Datanode の CPU system (Kernel 内での CPU 使用率) が高騰し、Job が遅延するという症状が発現しました。Hadoop で CPU system 高騰というと、 Transparent HugePage 設定が有名ですが、そちらについては既に特定し、対策済みでした。 THP と Hadoop に関係については下記 Blog が詳しいです。
Transparent Huge Pages and Hadoop Workloads

 今回は THP ではなく、 "zone_reclaim_mode" の設定による性能劣化について、現象から原因特定に至るまでの経緯と、推奨する設定について解説します。

現象


 観測された現象について簡単に箇条書きします。
  1. CPU user が 5% 程度の時でも CPU system が30% を超えるなど、 Kernel 内での CPU 使用率が異常に高かった
  2. CPU 使用率高騰により、いくつかの Job 実行時間が、問題発生前と比較して 1.5 倍に増えた
  3. 一部のマシンで発生し、他のモデルのマシンでは発生しなかった

perf による原因調査


Kernel 内での CPU 使用率が高騰した際には perf と呼ばれる Linux Profiling Tool が非常に有用です。特別な準備をする必要なく、簡単に Profiling を取得できます。
 今回は Profiling により、 "どの関数で CPU を使用しているのか" 、 "どの処理で問題の関数が呼ばれるのか(Call Graph)" を調査します。

perf での Profiling 取得

 CPU system が高騰したタイミングを見計らい、下記コマンドでプロファイルを取得します。
perf record -F 99 -a -g -- sleep 30
取得した結果を表示。Call Graph が取得できます。
perf report

結果の一部:
-  33.01%             java  [kernel.kallsyms]                     [k] _spin_lock_irq
   - _spin_lock_irq
      - 98.89% shrink_inactive_list
           shrink_mem_cgroup_zone
           shrink_zone
           zone_reclaim
           get_page_from_freelist
         - __alloc_pages_nodemask
            - 89.56% alloc_pages_current
               - 79.46% __page_cache_alloc
                  - 99.76% grab_cache_page_write_begin
                       ext4_da_write_begin
                       generic_file_buffered_write
                       __generic_file_aio_write
                       generic_file_aio_write
                       ext4_file_write
                       do_sync_write
                       vfs_write
                       sys_write
                       system_call_fastpath
                     + 0x3fda40e6fd
               + 17.54% tcp_sendmsg
               + 1.75% __get_free_pages
               + 1.25% pte_alloc_one
            + 9.29% alloc_pages_vma
            + 1.15% kmem_getpages
+  14.24%             java  [kernel.kallsyms]                     [k] _spin_lock
+   4.75%             java  libjvm.so                             [.] SpinPause
+   4.03%             java  perf-1947.map                         [.] 0x00007fd9550209cd
+   2.64%             java  libsnappy.so.1.1.3                    [.] snappy::internal::CompressFragment(char const*, unsigned long, char*,
+   2.01%             java  libjvm.so                             [.] ParallelTaskTerminator::offer_termination(TerminatorTerminator*)
+   1.84%             java  [kernel.kallsyms]                     [k] __isolate_lru_page
+   1.58%             init  [kernel.kallsyms]                     [k] intel_idle
...

 ちなみに、上記の Call Graph を可視化した FlameGraph の画像は以下です。

perf 結果からわかること

  1. CPU system を高騰させているのは "spin_lock*" 関数であること
  2. "spin_lock*" 関数は "メモリ回収処理" の延長で呼ばれていること

 つまり、メモリが足りなくなったために、メモリ回収処理があまりに頻繁に呼ばれ、spin_lock のオーバヘッドが高騰したことが予想されます。さて、メモリ使用量について改めて観測すると、半分程度しか使用していません。にもかかわらずメモリ回収処理が頻繁に呼ばれるのは何故でしょうか...

 Linux のメモリ解放に関連するパラメータを洗い出してみると、一つの気になるパラメータがありました。"zone_reclaim_mode" です。有効になっている場合、NUMA 環境で zone 毎のメモリ回収が積極的に行われるようになります。デフォルトでは無効のはずですが、今回の該当マシン(CentOS 6系)で調べてみると、なんと有効になっていました。
zone_reclaim_mode について詳細: https://www.kernel.org/doc/Documentation/sysctl/vm.txt

zone_reclaim_mode 無効設定の結果


/proc/sys/vm/zone_reclaim_mode に 0 を設定したところ、問題の CPU system 高騰は収まりました。Job の実行時間も元の水準に戻りました。

なぜ zone_reclaim_mode が有効になっていたか


zone_reclaim_mode は一部のマシンで有効になっており、 CPU system が高騰していない別のマシンでは無効になっていました。同じ OS を使っていたのに、なぜ設定に違いが出たのでしょうか。Kernel のソースコードを読んで調べてみましょう。Kernel の Version は CentOS6(2.6.32-431.11.2.el6) とします。

 zone_reclaim_mode は default = 0 ですが、NUMA 環境における Node 間の距離(RECLAIM_DISTANCE) の値によっては、Kernel 起動時に 1 に修正されてしまうようです。
  mm/pagealloc.c
3096         /*
3097          * If another node is sufficiently far away then it is better
3098          * to reclaim pages in a zone before going off node.
3099          */
3100         if (distance > RECLAIM_DISTANCE)
3101             zone_reclaim_mode = 1;
 その閾値は "20" とあります。
 57 /*       
 58  * If the distance between nodes in a system is larger than RECLAIM_DISTANCE
 59  * (in whatever arch specific measurement units returned by node_distance())
 60  * then switch on zone reclaim on boot.
 61  */      
 62 #define RECLAIM_DISTANCE 20

 有効になっていたマシンの NUMA Node 距離を調べると... 21
$ numactl --hardware
...
node distances:
node   0   1 
  0:  10  21
  1:  21  10 

 無効になっていたマシンでは 20 でした。
 要するに、ハードウェア構成によっては zone_reclaim_mode が自動的に有効になってしまう様です。

最新の Kernel ではどうなっているか


ちなみに最新の Kernel では 下記 Commit により、 "ハードウェア構成によっては zone_reclaim_mode を自動で有効とする" 挙動が無効に変更されています。
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=4f9b16a64753d0bb607454347036dc997fd03b82

他のソフトウェアでの推奨値


メモリを大量に使う DB 等のソフトウェアでの推奨値はやはり "無効" のようです。

まとめ

  • Hadoop Datanode での CPU system 高騰原因を perf を使って調査した
  • 原因: zone_reclaim_mode = 1 によるメモリ回収処理多発
  • ハードウェア構成によってい zone_reclaim_mode のデフォルト設定は変わる
  • Hadoop 含め、NUMA 環境でメモリを大量に使うソフトウェアで CPU system が高騰していた場合、 zone_reclaim_mode 設定を確認する

2015/07/16

FreakOut DSP 入札サーバの CPU 使用率を 30% 削減する Performance Tuning

はじめに


 勤務先の FreakOut 社では RTB で広告枠を買い付ける DSP の開発・運用を行っています。RTB とは、インターネット広告のインプレッションが生じる毎に、広告枠の競争入札を行う仕組みです。 DSP とは、 RTB において、競争入札をする側のシステムになります。広告枠/広告を見ている人 に対し、最適な広告を、最適なタイミングで届ける機能を広告主に提供する仕組みです。
 FreakOut DSP は最適な広告探索・入札価格調整のため、非常に多くのデータを参照し、沢山の演算処理を行います。広告を見ている人が過去にアクセスした Web ページの情報や検索ワード、さらに 広告がクリックされる予測確率(過去のログから機械学習で算出) などを参照し、入札価格を決定するのです。そのため、DSP で入札を担当するサーバは CPU がボトルネックになっており、台数も数百台に嵩んでいます。インフラコストの大部分を占めるのが入札サーバなのです。

 今回の記事では、入札サーバの CPU の使用率を 30% 程度削減した Performance Tuning 手法についてお伝えします。


入札サーバ実装概要


 ユーザに広告を素早く届けるため、入札サーバではレスポンスタイム 50ms 程度が求められます(50ms or die)。高速に動作する必要があるシステムですが、FreakOut DSP は Perl で実装されています。リクエストは Nginx + Starlet で処理されます。詳しくは下記のリンク先、 @myfinder さんの資料をご参照ください。3年ほど前の資料ですが、大きくは変化していません。


 Starlet は Prefork 型の Web App サーバであり、多数の worker process がリクエストを順次捌く構造を持っています。

Tuning の要点


htop、mpstat -P ALL 等のコマンドで Starlet の worker process の挙動を観察すると、すべてのコアを公平に使えていないことが分かりました。また worker process が CPU コア間を移動する(migration) 頻度も多いのです。これらは Linux Kernel の Process Scheduler の仕様によるもので、多くのユースケースでは特にチューニングせずとも良好な動作をするのですが、 入札サーバのように、Prefork 型でかつ CPU バウンドなワークロードでは、チューニングを施す方が性能が改善するケースがあります。
 CPU コアを公平に使えず、コア間の負荷に偏りがあると、負荷の高いコアではプロセスの Context Switch が頻繁に発生してしまいます。Context Swtich が頻発すると、 CPU Cache を有効に使えないため、オーバヘッドが嵩みます。
 また、プロセスが CPU コア間を移動すると、これまた CPU Cache を有効に使えないため、オーバヘッドが同上、となってしまうのです。

 上記の観測から、CPU Cache を最大限有効活用する Performance Tuning を下記2点、実施しました。

Tuning1. worker process のコア固定化


 worker process が生成された直後、プロセス内で sched_setaffinity システムコールを発行し、動作する CPU コアが一様に分かれるよう CPU コアを固定化しました。これにより、worker process が CPU コア間を移動することは無くなります。

Tuning2. Linux Kernel Scheduler Parameter 調整


 Linux Kernel にはプロセス Scheduler の挙動を調整する Parameter があります。
 代表的なものに sched_min_granularity_ns があります。これは、ざっくり言ってしまうと Process Scheduler が1つのプロセスを動作させ、次のプロセスに切り替えるまでの時間の最小単位です。つまり、sched_min_granularity_ns が小さいと、頻繁に Context Switch が起こり、逆に大きいと、Context Switch の頻度が少なくなります。
 類似の Parameter で重要なものに sched_wakeup_granularity_ns があります。プロセスが頻繁に wakeup と sleep を繰り替えすケースで Context Switch の頻度を調整する Parameter で、小さくすると Context Switch が頻繁になり、逆に大きくすると、Context Switch の頻度が少なくなるのは sched_min_granularity_ns と同様です。入札サーバは CPU バウンドとはいえ、memcached へのアクセスもある程度生じるため、wakeup/sleep による Context Switch も考慮する必要があります。

 一般にリアルタイム性の求められるシステムでは、プロセスの応答時間を短くするため、 これらの値を小さくします。

 今回実施した Tuning では これらの値を大きくしています。
「"50ms or die" なんだったらリアルタイム性が要求されるのでは?」 と思う方もいらっしゃるかもしれませんが、逆です。"50ms" は OS Scheduler から見れば非常長い時間であり、Context Switch が頻発すればそのオーバヘッドがバカにならないのです。オーバヘッドは切り替えのみにかかる時間だけではなく、一度 Context Switch が発生すると、CPU Cache Hit Rate が落ち、多くの CPU 時間を無為に使ってしまうのです。

 Tuning の実施にあたっては tuned を活用しました。tuned-adm の Profile: throughput-performance で設定しました。tuned-adm は 上記の Scheduler Parameter を含め、諸々良しなに設定してくれます。上記の Parameter は以下です。

  sched_min_granurarity_ns : 10000
  sched_wakeup_granurarity_ns : 15000

Scheduler Parameter について詳しくは doc/Documentation/scheduler/sched-design-CFS.txt 、Linux Kernel Watch の記事、及び Kernel のソースコードをご参照ください。

 他にも細かいところで Tuning している箇所がありますが、今日のところはこのあたりで。


Tuning 結果


 Tuning 1. の実施により 20%、Tuning 2. の実施により 10% 、合計 30% 程度の CPU 使用率を削減することができました。
 また、入札のレスポンスタイムも、平均 30ms 程度のものを 20ms まで低減することが出来ました。

余談: Tuning 1. の着想 Erlang VM(BEAM)


 Starlet の worker process コア固定化のチューニングは実は Erlang VM(BEAM) のオプションパラメータに着想を得ています。Erlang VM には Scheduler Thread をコア固定するオプション(+sbt) があり、以前このパラメータの有無で何らかのベンチマークを取ったところ、5% 程度性能が向上した経験が元になっています。(何のベンチマークだったかは忘れました)

補足


 「この記事で紹介した Tuning を実施するこどで Starlet が動作するサーバの CPU 使用率を 30% 削減できる」ということは保証されません。入札サーバのように、極端に CPU バウンドなサーバに対して効果のある Tuning です。

まとめ


- 入札サーバがインフラコストの大部分を占めていた
- CPU Cache を有効活用するため、プロセスの CPU コア間の移動や Context Switch によるオーバヘッドを削減する Tuning を施した
- 入札サーバの CPU 使用率を 30% 程度削減出来た