ソフトウェア割り込みが偏る?
Linuxを利用していて、ネットワーク負荷が高いサーバを運用していると、特定のCPU負荷が高くなっている事があるのですが、そのようなケースを経験されたことはないでしょうか?
topでみると特定CPU(topを起動して1を押すとCPU単位で確認できる)の**%si(software interrupt)がやたら高くなっている場合、それはネットワークの負荷が原因かも知れません。(実際のtopを貼り付けたかったのですが、持ち合わせがなかった・・・。)何も設定していない場合はネットワークの割り込みは特定のCPUで行われるため、ネットワークの割り込みに関連づいたCPUの%siが高くなります。ソフトウェア割り込みを確認するには、/proc/interrupts**を確認しましょう。
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 129 0 0 0 IO-APIC-edge timer
1: 7 1 0 0 IO-APIC-edge i8042
7: 0 0 0 0 IO-APIC-edge parport0
8: 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 IO-APIC-fasteoi acpi
12: 108 1 0 1 IO-APIC-edge i8042
14: 22 1 39 21 IO-APIC-edge ata_piix
15: 0 0 0 0 IO-APIC-edge ata_piix
16: 0 0 0 0 IO-APIC-fasteoi vmci
17: 129013 482231 35345 34170 IO-APIC-fasteoi ioc0
18: 21809010 8 0 0 IO-APIC-fasteoi eth0
24: 0 0 0 0 PCI-MSI-edge pciehp
<< snip >>
※ 下から2行目でeth0がCPU0で処理されていることがわかります。
要するに、これを各CPUで分散させればOKなわけです。これらを分散させるにはハードウェア的に行う方法(高級?なNICが必要。Multi-Queue NICと言われている奴になります。詳細は「Receive Side Scaling (RSS)」で検索しましょう。ちなみにvmwareのvmxnet3はmulti-queue対応です。)とソフトウェアで実現する方法があるのですが、今回はソフトウェア的に分散させる方法をご紹介します。
RPS,RFSを設定しよう
機能的には「Receive Packet Steering(RPS)」と「Receive Flow Steering(RFS)」を利用します。RHELのドキュメントでは、下記に記載が有ります。6.1以降と記載が有りますね。(なので、CentOS6.1以降であれば使える。)
https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/main-network.html#s-network-future
RPSがRSSをソフトウェア的に実現したもので、分散先をより最適にするのがRFSとなります。
具体的な設定方法は、先のRHELのドキュメントにもありますがRPSの設定は「/sys/class/net/ethX/queues/rx-N/rps_cpus」へどのCPUへ分散させるかを指定します。ちなみにCentOS6.6で確認すると、rx-Nに加えてtx-Nも確認できます。きっと受信に加えて送信でも分散させることが出来るのでしょう。(ちゃんと調べてない) rx-Nはmulti-queue nicの場合は複数存在することになります。(つまりはrx-0しか無いケースが多い。)
rps_cpusに指定する値ですが、CPU数を2進数の10で表現し、それを16進数に直した値を指定します。わかりづらいですね。例えば4CPUであれば、4bitで1111(16進数でf)で全CPUで処理。0011(16進数で3)で2個のCPUで処理、と言った感じです。1001(9)なんかも行けます(これも2個のCPUだけど、1番目と4番目を使う)。
# echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
# echo f > /sys/class/net/eth0/queues/tx-0/xps_cpus
RFSの方は「/proc/sys/net/core/rps_sock_flow_entries」と「/sys/class/net/ethX/queues/rx-N/rps_flow_cnt」を設定する必要が有ります。rps_sock_flow_entriesはシステム全体のエントリー数、rps_flow_cntはNICのqueue毎のエントリー数を設定し、合計がrps_sock_flow_entriesを超えてはいけないようです。どれぐらいが適切かは、アプリやミドルウェア、使い方によってきそうですが、Unbreakable Linuxのドキュメントが参考になるかも知れません。
RFSを有効にするには、rps_sock_flow_entriesの値を現在アクティブな接続の最大予測数に設定し、rps_flow_cntの値をrps_sock_flow_entries/Nqに設定します(Nqはデバイス上の受信キューの数です)。入力値は、すべて2の最も近い累乗値に切り上げられます。適度な負荷のサーバーに対して推奨されるrps_sock_flow_entriesの値は、32768です。
# echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
# echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
設定したらtopで**%si**がばらけているか確認してみましょう!
実際効果があるかなどは実機で確認した方が良いです。ちゃんと確認しましょう。ばらけたのは確認できても、パフォーマンス自体は変わらないとかもありえますね。