[SYN]に[SYN+ACK]ではなく[ACK]が戻ってくる超常現象が発生した

大分暖かくなってきましたね。
花見が待ち遠しい日々です。

そんな楽しみにしている花見を心置きなく(w)楽しむ為に、解決しなければいけない超常現象が発生しました。

今回の問題は新しいプロダクト(サーバ)とSSP(クライアント)という既存のSSPに加えて、新たに構築したサーバで発生しました。
何かしら問題は出るとは思っていましたが、法則通り厄介な現象が発生してしまいました。

症状としてはクライアントアプリケーションから見てサーバとの通信でタイムアウトが発生する現象です。
これがあまりにも頻発するためtcpdumpで確認することになりました。

内容を確認したところ、挙動としてはクライアント側が[SYN]を送信した後に、何故かサーバ側から[SYN+ACK]ではなく、いきなり[ACK]が戻ってくるという現象が確認できました。
これでは正常にTCPの3ウェイハンドシェイクを確立することができません。
クライアント側が[ACK]を単発で受信した結果、クライアント側の意図にそぐわないレスポンスということで[RST]をサーバ側に送信し通信が終了してしまうという流れです。

実際のwireshark画面です。
以下全てサーバ側から見た結果になります。
syn_ack1


常識的に考えて[SYN]に[SYN+ACK]が戻ってこないことなんてないだろjkjkjk(^ρ^)

正常に通信できているパターンとだめなパターンが激しく入り乱れており、発生条件の切り分けもできず2週間くらい毎晩酒を飲んでwiresharkを眺め続けました。

激しく悩まされたそんな問題の原因ですが、例によって広告配信という特殊な環境と、自社内NWの通信事情のせいでした。

原因に気づいたきっかけはwiresharkでcapファイルの末尾のあたりのシーケンスを眺めていた時です。
以下が該当の画面になります。
syn_ack2
RSTが発生するときに[Tcp Port numbers reused]が表示されているのは、キャプチャ開始から時間がたったシーケンスに限定されていました。
reusedと言われてるからには使いまわしているはずなので、クライアントのポート番号をフィルタの上限に追加してみたところ…。

syn_ack3
20秒ちょっと前のシーケンスは正常に終了しています。
その後しばらく時間が経った通信で[Tcp Port numbers reused]が発生して、通信が異常終了しています。

つまり先頭の方のシーケンスでは同じ挙動をしていても、キャプチャが断片的なためwiresharkがPort reusedと識別することができないと判断できます。
このPort reuseが色からして黒なのでほぼ間違いありません。

このPort reuseの悪影響として予想できる事は、ソケットはsrc dst IP,Portのタプルをハッシュ化して処理しているため、ハッシュが衝突するとよくない事が起こるはずです。
とは思ってはみたものの、既に通信は正常に終了しているのになんでなのか分からずここでも結構悩みましたが、犯人はTIME_WAIT状態で残っているソケットでした。

サーバ側に残っているTIME_WAIT状態のソケットに対して新規の通信が発生することにより、クライアント側は新規でコネクション要求をしているにも関わらず、サーバ側は既存コネクション扱いになり、結果的にお互いの意図に反する挙動をしていたようです。

ざっくりまとめると以下の通りです。

  • 1:1の通信であれば、通信終了後にTIME_WAITが残っても、双方でTIME_WAIT状態になるためクライアント側からソケットが衝突する条件で新規の通信を発生させることは不可能
  • NATルータのIPマスカレードを挟むことによって、ソースポートが変換されるのが原因(1)
  • NATルータはソースポートを稼ぐために、TIME_WAITを維持する時間を極端に短くしている。サーバ側とルータでTIME_WAITのタイムアウト時間の差異が原因(2)
  • NATルータはソースポートを意図的に使いまわすことがある(らしい)原因(3?)
  • 特定のホスト間での大量の細かいトラフィックにも起因する
  • 実は以前にサーバのポート枯渇問題がありTIME_WAITの最大値を設定して運用していたけど、サービス本格稼働前にフロント側のnginxの台数を増やしたため、最大値によるTIME_WAITのリフレッシュがほぼされなくなってしまったのも原因(4)

