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% 程度削減出来た