カテゴリー別アーカイブ: Oneman

Oneman 0.27 – テンプレートの一括コンパイル機能

Oneman 0.27を公開しました。

今回の変更はPSGIサーバではなくWAFの方がメインで、テンプレートの一括コンパイル機能を追加しています。

use FindBin qw( $Bin );
require MyWebApp;

my $psgi_app = MyWebApp->psgi_app(
    template_dir => "$Bin/templates",
    precompile => qr/ .html z/xms,
);

template_dirで指定したディレクトリ(指定していない場合はカレントディレクトリ)配下をFile::Findを使ってサブディレクトリも含めて辿り、precompileで指定した正規表現にマッチするパスのファイルを事前にコンパイルします。

通常は、ユーザからのリクエストを受け付けてWebアプリが動いてから、必要になった時に初めてコンパイルされます。マルチプロセスのサーバの場合、このコンパイルはforkされてからの処理になるためメモリ効率が良くありません。

precompileオプションを使い、PSGIアプリケーションをコンパイルする時点でテンプレートもあらかじめコンパイルしておく事で、COWの効果を得る事ができます。

また、PSGIではなくmod_perlの環境では、cache()メソッドを使う事でスマートに書く事ができます。

まずstartup.plに以下のように書きます。

use FindBin qw( $Bin );

require MyWebApp;

MyWebApp->build(
    template_dir => "$Bin/templates",
    precompile => qr/ .html z/xms,
)->cache;

cache()メソッドにより、Onemanのメモリ空間にアプリケーションがキャッシュされます。

次にModPerl::Registryで動作するCGIファイルです。

MyWebApp->cache->run;

これでキャッシュしたアプリケーションが動作します。

この機会に、Onemanアプリケーションの様々な使い方をまとめてみます。

# PSGI ファイル内で完結するアプリケーション
Oneman::psgi_app( sub { ... }, ... );

# CGI ファイル内で完結するアプリケーション
Oneman::run( sub { ... }, ... );

# .pl ファイル内で完結するアプリケーションサーバ
Oneman::psgi_app( sub { ... }, ... )->start(...);
or
Oneman::build( sub { ... }, ... )->start(...); # build は psgi_app のエイリアス

# Oneman を継承したアプリケーションモジュールの $psgi_app
MyWebApp->psgi_app(...);

# Oneman を継承したアプリケーションモジュールの CGI 実行
MyWebApp->run(...);

# Oneman を継承したアプリケーションモジュールをサーバとして実行
MyWebApp->psgi_app(...)->start(...);
or
MyWebApp->build(...)->start(...);

# Oneman を継承したアプリケーションモジュールをキャッシュしてから実行
MyWebApp->build(...)->cache; # キャッシュ
MyWebApp->cache->run(...); # 実行

# Oneman の一部機能(テンプレート処理機能等)だけを使いたい場合
my $oneman = Oneman->core(...);
print $oneman->get_content(...);

ソース

http://www.tamashiro.org/src/Oneman-0.27/
http://www.tamashiro.org/src/Oneman-0.27.tar.gz

Oneman 0.26 – 劇的に速度向上、というかこれまでが遅すぎた

Oneman 0.22で触れたとおり、OnemanはKeep-Aliveの実現のためにselectと1バイト取得の連続でリクエストヘッダの読み込みを行っていたため、ヘッダサイズが大きい場合の速度低下が顕著でした。
でもよくよく考えてみると、Oneman 0.21で実装した入力ストリームのバッファリングをリクエスト処理全体で行えば、リクエストラインとヘッダを取得した際にバッファリングしたリクエストボディやパイプラインリクエストを、後の処理に渡せる事に気付きました。気付くのが遅い。。

そこで、Oneman 0.26では入力ストリームを64KiBずつバッファリングするように変更しました。その結果、速度が劇的に向上しています。

