Jetty のベンチマークを取ってみた

- Linux version 2.6.18-164.el5xen
- Memory: 532480k
- http://localhost:8080/

Jetty 6

w/o keepalive

concurrency num [#/sec]
10 10000 1352.23
100 10000 3793.19
1000 10000 3836.32

w/ keepalive

concurrency num [#/sec]
10 10000 3351.16
100 10000 8065.65
1000 10000 6684.50

Jetty 7

w/o keepalive

concurrency num [#/sec]
10 10000 910.58
100 10000 2116.24
1000 10000 2488.36

w/ keepalive

concurrency num [#/sec]
10 10000 1333.15
100 10000 3350.84
1000 10000 4750.80

どっちとも負荷を与え続けると

2010-05-17 12:09:34.718:INFO::org.eclipse.jetty.io.nio.SelectorManager$SelectSet@3ff2947d JVM BUG(s) - injecting delay38 times

の警告を出したり

2010-05-17 11:31:19.828:WARN::EXCEPTION 
java.io.IOException: Too many open files
        at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:145)
        at org.eclipse.jetty.server.nio.SelectChannelConnector$1.acceptChannel(SelectChannelConnector.java:75)
        at org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect(SelectorManager.java:668)
        at org.eclipse.jetty.io.nio.SelectorManager.doSelect(SelectorManager.java:182)
        at org.eclipse.jetty.server.nio.SelectChannelConnector.accept(SelectChannelConnector.java:135)
        at org.eclipse.jetty.server.AbstractConnector$Acceptor.run(AbstractConnector.java:852)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:437)
        at java.lang.Thread.run(Thread.java:619)

のExceptionをたまに吐くけど、よくわかんない。

  • -

追記:2010/05/17 19:40

http://b.hatena.ne.jp/kazuhooku/20100517#bookmark-21584084
id:kazuhooku「too many open files は ulimit -n == 1024 だからでは? 」

おっしゃるとおりです><
ファイルディスクリプタ増やしたら出なくなりました。

QRコードを表示するapp.psgi

QRコードを表示するだけの簡単なお仕事を大量にさばきたいって言われたので書いてみた。

use Imager::QRCode;
use Plack::Request;

my $qrcode = Imager::QRCode->new(
    size          => 2,
    margin        => 2,
    version       => 1,
    level         => 'M',
    casesensitive => 1,
    lightcolor    => Imager::Color->new(255, 255, 255),
    darkcolor     => Imager::Color->new(0, 0, 0),
);

my $app = sub {
    my $req = Plack::Request->new(shift);

    my $url = $req->param('url')
        or
        return [400, ['Content-Type' => 'texxt/plain'], ['url is required']];

    my $img = $qrcode->plot($url);
    $img->write(data => \my $data, type => 'gif') or die $img->errstr;
    return [200, ['Content-Type' => 'image/gif'], [$data]];
};

これをStarmanで走らせて

starman -a app.psgi

ab -c 10 -n 100してみたら、うちのヘボサーバーでも247.78 [#/sec]くらいでた。

そういえば.cgiCGIスクリプトってよく言うけど.psgiPSGIスクリプトって呼べばいいのかな。PSGIアプリケーションでいいのか。

cronologを32bit環境下で動かすときの注意

lighttpdを使ってるとログローテートにcronologをよく使うと風の噂でよく聞きます。

そんなcronologをそのまま32bit環境下で動かすとファイルサイズ2GBを超えて書き込めないですねーというありがたい説法をid:kazuhookuさんにしてもらいました。

要は32bitアプリケーションがopen(2)で2GBを超えるファイルにアクセスするためにはフラグとしてO_LARGEFILEを指定する必要があるのですが、2002-01-24で開発が止まっているcronologはその指定がないのでパッチを当ててやる必要があります。

--- src/cronolog.c.org  2010-02-23 00:49:18.000000000 +0900
+++ src/cronolog.c  2010-02-23 00:50:27.000000000 +0900
@@ -82,6 +82,8 @@
  * written to "file" (e.g. /dev/console) or to stderr if "file" is "-".
  */

+#define _GNU_SOURCE 1
+
 #include "cronoutils.h"
 #include "getopt.h"

@@ -394,13 +396,13 @@
       timestamp(*pnext_period), *pnext_period,
       *pnext_period - time_now));

-    log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
+    log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE, FILE_MODE);

 #ifndef DONT_CREATE_SUBDIRS
     if ((log_fd < 0) && (errno == ENOENT))
     {
    create_subdirs(pfilename);
-   log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
+   log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE, FILE_MODE);
     }
 #endif

rpmforgeのパッケージにもパッチはあたっていないのでついでにrpmforgeのSPECファイルを変更するとこんな感じ。

# $Id: cronolog.spec 5040 2007-01-02 20:35:37Z dag $
# Authority: dag

Summary: Log file rotator
Name: cronolog
Version: 1.6.2
Release: 1
License: GPL
Group: Applications/File
URL: http://cronolog.org/

