Intel I350の受信バッファが最大値の4096でも溢れた

夏休みもいよいよ最終日となり、一息ついたところでI350の受信側のリングバッファが溢れた事象についてまとめます。

以前より3560Xのアップリンクポートでわずかながらパケット破棄が発生していました。
おそらくマイクロバーストによるものですが、最近新しいArista Networksのスイッチを導入したため、この症状自体は改善しました。

しかし上記の症状が安全装置になっていたのか、DMZ側でトラフィックを受けているLVSのパケット破棄が大量に発生するという現象が発生しました。
以下はその時のグラフです。

lv1

気づいた時にはドキっとしましたが、以前よりnet_devやigbのソースを一通り読んでいたため、カーネル側のキューか、NIC側のリングバッファ溢れだということはすぐ予想できました。
すぐにどちら側の問題か切り分けにはいりました。

まずキュー側です。
/proc/sys/net/core/netdev_max_backlog を超える値はキューイングされず、破棄されます。
影響範囲も少ないのでまずは何も考えずにふわっと増やしてみましたが、効果はありませんでした。

次にバッファ側を調べてみました。

このようにifconfigのoverrunsというなかなか上がらないカウンターが昇竜拳していました。

RX packets:1215382409979 errors:0 dropped:9836789 overruns:9836789 frame:0

値の元になっている/proc/net/devをみてみます。

$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
 bond1: 301302257145320 1242460031644    0 50134427 19769362     0          0  94522289 59829397999 1104977661    0    0    0     0       0          0
 bond0: 266789966151393 978716441978    0 95911204    0     0          0 142912972 564647908448725 2213999050962    0    0    0     0       0          0
  eth0: 266784042597227 978620538142    0 7402    0     0          0 142780453 564647908459099 2213999050966    0    0    0     0       0          0
  eth1: 5923561185 95903847    0 95903801    0     0          0    132519     1302      31    0    0    0     0       0          0
  eth2: 301300435242358 1242429666624    0 19769362 19769362     0          0  94522274 59829396211 1104977621    0    0    0     0       0          0
  eth3: 1821914034 30365078    0 30365065    0     0          0        15     1788      40    0    0    0     0       0          0
    lo: 6027064668 68489292    0    0    0     0          0         0 6027064668 68489292    0    0    0     0       0          0
bond0.11: 208950786744728 614004718922    0    0    0     0          0  88018817 487194387836941 1723708710469    0    0    0     0       0          0
bond0.12: 41205097680103 308328470394    0    0    0     0          0  50947732 72909647177800 412801455153    0    0    0     0       0          0

対応するカラムはfifoのところです。

fifoって意味はわかるけど、なんぞいやと調べたところFIFOバッファエラーということらしいです。
ethtool -Sでもエラーカウンターとして確認できます。

rx_no_buffer_count: 220474

参考サイト
https://nuclearcat.com/mediawiki/index.php/Intel_Gigabit_Performance#FIFO_buffer

つまり受信バッファが溢れているので、開けてやればいいということですね。
どうやるかというとNAPIはドライバが定期的にポーリングしてバッファの中身をとりにいくため、ポーリング頻度を上げるか、一度で処理する量を増やしてあげればバッファに空きができるはずです。
ただしCPUの負荷は上がります。
自社の場合NW I/O周りはひたすらCPUバウンドという経験がすでにあるため、LVSを新しく作る段階でCPUはXeon E5-1650v2というクロックの高いCPUを採用していたので余裕はまだまだあります。

以下の2がNAPIのバッファポーリングに関するパラメータです。

net.core.netdev_budget
net.core.dev_weight

テスト環境がない一発勝負なので、安全そうなnetdev_buger(処理数の上限アップ)を試してみたところ、見事にパケット破棄が消えてなくなりました。
また何故かわかりませんが、LAN側からのping監視に結構ジッターがあったのがなくなりました。
症状が改善した上に余計調子よくなってしまいました。
CPU負荷も目立って上昇はしていませんでした。

lv2

lv3

まあIntelのNICとはいえ、L3スイッチから全力で投げつけられたパケットを処理するのは箱出しパラメータだときつそうですね。
むしろよく動いてたと思います。

この勢いで10GネットワークでもLVSがまだまだ活躍することでしょう。

ウェブ広告のインフラについて(第4回) LVSのチューニング

多忙と現実逃避により間が開いてしまいましたが4回目です。
下書きの日付が5月付ですね・・・。

さて今回はLVSのチューニングですが、NICやドライバ廻りは前回と同じです。
IPVSのコネクションハッシュテーブルのチューニングが主な内容です。

netfilterのコネクションテーブルと同様にIPVSもコネクションテーブルをハッシュ化して高速に処理しています。
管理しているコネクションはipvsadm -Lncなどのコマンドで一覧が見られます。
この情報を元にすべての通信のコネクションオープンから、クローズまたはタイムアウトまでの処理が行われています。

デフォルト値だとこのハッシュテーブルが少なすぎるため、ハッシュの衝突が発生しどの程度かは未検証ですがパフォーマンスが低下します。
デフォルトだと大体10万コネクション程度が目安のようです。