自社の環境ではTIME_WAITは不要の存在どころか、トラブルを招く厄介者でした。

そして、上記の原因の対策をするにあたり、いくつか懸念事項があがりました。

  • LVSをを利用しているため、TIME_WAITの再利用はしない
  • タイムアウトを短くしようにも60秒はカーネルに決め打ちで書いてやがる
  • NATルータでTIME_WAITを保持するのは手数が多すぎて無理
  • NATルータ側でソースポートをランダムに使ってもいつかは衝突する

等あり、運用面を考慮してサーバ側のmax_tw_bacuketを、100と極端に小さい数値に設定することにしました。
これにより、サーバ側のTIME_WAITソケットが短い時間で破棄され、問題の発生を解消することができました。

原因を調べる過程でLinuxのTCPスタックのソース全部読めたのでそこそこ収穫もありました。
普段の暮らしだとTCPなんて高レイヤーな部分なかなかじっくり見れませんからね。

いやあ今回はきつかった…。
こんだけ頑張ってるのにクラウドでコスト圧縮とか何も考えないで言ってくるのが定期的に湧くんですよ、ホント嫌になっちゃう。

LinuxのTCP受信バッファ溢れと輻輳制御の話

年明け早々に新鮮なネタがあがったため、暖かいうちに書いておこうと思います。

今回はサービスのフロント側でトラフィックを受けているnginxサーバのTCP受信バッファが溢れた問題です。
後述しますが厄介なところは、ユーザがOSから得られるすべてのカウンターの値を見ても一切ドロップが発生していることが把握できないことです。(多分)

以前よりサービス品質を保つために自社サブネットを指定してtcpdumpをとり、外部から見た通信に異常がないか定期的に確認していました。
その作業をVCの銭亀おじさんたちに仕事の腰をへし折られてからしばらく忘れて怠っていました。
その間いい加減な仕事をしていたということですね。

年が明けてから思い出して久しぶりにdumpをとってみたところ、下記のようにものすごい数のTCPの再送や、重複ACKも確認できました。(popinが邪魔かも…)rmem1
これは明らかに経路のどこかでブラックホールが存在している症状です。
社内L8層とは関係なくトラフィックも地味に伸び続けていたため、どこかのリミットがはじけたと思いました。
かといってtcpdumpまではいかないものの、スイッチやサーバの各種エラーカウンターは休日も毎日確認していました。
なんでこんなひどい状態になるまで検出することができなかったのか、ちょっと考えましたが答えはcapファイルを見てちょっとまじめに考えたらすぐに予想がつきました。

再送多発のトリガーは下記の画像です。
TCPウィンドウサイズの変更をサーバ側がクライアントにプッシュしています。
増える分にはスループットが向上するので構いませんが、減少している場合はTCPの受信バッファに空きがなく、お腹いっぱいなのでちょっとまって状態という解釈ができます。rmem2
では実際に流れを追って見てみます。

まずサーバはクライアントからSYNを受信し、SYN+ACKを返す段階ではウィンドウサイズを14480に設定しています。
rmem3
次にデータのやりとりを始めた段階ですぐにTCP受信バッファの空きがないため、TCP Window Updateでウィンドウサイズを114に設定しています。
rmem4
通常であればこの流れで輻輳制御が作用し、ネットワークの輻輳が回避できます。
ここまでひどいパケットドロップも発生しないはずです。
しかしここで問題になるのが、例によって広告配信という特殊な環境と、Linux TCPスタックの実装との相性の悪さです。

TCPはそもそもの設計思想がスループットを優先する仕組みになっています。
スループットが優先されるシステムやサービスでは、ウィンドウサイズの操作による輻輳制御はとても有効に作用します。
しかし一つ一つの通信が小さく、短い時間で終了し、手数も馬鹿みたいに多い広告配信においては、ウィンドウサイズによる輻輳制御はまったく有効に機能しません。

クライアントからの再送も重なり、結果的にnginxサーバが慢性的なバッファフルによる輻輳状態に陥り、パケットをぼろぼろ落とすという現象が発生していたようです。

またカーネルソースを読んだところ、バッファフルの時は goto drop; とシンプルに一行書いてあるだけでした。
再送があるしウィンドウサイズで輻輳制御もしてるし、バッファフルになったらふわっと捨てればいいやという感じなんですかね。
せめてなにかのカウンターインクリメントしてほしい…。