Source: http://cronolog.org/download/cronolog-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Patch: cronolog-large-file.patch

%description
cronolog is a simple filter program that reads log file entries from
standard input and writes each entry to the output file specified by
a filename template and the current date and time. When the expanded
filename changes, the current file is closed and a new one opened.

cronolog is intended to be used in conjunction with a Web server,
such as Apache, to split the access log into daily or monthly logs.

%prep
%setup
%patch -p0

%build
%configure
%{__make} %{?_smp_mflags}

%install
%{__rm} -rf %{buildroot}
%{__make} install DESTDIR="%{buildroot}"

%clean
%{__rm} -rf %{buildroot}

%files
%defattr(-, root, root, 0755)
%doc AUTHORS ChangeLog COPYING NEWS README TODO
%doc %{_mandir}/man1/cronolog.1m*
%doc %{_mandir}/man1/cronosplit.1m*
%doc %{_infodir}/cronolog.info*
%{_sbindir}/cronolog
%{_sbindir}/cronosplit

%changelog
* Thu Dec 28 2006 Dag Wieers <dag@wieers.com> - 1.6.2-1
- Initial package. (Contributed by Christoper Maser)

とっとと64bitに移行しなさいという天の声が聞こえました。

Macのデフォルトのファイルディクリプタ数は最大256

Mac

ab -c 1000 -n 10000 http://example.com

とかすると

socket: Too many open files (24)

とか怒られる。
CentOSとかなら大丈夫なのに。

おもむろに

ulimit -n

とかしてファイルディスクリプタの制限値を見ると、Mac

256

おふっ。
ちなみにCentOS 5.3だと1024

一時的に

ulimit -n 1024

で上げてやった。

mac で移行アシスタントを使って困った

会社のmacが交換になったので移行アシスタントとやらで移行してもらったんだけど、色々不具合が…

  • /etc/以下のファイルが色々バックアップされてた(hostconfig~(古い Mac から)みたいなファイル名)
  • /etc/hosts がバックアップなしに綺麗にデフォルトに戻ってた
  • locate が使えなくなってた (/usr/libexec/locate.updatedbで復旧)
  • iTerm でxterm-256colors を選択して emacs を上げようとすると emacs: Cannot open terminfo database file と言われて立ち上がらない (TERMINFOを指定してお茶を濁した)
  • xcodeは移行されないのでgccがなくなっても驚かない(いまここ)

PlackをProxyサーバーに使う意義

ircで聞いたときはうまく説明できなかった&tokuhiromさん、Yappoさん、kazuhoさんに直接教えてもらったのでまとめとくなり。

Proxyサーバーを作ることになった。

こんな感じのやつ。

で、これの問題として対抗のサーバーの応答速度が遅い場合があってそこにProxyサーバーが引きずられる点がある。つまりクライアントからの毎コネクションが比較的長くなりがちなサーバーをいかに効率よく組むかという課題がある。

最初は勘違いして他のサーバーへの問い合わせの間に他のことをして全体の応答速度を速くする、つまり非同期化によるメリットを模索していたんだけど、1回の応答で他サーバーへの問い合わせがたくさんあるようなクローラーみたいなことをする場合はメリットがあるけど、基本的に1回の応答で他サーバーへの問い合わせは1回だし、コンテンツを持ってくる以外にも処理はあるけど、処理時間の多くはこの1回のコンテンツの取得なのでそもそもこの部分の非同期化の恩恵はあまり受けられない。これはちゃんと理解してなかったので馬鹿みたいな勘違いだった。

対抗のサーバーからの応答が遅い以上、コネクション毎の応答速度を上げることは無理なのは明白なので、同時接続数をなるべく多く確保できる仕組みが要求される。つまり1回のコネクションリソースが少ないもの、もしくはIO待ちが発生している時にリソースの有効利用ができるもの。

となると、preforkではなくマルチスレッドやノンブロッキングIOの上で動く軽量HTTPサーバーの検討に入ったときにPlackが目の前に転がっていた。今回の要件ではコンテンツを持ってくるだけじゃなくてさらに色々処理が発生するのでロジックをPerlで書けることも要件になっていたし、すでにCoroやAnyEventなどの実装を持っているPlackは非常に取っ付きやすかった。今回は単機能なProxyでディスパッチなどは必要ないため、Plackの上にWAFなどは作らず直接実装していくと思う。

ただ、ここで問題なのがユーザーからのリクエストを直接Plackのサーバーが受けられればいいのだが、SSLなどを受ける必要が出てきたときにPlack::Implでは実装しない場合、前にReverse ProxyとしてApacheなどを立てた場合、今度はここでの同時接続が問題となる可能性があるので、ここは考えていかなければいかないと思う。

全体的に理解が浅いしまだまだ勘違いしていることが多そうだけど、問題の局所化はできたので色々ベンチを取りながら色々試していく感じ。