Oneman 0.25 (Keep-Alive)
    Requests per second:    812.36 [#/sec] (mean)

Oneman 0.26 (Keep-Alive)
    Requests per second:    2402.44 [#/sec] (mean)

Starman 0.3008 (Keep-Alive)
    Requests per second:    2412.10 [#/sec] (mean)

Oneman 0.25 (No Keep-Alive)
    Requests per second:    745.69 [#/sec] (mean)

Oneman 0.26 (No Keep-Alive)
    Requests per second:    1325.19 [#/sec] (mean)

Starman 0.3008 (No Keep-Alive)
    Requests per second:    797.39 [#/sec] (mean)

※いずれも、5回×3セット=15回中の最高値を抜粋しています。

このベンチマークはOneman 0.22の時と同様ですが、Keep-Aliveの扱いにミスがあったので、今回は一部を変更しての実施です。

当時Keep-Aliveを有効にするために、Apache Benchにオプション-kを指定していたのですが、あらためて確認すると、Apache Benchの結果がKeep-Alive requests: 0となっていました。なぜKeep-Aliveが無効になっていたか?
確認したところ、まずStarmanは、出力がストリームになっていたためでした。Apache BenchはHTTP/1.0でリクエストを行うため、サーバはchunkedエンコーディングが使用できません。Hello.psgiはContent-Lengthヘッダを持たないため、chunkedエンコーディングが使用できないStarmanはストリームで出力した上でコネクションを切断していました。
次にOnemanは、当時のバージョン0.22ではConnectionヘッダを出力していなかったためでした。Apache Benchは、レスポンスヘッダにConnectionヘッダによるKeep-Aliveの指定が無い場合、コネクションを切断する仕様のようです。バージョン0.25でConnectionヘッダを付加するよう変更したため、現在はApache BenchのKeep-Aliveに対応できています。

ベンチマークに使用するHello.psgiには、StarmanにKeep-Aliveを行わせるためにContent-Lengthヘッダを追加しました。

my $app = sub {
    my $env = shift;
    return [
        200,
        [ 'Content-Type' => 'text/plain', 'Content-Length' => 11 ],
        [ "Hello World" ],
    ];
};

前回と同様に、OnemanとStarmanをそれぞれ以下のように実行して、Apache Benchにより秒間リクエスト処理数を計測します。

$ perl -MOneman -e 'oneman "Hello.psgi", workers => 10'
$ starman --workers 10 Hello.psgi

OnemanもしくはStarmanと、Apache Benchは、いずれもLet’snote CF-R8 (Windows 7) のVirtualBoxで動作するUbuntu上で実行します。Perlは5.16.2、Apache Benchのオプションは以下のとおり。

$ ab -c 10 -t 1 -k http://127.0.0.1:5000/

OnemanとStarmanを交互に3回実行して、それぞれでabを5回ずつ実行、計30回のベンチマークを取りました。
※抜粋のみで詳細の結果は載せていませんが、Onemanの前バージョンとの比較のためバージョン0.25も同様に計測しました。

Oneman 0.26 (Keep-Alive)
    Requests per second:    1730.11 [#/sec] (mean)
    Requests per second:    2344.59 [#/sec] (mean)
    Requests per second:    2368.74 [#/sec] (mean)
    Requests per second:    2316.48 [#/sec] (mean)
    Requests per second:    2391.14 [#/sec] (mean)

    Requests per second:    1700.56 [#/sec] (mean)
    Requests per second:    2317.97 [#/sec] (mean)
    Requests per second:    2358.85 [#/sec] (mean)
    Requests per second:    2402.44 [#/sec] (mean)
    Requests per second:    2366.34 [#/sec] (mean)

    Requests per second:    1803.13 [#/sec] (mean)
    Requests per second:    2362.14 [#/sec] (mean)
    Requests per second:    2322.59 [#/sec] (mean)
    Requests per second:    2381.22 [#/sec] (mean)
    Requests per second:    2377.59 [#/sec] (mean)

Starman 0.3008 (Keep-Alive)
    Requests per second:    1736.05 [#/sec] (mean)
    Requests per second:    2375.56 [#/sec] (mean)
    Requests per second:    2406.15 [#/sec] (mean)
    Requests per second:    2412.10 [#/sec] (mean)
    Requests per second:    2392.75 [#/sec] (mean)

    Requests per second:    1693.59 [#/sec] (mean)
    Requests per second:    2352.28 [#/sec] (mean)
    Requests per second:    2369.86 [#/sec] (mean)
    Requests per second:    2382.01 [#/sec] (mean)
    Requests per second:    2287.22 [#/sec] (mean)

    Requests per second:    1783.65 [#/sec] (mean)
    Requests per second:    2374.16 [#/sec] (mean)
    Requests per second:    2405.08 [#/sec] (mean)
    Requests per second:    2377.22 [#/sec] (mean)
    Requests per second:    2337.31 [#/sec] (mean)

また、Keep-Aliveを使用しない場合のベンチマークも取りました。結果は以下のとおりです。

$ ab -c 10 -t 1 http://127.0.0.1:5000/
Oneman 0.26 (No Keep-Alive)
    Requests per second:    970.57 [#/sec] (mean)
    Requests per second:    1258.08 [#/sec] (mean)
    Requests per second:    1299.39 [#/sec] (mean)
    Requests per second:    1319.84 [#/sec] (mean)
    Requests per second:    1273.80 [#/sec] (mean)

    Requests per second:    930.12 [#/sec] (mean)
    Requests per second:    1307.79 [#/sec] (mean)
    Requests per second:    1313.25 [#/sec] (mean)
    Requests per second:    1325.19 [#/sec] (mean)
    Requests per second:    1279.76 [#/sec] (mean)

    Requests per second:    977.43 [#/sec] (mean)
    Requests per second:    1289.74 [#/sec] (mean)
    Requests per second:    1297.43 [#/sec] (mean)
    Requests per second:    1308.71 [#/sec] (mean)
    Requests per second:    1276.44 [#/sec] (mean)

Starman 0.3008 (No Keep-Alive)
    Requests per second:    616.22 [#/sec] (mean)
    Requests per second:    769.84 [#/sec] (mean)
    Requests per second:    797.39 [#/sec] (mean)
    Requests per second:    756.97 [#/sec] (mean)
    Requests per second:    759.92 [#/sec] (mean)

    Requests per second:    539.82 [#/sec] (mean)
    Requests per second:    736.61 [#/sec] (mean)
    Requests per second:    707.97 [#/sec] (mean)
    Requests per second:    681.39 [#/sec] (mean)
    Requests per second:    728.07 [#/sec] (mean)

    Requests per second:    491.34 [#/sec] (mean)
    Requests per second:    584.63 [#/sec] (mean)
    Requests per second:    668.69 [#/sec] (mean)
    Requests per second:    668.10 [#/sec] (mean)
    Requests per second:    698.69 [#/sec] (mean)

ソース

http://www.tamashiro.org/src/Oneman-0.26/
http://www.tamashiro.org/src/Oneman-0.26.tar.gz

Oneman 0.22 – Starmanに肉薄する速さ YMMV ;-)、ほんとか??

細かな修正を行い、Oneman 0.22を公開しました。

さて、Onemanの機能数や品質は低く、Starmanには到底及びませんが、さて速さはどんなものだろうと思い、試してみました。

ベンチマークに使用したのはHello.psgi。

my $app = sub {
    my $env = shift;
    return [
        200,
        [ 'Content-Type' => 'text/plain' ],
        [ "Hello World" ],
    ];
};

StarmanとOnemanをそれぞれ以下のように実行して、ab (Apache HTTP server benchmarking tool) により秒間リクエスト処理数を計測します。

$ starman --workers 10 Hello.psgi
$ perl -MOneman -e 'oneman "Hello.psgi", workers => 10'

StarmanもしくはOnemanと、abは、いずれもLet’snote CF-R8 (Windows 7) のVirtualBoxで動作するUbuntu上で実行します。Perlは5.16.2、abのオプションは以下のとおり。

$ ab -c 10 -t 1 -k http://127.0.0.1:5000/

StarmanとOnemanを交互に3回実行して、それぞれでabを5回ずつ実行、計30回のベンチマークを取りました。さてその結果は?

やっぱりStarmanは速いですが、Onemanも意外に健闘しています。

Starman 0.3006
    Requests per second:    544.31 [#/sec] (mean)
    Requests per second:    616.26 [#/sec] (mean)
    Requests per second:    635.29 [#/sec] (mean)
    Requests per second:    682.02 [#/sec] (mean)
    Requests per second:    639.67 [#/sec] (mean)

    Requests per second:    591.27 [#/sec] (mean)
    Requests per second:    788.01 [#/sec] (mean)
    Requests per second:    728.52 [#/sec] (mean)
    Requests per second:    746.70 [#/sec] (mean)
    Requests per second:    727.46 [#/sec] (mean)

    Requests per second:    608.93 [#/sec] (mean)
    Requests per second:    705.51 [#/sec] (mean)
    Requests per second:    746.26 [#/sec] (mean)
    Requests per second:    739.99 [#/sec] (mean)
    Requests per second:    729.13 [#/sec] (mean)

Oneman 0.22
    Requests per second:    540.42 [#/sec] (mean)
    Requests per second:    648.77 [#/sec] (mean)
    Requests per second:    609.61 [#/sec] (mean)
    Requests per second:    637.44 [#/sec] (mean)
    Requests per second:    615.70 [#/sec] (mean)

    Requests per second:    443.15 [#/sec] (mean)
    Requests per second:    640.58 [#/sec] (mean)
    Requests per second:    625.77 [#/sec] (mean)
    Requests per second:    656.86 [#/sec] (mean)
    Requests per second:    639.11 [#/sec] (mean)

    Requests per second:    517.57 [#/sec] (mean)
    Requests per second:    638.55 [#/sec] (mean)
    Requests per second:    626.50 [#/sec] (mean)
    Requests per second:    634.62 [#/sec] (mean)
    Requests per second:    645.03 [#/sec] (mean)

特定の環境でのテストではありますが、Onemanも実用に十分耐える速さと言えそうです。ちょっと嬉しい。
しかし、仮想環境でのベンチマークとは言え、StarmanのPODに載せられている結果と1桁違うのがなんとも。。

また、Starmanには、

High Performance
Uses the fast XS/C HTTP header parser

とあります。
Onemanはpure perlである事に加え、Keep-Aliveの実現と、(alarmを使えない)Windows上で実行時のタイムアウト処理のために、selectと1バイト取得の連続でヘッダの読み込みを行っているので、効率がかなり悪いです。
そのため、リクエストヘッダのサイズが大きくなると、Starmanとの差はもっと大きくなるかもしれません。

ソース

http://www.tamashiro.org/src/Oneman-0.22/
http://www.tamashiro.org/src/Oneman-0.22.tar.gz

Oneman 0.21 – PSGI版Movable Typeを動かせるようにしました

先日公開した自作のPSGIサーバOnemanは、Plack::Test::SuiteはPASSしたものの、巷のPSGIアプリケーションがきちんと動くのか?
そこで、PSGI対応したMovable Typeの動作確認をしてみました。

>perl -MOneman -e "oneman 'mt.psgi', workers => 10"
Oneman: accepting connections at http://0.0.0.0:5000/

ブラウザでアクセスしてみると、、動かない。。
トップページはきちんと表示され、おぉ、自分で作ったサーバでMTが動いている!という少しの感動があったのですが、まず静的コンテンツの取得が遅く、次のページにも遷移できない。

早速調査を行い、大きく2点の修正をして、Movable Typeが正常かつ軽快に動作するようになりました。これで多少は使えるサーバになったかな。

以下に、その修正点について書いています。

ハンドルからの入力を固定長化

PSGIアプリからのレスポンスがhandleの場合、getline()で処理をする前に、入力を64KBytesに固定長化するよう変更しました。

local $/ = 65536;

これを忘れていたためにgetline()で行毎に読み込みしていたため、jquery-ui.jsのような巨大なテキストファイルのレスポンスに時間を要していました。
固定長化した事で劇的に改善しました。64KBytesという値はPlack::Utilを参考にしています。

入力ストリームのバッファリング

ブラウザからのGETリクエストはうまく動作するのですが、POSTリクエストに反応しないため、MT::PSGIのソースを読んでみると、以下のコードがありました。

        syswrite $child_in, do {
            local $/;
            my $fh = $env->{'psgi.input'};
            <$fh>;
        };

クライアントが送信するデータをEOFまで読み込みさせています。なんと乱暴な!これではKeep-AliveなHTTP/1.1クライアントではアクセスできなくて当然ではないか、と思ったのですが、でもplackupやstarmanで試してみると正しく動作する。。

HTTP::Server::PSGIのソースを読んでみて分かったのですが、PSGIサーバ側でいったん入力をバッファリングしてあげるんですね。Oneman 0.20では、クライアントのソケットをそのままpsgi.inputに入れていたため、そこを修正して入力をバッファリングするようにしました。

これで無事Movable Typeが動作するようになりました。さて、次は何を動かしてみようかな。

ソース

http://www.tamashiro.org/src/Oneman-0.21/
http://www.tamashiro.org/src/Oneman-0.21.tar.gz

Oneman – A simple web application framework with PSGI server

Windowsでも動作するpreforking PSGI server「Oneman」を作ってみました。5.8.1以上のコアモジュールのみで動作する1ファイル構成のPerlモジュールです。つまり、1ファイル置くだけで動くPSGIサーバです。

HTTP/1.1の一部(Chunked Transfer Coding、Persistent ConnectionsいわゆるKeep-Alive、他)に対応していて、Plack::Test::SuiteをPASSしています。preforkingなマルチプロセスサーバなので、ブロッキングが発生するリクエストの処理中にも別のリクエストを受け付ける事ができます。

使い方

「oneman()」という関数を実行する事でOneman PSGIサーバが動作します。oneman()を実行するスクリプトのコマンド引数にPSGIファイルを指定すると、デフォルトのパラメータでPSGIサーバが起動します。

>perl -MOneman -e oneman mywebapp.psgi
Oneman: accepting connections at http://0.0.0.0:5000/

詳しい使い方はドキュメントをご覧ください。

作成の目的と経緯

Oneman PSGIサーバは、とある方から「ブラウザで動作するターミナルソフトを作って欲しい」という依頼をきっかけに作成しました。

具体的には、Windows PCのシリアルポートに接続されたマイコンから受信したデータを、Ajaxを用いてブラウザに出力する、という動作が要件です。シリアルポートの接続には プロセスを常駐させる必要があり、またデータの受信をAnyEvent等で対応する事が難しくマルチプロセスで処理したかったのですが、そのようなWebサーバモジュールが見つかりませんでした。だからといってApacheを使用するのも大げさなので、要件を満たすサーバを作ってみた、というのが経緯です。昨今の状況に合わせてPSGIサーバとして実装しました。

当初はHTTP/1.0のみに対応した今よりもさらに簡素なサーバでしたが、ブラウザから大量のAjaxリクエストを実行すると数百回~数千回のところでエラーが発生してしまい、その原因・対策も見つけられませんでした。 netstatで確認すると大量のセッションが残っていたため、Keep-Aliveが必要かなと思い、HTTP/1.1に対応させる事にして今の形になっています。

このように単一クライアント向けWindowsソフトウェアへの組み込み用途で作成しており、インターネットに公開する不特定多数クライアント向けサーバとしての使用はあまり想定していません。セキュリティにはある程度気をつけており、Ubuntu、CentOS、Mac OS X等でも正常に動作する事を確認していますが、もしご利用いただく場合はご自身の責任の範囲でお願いいたします。

興味のあった無停止更新のためのプロセス構造等も試してみたり、良い勉強になっています。

web application framework?

実はPSGIサーバは後付けの機能で、Onemanは元々ChatPromptのために作成したCGI/PSGI対応の簡易なWebアプリケーションフレームワークです。Perlコードをそのまま埋め込めるテンプレート機能と基本的なパラメータハンドリングの機能を持っています。

ソース

http://www.tamashiro.org/src/Oneman-0.20/
http://www.tamashiro.org/src/Oneman-0.20.tar.gz