と経緯は書いてみると長いものの、対策はLinuxネットワークチューニングのテンプレートによく書いてある内容です。
調査過程で気づいた通信中にふわっといなくなるモバイルネットワーク対策で送信側もついでに増やすことにしました。

net.core.wmem_max = 12582912
net.core.rmem_max = 12582912
net.ipv4.tcp_rmem = 10240 12582912 12582912
net.ipv4.tcp_wmem = 10240 12582912 12582912

どうせフルフルで使うので通常値 = 最大値の設定で運用しています。
前にドキュメント読んだ時「動的だから通常ここは設定変えなくてもおk」みたいなこと書いてあったんですけどね。
自社サービスを外から見てみるという事が改めて重要なことだと認識しました。

広告インフラまじ鬼畜。

I350のRSS Queueが溢れた

明けましておめでとうございます。
今年も本糞ブログを細々と続けていくのでよろしくお願いします。
去年はイーサネットフレームのバイト数もわからない様々な人たちに自社のインフラにクラウド導入を促されてキレそうになっていましたが、今年は遠慮なくキレようと思います。

コミケにフル参戦していたため年を越してしまいましたが、年末に起きたちょっとしたRSSのQueueに関する騒動をまとめます。

新しく構築した環境の特定のサーバで以前に書いたI350の受信バッファが溢れた時と同様にifconfigのoverrunsのカウンターの急上昇を確認しました。
なんだまたリングバッファ溢れかとその時は思いましたが、前回とは異なりパケットドロップ、破棄のカウンター上昇はありませんでした。

前例からoverrunsが出ている時点でパケットが捨てられているという先入観があったため、最初は意味がわかりませんでしたがぼちぼち調査を開始しました。

例によってethtool -S | grep dropで調べたところ、以下のカウンターがゴリゴリあがっていました。

     rx_queue_0_drops: 0
     rx_queue_1_drops: 0
     rx_queue_2_drops: 1098532642
     rx_queue_3_drops: 5009716490

カーネルソースを読んだところ、このキューはRSSのキューです。
このサーバはRSSでIRQを4個掴んでいるためこのようなキューの合計数になっています。

今回はここからが本題です。

この問題は自社では新しい環境のリアルサーバとLVSとで初めて確認されました。
原因は特定のホストとの同一の条件の通信が大量発生することによるRSSのキューイングアルゴリズムの偏りです。

RSSはデフォルトではIPとPortをHask keyにして各キューに分散しているため、特定のホストと同じ条件の通信が集中するとRSSのキューの分散に偏りがでます。
偏った結果、キューが溢れ順番にリキューされ、上記のようにdropのカウンターが階段状になります。

対策としてRSSのキュー自体を綺麗に分散する方法を考えましたが、現状Linuxで実用的な方法はないようです。
Symmetrical RSSというロードバランス方法がひっそり世間に出つつあるようなので期待しています。

自社の対策としてはキューであるリングバッファの設定値をデフォルトの256から4096にすることも考えましたが、現状の通信状況を考慮してあえてデフォルトの256のままで運用することにしました。
根拠は以下の図の通りです。

rss_queueキューが偏っている状態でキューサイズであるリングバッファの設定を増やしても遅延するだけと考えました。
現状では効率は悪いけどリキューさせたほうがまだ遅延しないという判断です。

各界で言われているようにバッファは確かに遅延を招きます、しかしバッファが溢れドロップした場合はさらに膨大な遅延を招きます。
キューやバッファマネージメントは土管が太くなる一方の世の中で、我々ネットワークに携わる者に求められる能力になると思います。

SPDYをRTBに応用してみた

かなり冷え込んできましたね。

先週末に長野戦のラリーにコ・ドライバーで出場するために木曽まで行ってきました。
日中は暖かく絶好のラリー日よりで快適でしたが、朝夜は氷点下で日陰では前日に降った雪が薄っすら残っていました。

現地でいろいろな方に話を伺いましたが、御岳の報道をマスコミがこぞって木曽で行った結果、風評被害により木曽温泉をはじめとした観光産業が壊滅寸前とのことです。
ウィンタースポーツを楽しむ人には雪質もよくて人も少なくてお勧めかも?
微力ながら今後も継続して応援したいと思います。

