ネットワーク負荷をCPUの各コアに分散させるRPS,RFS設定スクリプト

高負荷なネットワークアプリケーションにおいて、他のコアが空いているにも関わらず、CPU0 の softirq(%soft) に負荷が集中することがよくある。Linuxでロードバランサやキャッシュサーバをマルチコアスケールさせるためのカーネルチューニングでその詳細について詳しく解説されているが、対応策として、RPS (Receive Packet Steering)とRFS (Receive Flow Steering)の設定を行うことで、負荷をCPUの各コアに分散させることができる。

この設定は次の三つのコマンドで実現できるが、再起動すると消えてしまう。ここでは再起動しても消えないためにどうすればいいかを書いていく。

  • # echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
  • # echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
  • # echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

まず上記の3つの設定で使われている値の説明から。
rps_cpusにfを設定しているのは、fが2進数表記で1111なので、4コアを使用するという意味。もし8コアあるのなら、echo "ff"とすればいいが、CentOS6では実際のコア数より多い"fff"を設定しても自動で実際のコア数に応じた値になるようになる。そのため、コア数に関わらず適当に多めのecho "ffff"としておけばいいだろう。
rps_flow_cntとrps_sock_flow_entriesはRFSの設定値となる。rps_sock_flow_entriesは通常32768を設定するのが一般的なので固定値とする。rps_flow_cntはNIC キューごとのフロー数を設定でき、合計がrps_sock_flow_entriesを越えてはならない。NIC キューの個数は/sys/class/net/eth0/queues/rx-Nが何個あるかでわかるので、この関係性を式に表すと自動で計算ができる。

以上のロジックでinitスクリプトを簡潔に実装することで、CPUコア数やNICキュー数によらず適切に、起動時に必ずRPSとRFSの設定がされる。
rps_updateというinitスクリプトを以下のように作成する。startさえできればいいので、最低限の実装しかしていない。

initスクリプトに適切な権限を与え、chkconfigに登録する。

あとは正しく設定されているか確認する。

以上で完成だ。さて、ここではじめにあげたサイトの中で、RPSとRFSの設定の永続化方法を述べた別サイトへのリンクが張られている。今回そのサイトの方法を用いず、initスクリプトを作成した理由を述べる。
そのサイトでは/etc/udev/rules.d/70-persistent-net.rulesを用いて設定すると書かれていた。しかし、使用しているCentOS6の環境では、/etc/udev/rules.d/70-persistent-net.rulesが/dev/nullへのシンボリックリンクとなっている。/dev/nullへシンボリックリンクを張ると、CentOS 6.0 で NIC と ethX の対応を固定化するで書かれている効果があるため、現状を変更してRPS,RFSの設定を書くことはできない。

CentOS 6.0 のデフォルトでは、MAC アドレスと ethX を対応付けており、交換により MAC が変化すると、自動的に新たな対応関係が作られるようになっているため、ethX がずれてしまいます。対応関係は、/etc/udev/rules.d/70-persistent-net.rules に設定されます。(中略)この 70-persistent-net.rules を無効化します。少々、荒っぽいやり方ですが、ルールが自動生成されないように、/dev/null へシンボリックリンクを張ってしまいます。

-Linux