PoCo::HTTP による Comet チャットサーバのスケーラビリティ

せっかくのイベントドリブンフレームワークな POE ですから Comet でチャットをやってみようかと。

http://d.hatena.ne.jp/dayflower/20061116/1163663677

こちらの POE::Component::HTTP による Comet チャットサーバですが、どのぐらいの接続まで耐えられるのか? 実験。
クライアントとして、HTTP::Async を使って同時接続を張りまくるスクリプトを用意。

#!/usr/bin/perl
use HTTP::Async;
use HTTP::Request;
use Time::HiRes qw/time/;
use strict;
my $slots = shift || 20;
my $async = HTTP::Async->new( slots => $slots );
my $req   = HTTP::Request->new( GET => 'http://localhost:8888/view' );
while ( 1 ) {
    my $start = time;
    $async->add( $req ) for ( 1 .. $slots );
    my $conn_t = time - $start;
    printf "c,%d,%f,%f\n", $slots, $conn_t, $conn_t / $slots;
    $start = undef;
    my $count = 0;
    while ( my $res = $async->wait_for_next_response ) {
        $start = time unless $count++;
    }
    my $res_t = time - $start;
    printf "r,%d,%f,%f\n", $slots, $res_t, $res_t / $slots;
    sleep 1;
}

long poll する url に接続し、レスポンスが返ってきたら 1秒 sleep して再接続する、というもの。

まずはメモリ使用量 (VSZ)。

接続数VSZ
013412
1014008
2014100
5014232
10014760
20015816
30017000
50018980
100024260
200024656
1000 -> 2000 でそれほど増えていないのが不思議ですが、2000接続で 24MBほど。1接続毎に 10KB ほど増加する。最近の機械なら全く問題ない。

次に接続数毎の、コネクションを張り終わるまでの時間 (c) と、long poll から返ってきたレスポンスの最初の一つめを受け取ってから最後のを受け取るまでの時間 (r)。単位は秒。

 type | conn |     time    |  per_conn
------+------+-------------+-------------
 c    |   10 |  0.00859111 |    0.000859
 r    |   10 | 0.000830875 |  8.3125e-05
 c    |   50 |   0.0460453 |    0.000921
 r    |   50 |   0.0282752 |   0.0005655
 c    |  100 |   0.0924309 | 0.000924364
 r    |  100 |    0.073008 |   0.0007301
 c    |  200 |     0.19919 |   0.0009959
 r    |  200 |    0.234428 |  0.00117211
 c    |  300 |     3.30754 |   0.0110251
 r    |  300 |    0.528443 |   0.0017615
=========================================
 c    |  500 |    0.599251 |  0.00119875
 r    |  500 |     1.17672 |  0.00235343
 c    | 1000 |     1.35176 |  0.00135175
 r    | 1000 |     4.64576 |  0.00464571
 c    | 2000 |     2.62022 |  0.00131017
 r    | 2000 |     9.47508 |   0.0047376

接続数を増やしながら計測してみると、どうも conn=230 あたりを境に、急に (c) が悪化する現象が発生。
最初はクライアント側の問題か? と思ったのだが、そうではなく。

fedora core 5 のデフォルト設定で、SOMAXCONN が 128 になっている。

# cat /proc/sys/net/core/somaxconn
128
# perl -MSocket -e 'print SOMAXCONN'
128

で、POE::Wheel::SocketFactory でも SOMAXCONN の値を、queue の最大値に使用している。

      my $listen_queue = $params{ListenQueue} || SOMAXCONN;

この値を大きくしたら、接続に時間が掛かる現象が解消した。具体的には

# echo 4096 > /proc/sys/net/core/somaxconn

して、更にチャットサーバのスクリプトの方で

sub Socket::SOMAXCONN { 4096 }

とした。本来は POE::Wheel::SocketFactory のコンストラクタで ListenQueue 引数を渡すべきなのだが、これが PoCo::Server::TCP 内にあるため手を入れられず。強引だが Socket::SOMAXCONN を再定義。
ということで、(c) >= 500 の結果は SOMAXCONN をいじった後の結果。

結果については、チャットとして快適なレスポンス、と思えるのは (c)==500 ぐらいまで。すべての (500クライアントに対する) レスポンス送信が 1秒ちょっとで終わる。
1000 になると、発言してから反映されるまでに最悪 5秒掛かるので、ちょっと重い感じ。(もちろん、一番最初に受け取るクライアントはほぼ 0秒だし、平均だと 2.5 秒)

まあ、500〜1000接続 (ほぼ繋ぎっぱなし) を 1台でまかなえれば、結構いいんじゃないでしょうか。

  • サーバ側で複雑な処理をするとメモリ使用量はもっと増えるはず (クライアント毎のリソースは大して変わらないから大丈夫か?)
  • 特に送信時に時間が掛かることをすると、n クライアントに一斉に送信するので n 倍で効く

このへんには注意したほうがいいかも。

1台で捌けなければ → Spread Toolkit で Comet チャットサーバをクラスタリング - 酒日記 はてな支店 で。