さて、今回はあまり手の込んでないWAN高速化の話です。
自社のSSPは特徴であるところの複数のDSPと接続してRTBを行っています。

DSPからのレスポンスのタイムアウトは150msに設定してあります。

すべてのDSPからのレスポンスを待ってからピカチュウ、君に決めたも当然ありですが、利益機会を追求しすぎてRTBのタイムアウトを長くした結果、広告表示の遅延に繋がりメディア運営者や閲覧者からクレームが来ることは避けなければいけません。
自社ではぎりぎりの足きりが150msという数値の設定です。

また、唯一絶対神株主様の意向で限られたインフラリソースでやり取りしているため、タイムアウトを長くすることにより、口をあけて待っている開きっぱなしのソケットが増えることはリソースの浪費になるため好ましくありません。

このような背景がある中で、今までは国内に拠点を持ったDSP事業者との接続がメインでしたが、海外に拠点をもったDSPとRTB接続をする要件があがりました。

海外とのRTBで一番の問題になるのはレイテンシーです。

今回の相手側のロケーションは香港ですが、IIJの良質のバックボーンを使ってもpingで50msちょっとかかります。
一般ユーザが意識せずに使うには十分なパフォーマンスですが、TCPの3ウェイハンドシェイクを考慮すると最低でも通信に要する時間が100msとなり、DSP側は50ms以内にレスポンスを返さないとRTBでの取引が成立しないことになります。

3way hand shakeチャキチャキなDSPは”50ms or die”とおっしゃってくださっているのですが、、、残念ながら実際はでかく見せておいてあまりがんばってなさそうな方々が大多数です。
これではレスポンスを送る側も受ける側もタイムアウトの足きりによって、お互いに機会損失とリソースの無駄があるという社内での議論や、そもそもWANの高速化なんて誰でもやってんだからその気になればやりゃできるだろ、という単純な自分の思考から改善に取り組み始めました。

単純な答えはTCPの3ウェイハンドシェイクを省略すれば2倍の速度で通信することができます。
問題は実装です。

いくつか方法を考えましたが、開発コストや現実不可能なためすべて却下しました。

・独自プロトコルを実装する
・UDPを使う
・SIPのヘッダとかに無理やりのせる
・DSPと調整してHTTP Keepaliveを有効にできる環境を作る
・TCP Fast Openでなんとかする
・低軌道衛星使いたい
・採算度外視で香港に東京とほぼリアルタイムで同期しながら動く拠点を作る
・VPNでごにょごにょ

etc・・・

開発コストをかけないためにできるだけインフラ側で吸収するために現行の枠のまま通信速度を2倍にするという無理難題に対して、数々のアイデアを出してはボツにしました。
そんな中でフワっと注目したのが、SPDYでした。
SPDYをProxyとして利用して、東京と香港にSPDY Proxyサーバを用意すれば一番時間のかかる海を渡る拠点間の通信が無理やりKeepaliveできるのではなかろうかと思いました。

そこからはあっという間で一週間くらいですべて検証できました。

同じこと考える人間が絶対いるはずだからSPDY Proxyとか普通にあるだろーとか調べていたらspdylayというライブラリにshrpxというSPDY Reverse Forward Proxy デーモンが含まれており、まずこれを利用してSPDYでHTTP通信が海外拠点と高速化できることを実証しました。

しかしshrpxはバックエンドのサーバをIPで直接指定する機能しかないため、実運用上のハードルはかなり高いと思いました。

そこで、shrpx のバックエンドにローカルホストでsquidを立てて、そこを参照するようにしました。

rtb2これにより、アプリケーション側は特にSPDY環境を意識することなく、香港宛の通信を高速化したいときはPOSTしているcurlのオプションにproxyサーバのオプションを一個追加するだけで利用することができます。

SPDY Proxyありとなしの差の結果は以下の通りです。

$ time curl --proxy [LVS-VIP]:3000 http://[香港ホストのURL]
{"result":"0"}
real 0m0.070s
user 0m0.008s
sys 0m0.000s