衝突と聞いてどきっとするかもしれませんが、コネクションテーブルハッシュはチェイニングスキームを使用しているため、ハッシュが衝突したからといって直ちに影響はありません。
また単純にハッシュテーブルを広げるだけで衝突する確率を大幅に下げることができます。

ハッシュテーブルサイズを大きくするためにカーネルを再構築する必要があります。
昔のLVSを使うためにカーネルを再構築していた時代がありましたね・・・。
自社では運悪く入社早々カーネルのリビルドを押し付けた押し付けられた人がいました。

対象のメニューは以下のとおりです。
適切な値はハッシュテーブルは1コネクションあたり128byte、ハッシュエントリは8byteのメモリを要するとドキュメントにはありますので、必要なメモリ量が逆算できます。

Networking Support ->
Networking Options ->
[*] Network packet filtering framework (Netfilter) ->
IP virtual server support ->
(20) IPVS connection table size (the Nth power of 2)

また以下のようにnetfilterと同様にタイムアウトを短くすることでコネクション数を削減することができます。

ipvsadm --set tcp tcpfin udp(TTL/sec)

LVS自体シンプルにできているのでチューニングすべき箇所はあまりありません。
自身で一番こまったのは、この3つです(一つじゃない)。

  • weightを変えてreloadしてもweightが変わらないため野良パッチをあてたdebを作った
  • スイッチ跨ぎで発生するunicast floodingを美しく解決する方法がなく悩まされている
  • DRだとL2前提になりNW設計に制限がでる

自社ではDRを主に利用していますが、入り口と出口が分けられるという特徴を利用して、L3スイッチの負荷分散や、トラフィックのバランスが調整できたりと以外なところでも活用しています。

次回はLinuxのTCP再送アルゴリズムの実装と広告配信の相性の悪さについてです。

秋ぐらいに・・・。

LVS-DRに適さないトポロジでUnicast Floodが発生

LVS-DRで同一セグメント内のロードバランスというよくある構成でUnicast Floodが発生しました。
同じような症状は前から出ていたんですが原因が掴めずじまいでした。今回、急遽スイッチを増設したサーバからトラフィックを流したところ、関係ないサーバのトラフィックが跳ね上がり、調べたところUnicast Floodだということがわかりました。
そのときのトラフィックです。

tcpdumpしてみたところ、ユニキャストがブロードキャストされるというわけのわからない現象でしたが、Unicast Flood自体はあるあるのようです。

原因ですが、スイッチを増やしたことと、LVS-DRによりMACアドレスを学習できなかったホスト宛のパケットを、スイッチが全ポートに送信していたせいでした。
とりあえず全ポートに送信することによって誰かが受け取ってくれるということだと思います。
たしかにグラフを見るまでは通信も正常で、まったく異常に気づきませんでした。
このまま放置していたらスイッチが負荷で発狂していたかもしれません。

復習もかねて図にしてみました。

L2くらいになるとスイッチの個性が結構でますね。
CiscoはUnicast Flooding Blockなる機能があるそうです。

LVS-DRのほかにルーティングやスパツリの設定ミスでも起こるようです。
MACアドレステーブルのタイムアウトをオフにすれば解決すると思ったんですが、Procurveは極端に長い時間に設定はできても、無期限はできませんでした。

しょうがないので、クライアント側からリアルサーバ宛にcronで定期的にpingを打って、対岸のスイッチにMACアドレスを学習させるようにしました。
根本的な解決にはなっていませんが、とりあえずしのぎの運用だったし、今はよしとしました。

ネットワークはやはりトポロジが肝だということが改めてわかりました。
超常現象も知れば納得ですね。

ip_vsのhash tableについて調べてみた

ipvsadm -Ln しか使わない現場も多いと思います。
しかしip_vsについてちょっと気になって調べてみたらものすごい奥が深いことがわかりました。

自分の会社ではほぼすべてのトラフィックをグローバルIPを持たせたLVSマシンで受けています。
また提供しているサービスの特性で、ipvsadmのタイムアウトを短くしていても、多いときで30万ちょっとくらいのセッションがあります。

ipvsadm -Ln でも確認できるのですが、ipvsadm -Lnc | wc -l を以前から使っていました。
これはip_vsが保持しているセッションのリストを表示するオプションです。

とまあ、ここまでは理解していたんですが、 netfilterとかチューニングしてる過程でハッシュのチューニングとかしたし、何十万本もコネクションリストもってるip_vsも、実はコネクションリストを高速でスキャンするためのハッシュ領域があるんじゃね?と、思い調べ始めました。

ちなみに、ip_vsは新しいコネクションを作ろうとするたびにすべてのコネクションリストを評価する、とどこかに書いてありました。どこかに。何十万もセッションがある状態で、ハッシュが効率よく使われていないとしたら、恐ろしいCPUのリソースを無駄に消費してることになります。

調べたところハッシュの実体はすぐ見つかりました。以下の size=4096 です。

$ sudo ipvsadm -L                                                                                 
IP Virtual Server version 1.2.1 (size=4096)

果たしてこの値が最適なのか、変更できるかは以下のリンクを参考にしました。

