割り込みによって書けてなかった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の消費になるから。。。(^^;;;)