$ time curl http://[香港ホストのURL]
{"result":"0"}
real 0m0.158s
user 0m0.004s
sys 0m0.004s

香港側はVPSを利用しましたが、AWSなどメジャーどころがなく怪しいところばかりでいろいろ探した結果、Rackspaceを利用しました。
アクティベーションがちょっと面倒だったものの、幸いにも今回の香港接続先ととても近く2~3msで通信できました。

この話をN社の方にしたら普通はTCPでそこまでしないとありがたい言葉をいただきましたが、WANのアクセラレーションは身近な技術で安価に実施できますので、ぜひ機会があれば試してみてください。

VyOS Users Meeting Japan #2で話をしてきました

VyOSをビルドしたという自分のふとしたツイートから @higebu さんと知り合い、軽いノリで次回のユーザ会をやるときは話すとうっかり言ってしまったので約束を守るために話をしてきました。
ちなみに髭生やしてると思い込んでいたので、ご本人をみたときは軽くショックを受けました。

当日はおまけ資料の動画ファイルを職場のPCに忘れて午前中取りにいった割には使い物にならなかったり、ついでに新宿でエージェント業を遂行していたら、どこにいても聞こえるくらいでかいドラム鳴らしてる迷惑集団がいたりしてナヨナヨでした。
彼ら最終的には公園に落ち着いたようですが、休日を公園で過ごしてる人たちにはさぞ迷惑だったでしょうね。

そんなテンションで話しをさせていただいたVyOSユーザ会ですが、開催を知らされてから当日まで一週間もなく、内容も英語とのことで薄いスライドの内容の割にはかなり時間をくってしまいました。
(前日にレビューしてくれた秋津メンのみんなありがとうございました)

さてせっかくなので、自分がVyOSに限らず各種ソフトウェアルータや、SDNについて抱いている感想をカス零細弱小広告配信会社的な観点で軽くまとめます。

Vyattaを使い始めたきっかけは、入社して二日目に崩壊した箱出しで使っていたCentOS5のiptablesです。
初日で環境も揃い、二日目になりがんばるぞいってなったところで向かいの席から悲鳴があがり、そこからすべてが始まりました。

私物のRTX1200を試しに投入してみたところ見事に瞬殺w、今まで経験のない現象だけどRTXよりまだましなiptablesでがんばったほうがいいとなり、ならLinuxのチューニングが丸ごと使えるしNW機器として使える細かい手設定が不要なVyattaを選びました。
あとはひたすらパケットドロップ&破棄→チューニングで対処を繰り返して現在にいたるという感じです。

さて自社では大活躍しているVyOSをはじめとするソフトルータ(各種SDNも)ですが、自分の環境だとイマイチ使いどころが分からないとか使ってみたいけどーみたいな発言を見かけます。
まずは使ってみることをお勧めします。
なんとなく使ってるうちに自然とここで使えるなとか発想が出てくるはずです。

以下のような安価で低消費電力の2Port NICのマザーにVyOSを入れて家のルータとして使うのはどうでしょうか。
家庭用ルータをぶっちぎる性能と機能に足して、RTXより低価格で同等かそれ以上の性能を発揮するでしょう。

GA-J1900N-D3V

今後はIntelのDPDK等、高価なネットワーク機器の変わりに、安価なサーバやOpenFlowスイッチとコントローラを利用したパケットプロセッシングが主流になっていくとおもっています。
今のうちに自分自身の既成概念をぶち壊しておきましょう。

たとえば今ですが、数千万の小銭しか出資してないくせにあーだこーだ口出しして足引っ張ってくるVCのクラウドおじさん達がBGPルータを買ってくれないので、Lagopus(DPDK)とRouteFlowとか、最新カーネルでBulk dequeueのパッチが当たったので実はVyOSのquaggaで安価になんとかなるんじゃないかなと目論んでいます。(やるかは別として)

なおスライドでちょっと紹介した自作サーバについては、今年の初めに自作サーバ同窓会で詳細を発表しています。
スライドはこのへん動画はこのへんにひっそりと公開されていますので、興味のある方は悪の道に染まってみてください。

次回もなにかネタをこしらえて話をさせていただけたらとおもいます。
参加された皆様お疲れ様でした!