http://daretoku-unix.blogspot.jp/2011/04/ipvs.html

IPVS 接続ハッシュテーブルはハッシュの衝突を扱うのにチェイニングスキーム
を使っています。大きなIPVS接続ハッシュテーブルを使うと100,000単位の
コネクションを扱う場合においてハッシュテーブルの衝突を大きく減らします。

デフォルト値ではせいぜい10万くらいがいいところというみたいです。
また実メモリの使用量なども書いてありますが、計算すればいいだけなので割愛します。

値の増やし方は以下のリンクを参考にしました。

http://kb.linuxvirtualserver.org/wiki/Performance_and_Tuning

カーネルを再構築しないとダメみたいです。

やたらめったらコネクションが多い場合は増やしたほうがいいということですね。
また自分の現場では設定済みですが、コネクションはタイムアウトを短くすることで減らすことができます。netfilterと一緒ですね。

ipvsadm --set tcp tcpfin udp(それぞれ秒)

そしてここまで調べて気になったのが”ハッシュの衝突”という表現です。

衝突したらコネクションは破棄されてしまうのか?もしかしてリハッシュとかしてCPUに余計なコストがかかってるんじゃないか?
など、気になり調べてみたんですが、ここまでくるとインフラではなくプログラミングのアルゴリズムの話です。

”ハッシュの衝突を扱うのにチェイニングスキーム”を使う、とドキュメントには書いてあります。
チェイニングスキームについてすぐ出てきたドキュメントが以下のリンクになります。(wikipediaですが・・・)

http://en.wikipedia.org/wiki/Hash_table#Separate_chaining

同じハッシュに複数の値が入るというアルゴリズムのようです。

プログラムをしっかり勉強した方ならわかると思いますが、ハッシュがコリジョンする確率とか、その場合の回避方法をいくつかしっていると思います。

ip_vsはパフォーマンスをできる限り優先するために、チェイニングスキームで多少コストがかかっても、生のコネクションリストをなめるよりはるかに早いので、多くのハッシュエントリーを確保して、コネクションリストをできるかぎり高速でスキャンするという設計思想みたいです。

ハッシュの衝突を回避するためには、そもそも箱を大きくすれば当たらないということですね。
デフォルトが小さすぎるんです。

ついでなんですが、32bitだと95%の確立を保てるのは15万前後のようです。

http://preshing.com/20110504/hash-collision-probabilities

興味深いですね。
誕生日パラドックスとか誕生日攻撃とか、ついでにいろいろ調べていて目に入りました。

知れば納得です。
こんどはnetfilterのハッシュの扱いについて調べてみようと思います。

当然ですがカーネルを再構築したところ確かにふえました。

$ sudo ipvsadm -L
IP Virtual Server version 1.2.1 (size=1048576)
Prot LocalAddress:Port Scheduler Flags
 -> RemoteAddress:Port Forward Weight ActiveConn InActConn

UC●MのLocusnetworksのスイッチがGARPを無視する場合の対処法

UC●Mが使用しているLocusnetworksの謎スイッチがkeepalivedのGARPを無視してフェイルオーバーできない場合の対処として、仮想MACを利用する以外にも比較的簡単な方法があります。

ちなみにL2おろしのサービスなので、MACアドレスが直で見れるので見てみたところLocusnetworksという謎のスイッチでした。

以前の検証でフェイルオーバーできないIPもIPエイリアスではなく、実IPをふりなおせば問題なく使えることは確認できていました。
もしやと思い、ソースIPを指定してLocusnetworksの謎スイッチのアドレスにpingを送ったところ、その瞬間にLocusnetworksの謎スイッチのARPテーブルが更新されて疎通できるようになりました。

ping -I [VIP] [UC●M GW IP]

GARPは無視するけど、なんらかの形で存在を教えてあげると切り替わってくれるみたいです。

フェイルオーバー後にkeepalivedがUC●MのGWにVIPをソースIPとしてpingを打てばとりあえずフェイルオーバーできない問題は解消しそうです。

仕組みはいろいろできると思いますが、keepalivedのnotify_masterを使います。

vrrp_instance IV_1 {
    state BACKUP
    interface eth0
    virtual_router_id 1
    priority 50
    advert_int 1
    notify_master /usr/local/bin/to_master.sh
    authentication {
        auth_type PASS
        auth_pass papapass
    }
    virtual_ipaddress {
        192.168.144.254
    }
}

/usr/local/bin/to_master.sh の中身はこんな感じです。

#!/bin/bash
ping -I [VIP] -c 10 [UC●M GW]

keepalivedがマスターに昇格したら自動でVIPをソースにしてpingをうちます。これでLocusnetworksの謎スイッチのARPテーブルが更新されて疎通できるようになります。
(-c10 は不信感の表れです)

この件でUC●M側の対応としてはファームアップだったみたいですが、ファームアップ後もしっかり再現したのでこのような実装を思いつきました。
「他のお客様はアプライアンスを使っていてみなさん仮想MACなので問題ない」とのありがたい言葉もUC●Mからいただいております。

この程度の金額のサービスで仮想MACが使えるアプライアンスを維持できるお客様しかいないもんでしょうかね。