HerokuのWebSocketでC10Kに挑戦(後篇)
割り込みによって書けてなかったHeroku C10Kの続きです。
テスト実施から少し時間が空いてしまったため、改めてまとめようとすると疑問点や追試したいこともあるんですが、当面これ以上時間を割くことができそうにないのでざっくりとまとめてしまいます。
★前篇のおさらい
前篇の内容から今回ポイントとなる点をピックアップすると以下のようになります。
- 1Dynoでさばける同時接続数は400前後。
- それを越えるとH11の接続エラーが返ってくるようになる。
- 雑なテストプログラムで25台のDynoに1万クライアント接続を試みたところ少なくとも5600位は繋がったっぽい。
「繋がったっぽい」というあいまいな表現になっているはテストプログラム側で何を持って成功とみなすかという点をはっきりと決めていなかったためです。
エラーが発生しなければ試行数をそのまま成績と判断できますが、途中でエラーが発生した場合そのテストがどの程度の成績を残したのかということを判断する基準がなかったんですね。
なので、それを踏まえてテストのレギュレーションは以下のように決めました。
- ルームを100個用意する
- 各部屋に3秒に1人ずつ、100人(100クライアント)入室する(最大時同時接続1万クライアント)
- 各クライアントは5秒に1回チャットメッセージを投げる(合計100万メッセージ)
- 各ルームに最初に入室したクライアントの受信したメッセージ数の合計をテストの得点とする(満点は100万点)
各部屋では100人が100回発言するのでエラーがなければ最初に入室した人は1万回メッセージを受信するはずです。
負荷があがってくると入室(WebSocket接続)時にエラーとなる可能性がありますが、負荷のかかっていない最初の入室者がエラーとなることはほとんどありません。
つまりエラーが全く起こらなかった場合の各クライアントの得点は10000点(100メッセージ × 100クライアント)で、クライアントの接続エラーが起きた場合は-100点(そのクライアントは一度もメッセージを発信しないから)、個別メッセージの送受信でエラーが発生した場合は-1点となります。
なんらかの理由でこのクライアントのWebSocket接続が途中で切断された場合は、以降のメッセージは受け取れないので、この場合は極端に低い得点となる可能性もありますがここまでの経験則から一度確立したWebSocket接続はかなり安定して動作することがわかっているので、その場合もそれをそのまま得点とすることにします。
ちなみにテストプログラムは前回使用したものから以下の改良を行いました。
- WebSocketクライアントをjava_websocketからAsyncHttpClientに変更
- 接続エラー時には最大10回のリトライを行うようにした
ノーマルSocket + Thread監視のjava_websocketはやはりメモリ消費量が多かったので1プロセス内でたくさんの接続を作れなかったのと、前回のテストからH11はリトライである程度回避できるのではないかと予想したからです。
テストは25Dyno(1X)と30Dyno(1X)の2回行いました。
★ 1回目 25 Dyno(1X)
得点: 795555
ぎりぎり8割いかなかった。。。(--
しかし、意外と良い数字という気もします。
傾向としては以下のような感じです。
- 各クライアントの最低点は4152、最高点は9687.
- 得点分布は5200点半以下(24クライアント)と8300点以上(76クライアント)にわかれている
- 6000点台、7000点台を取ったクライアントは何故かいない
- 満点(100000点)を取ったクライアントもいない
- H11は少し発生しているがリトライで接続できているらしい
- 多分接続自体は1万クライアント全部成功している
- IOException多数
得点の低いクライアントは途中で接続が切断されています。サーバ側のログを見るとその頃に複数のDynoでIOExceptionが多数記録されていますが原因は不明です。
その後しばらくはエラーなくテストが進行していますがその理由も謎です。
また、10000点を取ったクライアントがいなかったことも特徴的です。はっきりとした数字は数えていませんが前回のテストでは10000点を取ったクライアントがかなりあったはずです。
これはクライアントをjava_websocketからAsyncHttpClientに変更した影響かと思っているんですが、過負荷な状況ではNonBlocking I/Oはメッセージの一部を取りこぼすことがあるんでしょうか???
どなたか知見のある人は教えてください。(^^;;;
★ 2回目 30 Dyno(1X)
考察もそこそこに2回目のテストへ。今回テストサーバも結構な数のDynoを使っていますが、負荷をかけるクライアントの方では2XDynoを100台使っていたり、Redisもいいお値段のものをテスト実行中のみ追加していたりする関係でテスト実行にかかる時間は1秒でも短い方が良いのです。(^^;
2回目は変更した条件はサーバ側のDyno数のみ30台。結果は。。。。
得点:999976
おー?!マジでか???
まさかのフォーナイン!ほとんどのメッセージが拾えています。
傾向としては以下のような感じ
- IOException激減
- 発生しているIOExceptionは「broken pipe」とか「connection reset by peer」とか
- H11未発生
1Dynoあたりの平均接続数は400クライアント(10000 / 25)から333クライアント(10000 / 30)に下がってますが、それでこんなにも結果が違うんですね。
ちなみにRedisの負荷はサーバ数が増えれば大きくなるはずですが、こちらは何の問題もなくリクエストを捌いていたようです。
これ、タイトルにある「HerokuのWebSocketでC10Kに挑戦」はとりあえず達成と言っても良いんじゃないでしょうか?
まぁ金で解決しただけと言われればその通りなんですが、IOExceptionはアプリ側の問題なのでチューンすればもうちょっと少ないDyno数でC10Kを達成できそうな気はします。
あとWebSocketアプリ限定で使えるもっと効率的に負荷分散する方法も思いついているのでこれは多分そのうち書きます。
★ その他補足事項
どこに書こうか迷ったんですが、収まりの良いところがなかったのでここで。
前回、「1dynoあたりの接続数をもっと増やせないか?」という問い合わせをHerokuに対して行っているということをちらっと書きましたが、その回答が実は来ています。
回答を要約すると
とりあえず同時接続数増やしたよ。我々の目標とする数値には届いていないのでまだルーターの改修は続けるけど、前よりは格段に接続数増えたはず。試してみれ
て言う感じです。
で、上のテスト結果はおそらくその改修が効いた状態でのテストです。(どこかでもう全部のアプリで有効になっているという文章を読んだ気がするけど、探しても見当たらないので見間違いだったかも。ChangeLogにはあがってないので、ルーター改修はまだ作業中かもしれません。)
1Dynoで追試を行ったところH11に対してリトライをかければ800~1000位は繋がるようなので、たしかに以前よりはずっと同時接続性があがっているようなんですが、いまいち結果が安定していない気がするので、とりあえず結論は保留です。
2X Dynoや PX Dynoを使ったテストもそのうちするかもしれませんが、早くても来月以降になると思います。(何故なら2X Dyno100台を3時間あげるとそれだけで600Dyno Hourの消費になるから。。。(^^;;;)
結論:heroku で C10K は 6dyno あればいける!
HerokuのC10K挑戦の記事を興味惹かれたheroku歴まだ3日(原因:食わず嫌い)のプログラマです^^
気になったことを自前で調査した結果をご報告します。
この記事における失敗の原因が、リクエストコネクション(heroku router がらみ)にあるのか
バックエンド(Redis やサーバプログラム)にあるのかを切り分けてみました。
調査目的は「確率的に100%コネクションを張るための Web dyno 数」の計算となります。
用意したサーバプログラムは nodejs(express) で構築した最小構成のウェブサーバ。
単純なリクエスト(25秒後に1バイトのtext/plainを送るだけ)に対して
Apache Bench の -n 10000 -c 10000 でリクエストを送信した結果で判断しています。
結論から書くと、1X dyno 6台で 10000リクエストが36~38秒で Fail 無しで完了できました。
少なくとも 13秒~25秒 の間は同時にコネクションがサーバプロセスに届いていることになります。
先んじて調査した 1X dyno 1台でのテストでは
-c 1700 で 1~4 程度の 503 レスポンス
-c 2000 で 96~140 程度の 503 レスポンス
という結果となりました。
(かなり適当な)確率的にはdyno1台あたり 1666 コネクションくらいはまず間違いな
く同時コネクションが張れるようです。
もしこのレスに反応頂けるようであれば、同じく100部屋×100ユーザ×100発言についても
検証してみたいと思います。乞うご期待!
投稿: heroku歴3日プログラマ | 2014年5月18日 (日) 21:37
小西です。
大変興味深いです。
えーと、これはWebSocketのテストではなくて、単純にhttpでの同時接続数のみを対象としたテストですよね?
私の行ったテストはPlay(Scala)ですが、Node.jsの方がフットプリントが小さく、また非同期IOでもあるので同時リクエストを大量に捌けるだろうと思います。
また、時期的にHeroku側でのRouter改修の真っ最中だった気がするので今はもう少し同時接続数あがっているかもしれません。
実際、HerokuのRouterの制限がなくEC2直接使用ならNodeはもっと多くの同時接続をさばけるはずです。(このテスト内容なら1台でも10000接続捌けるんじゃないかと思う。)
現在別のことをやっていてあまり時間が取れていませんが、近日NodeでのWebSocketテストも試したいと思っているので、ご興味あればWatchしてください。(^^;
あと5/22のHeroku MeetupでのLTと6/9のSalesforceのイベントでWebSocketの話をするのでそちらも是非(^^;
投稿: konishi | 2014年5月19日 (月) 10:48
小西様、お返事ありがとうございます。
ご指摘の通り、C10K 問題に対するシステム寄りの仕様(Web Dynoが何台必要か?)を
確認したかったという動機が最初にありきですから、WebSocket とは結果が違うかも
しれません……が、 router(reverse proxy)の仕組み上同じ結果になるのではと推測しています。
お返事もらえたので予告通り 1X Web Dyno 6台と RedisCloud(無料の最大10コネクション) で、
100部屋100人100発言100%メッセージ到達を目指してみたいと思います。(7台かもしれませんけどw)
WebSocket については個人的に興味はありますが、(実は当方はComet以前からとある方法で
リアルタイム通信やってました。とある手法で特許も出願しましたが国際特許の申請と
審査請求までお金持たず諦めて失効した経緯がw)普及やブームになるには中々難しそうです。
チャット以外のアイデアもいくつか当てがありますので、イベントやご機会があれば是非(^^;/
投稿: heroku歴4日目プログラマ | 2014年5月19日 (月) 22:40
そうですね。テスト方法はそれで良いと思います。
ただWebSocketを有効にするとHeroku側でRouterが切り替わるので「heroku labs:enable websockets」とした後だと結果が変わるかもしれません。
Cometとはまた懐かしいですね。(^^;
僕は元々Lingrを作ってた会社にいたんですよ。あんまり関わってませんでしたが。
WebSocketアプリはhttpアプリ開発の文法で作ろうとしてもあまり美味しくないと思います。
今はクラサバをずっとやってきた人が初めてWebをやろうとして壁にぶちあたったような状況に似ています。
ここでも根本的なパラダイムシフトが必要だと思っているんですが、そのためには何かしらのフレームワークが必要です。
が、今は何もないから自分で作ろうかなと思ってます。(^^;;;
(githubのroomframeworkという奴です。)
サーバーサイドはとりあえずPlayで作ってますがNode版も作りたいので、もし、NodeでWebSocket、Redisなどを使うテストコードを書くならソース公開していただけるととても助かります。(^^;
投稿: konishi | 2014年5月20日 (火) 10:51
Cometが話題になってすぐLingrがリリースされて有名でしたね。
デザインも Web2.0 っぽくて、インターフェースもこなれてて
私もその時期かなりの時間日本語部屋に常駐してましたw
HerokuのWebSocketsプラグインの前後で挙動確認しましたが、
有効にしないと WebSocket 接続がルータ時点で拒否されてしまうようですね。
有効にすると、無事 WebSocket 通信がやり取りできるようになりました。
ただ、RedisのSub/Pub機能を使うには1Dynoあたり2本必要だったので
WebSocketのC10K挑戦は5Dynoでのチューニング勝負となりそうです。
普通のHTTP通信については、WebSocket有効後でも1666本同時接続ははミス無し、
1700でミス3件というほぼ同じ結果だったので、
単純に WebSocket(Upgadeヘッダかな?) リクエストの処理が違うだけなのかな?
WebSocket を含めたリアルタイムウェブ基盤技術の普及やブームについての
難しさやあれこれは、当方も思うところありましてw そちらもまたどこかで。
投稿: heroku歴5日プログラマ | 2014年5月20日 (火) 19:38
C10Kテスト中間報告なのですが、WebSockets有効時の 1X Dyno 1台 でのHTTPリクエストが、
3000コネクションでもH11多発(2634接続成功で366リクエストがH11)したり、
運がいいと同時接続4986コネクション(H11は14本のみ)行けたり謎な状態が続いております……。
(サーバ側での同時接続数のカウンタとベンチマーククライアントでの通信結果ログでの集計で一致を確認)
本格的に個別コネクションのログ記録をとって状況の集計をしないと挙動のブレの原因の推測すらできなさそうです。(ちなみに1X Dyno 5台にしても、3000接続でもH11発生していたりと解析は一筋縄ではいかない気配が出てきました……)
検証データ増えてきたので検証用リソースやログデータ公表のために初GitHub挑戦するか、
どこかにHeroku攻略ブログでも立ち上げた方がよさそうな気がしてきましたw
投稿: heroku歴5日プログラマ | 2014年5月20日 (火) 23:36
ん~、リクエスト数に応じてRouterの数が動的に変わっているのかもしれないです。この辺僕も結果が不安定だなぁと思いつつも放置したままなので。。。
Herokuで既にgitは使っているんだから、GitHub使うのには何のハードルもないですよ。(^^;
是非使いましょう。
投稿: konishi | 2014年5月21日 (水) 11:58