D-7 <altijd in beweging>

Day to day life of a Perl/Go/C/C++/whatever hacker. May include anything from tech, food, and family.

カテゴリ:技術/Tech

cartonを使ってアプリケーションの依存関係を解決する際にちょっとはまったのでメモ。

複数のPerlがインストールされている環境、具体的には/usr/local/bin/perlが過去の遺産の5.8.8で、どこか新しいところに /path/to/perl-5.16.3/bin/perl とかをインストールしている環境で、新しい方のperlにcartonを入れて実行したら実行時にエラーになった。

エラーログを よく見るとモジュール読み込みに失敗していて、なぜか参照パスが/usr/local 以下とかになってる。

なんでやー、と思っているとどうもcartonを実行した際に以下のように絶対パスで実行していて:
/path/to/perl-5.16.3/bin/carton exec -- ....
はて、と考えると/usr/local/bin/cpanmが存在していて、どうもそれを使っているくさい。/path/to/perl-5.16.3/bin にはパスを通してなかったので、そこで取り違えが起きたということみたい。

/usr/local/bin/cpanmを消せばいいんだけど、なんかあとで言われるのも嫌なので、その辺りも含めて色々やるラッパースクリプトからcartonを呼び出す事にして、ざっくり以下のようにしておいた

local $ENV{PATH} = join ":", perl_binpaths(), $ENV{PATH};
exec("carton", ... ) 
 perl_binpath() は こんな感じ

use Config ();
sub perl_binpaths {
my %hash = map {
($Config::Config{$_} => 1)
} qw(installbin installscript installsitebin installsitescript);
return keys %hash;
}

まぁここまでやらんで、普通に PATH=/path/to/perl-5.16.3/bin:$PATH でもいいとは思う。癖です。 
    このエントリーをはてなブックマークに追加 mixiチェック

10年物の20万行ほどあるWebアプリの配信部分をPSGI化したところ、先ほど無事○○Gbps単位のピークタイムをシステムの負荷をあげすぎず(アラートをあげず)に乗り切れたようです。

関係者の皆様お疲れ様でした。ご協力ありがとうございます。

最初パフォーマンスの問題があってがっかりしたけど、良いコード書けたと思うし、最終的にはちゃんと期待してたくらいのパフォーマンスが出て良かった。






ちなみにそのWebアプリっておまえの読んでるこれだよ、これ。  
    このエントリーをはてなブックマークに追加 mixiチェック

すごいヘビーな負荷を受けているPSGIアプリケーションで「なんでこれで負荷があがるの?」的な現象があったので二つほどTipを。ちなみにこれは 2013/03/06時点での話なので、もしこれをあなたが大分将来に読んでいるのなら、状況に変更がないかちゃんと確認すること!

まずこのお話の前提:mod_perlなアプリをPSGIに移行したかった。アプリはmod_perlハンドラで書かれているので、Apache::RequestをPlack::Requestに書き換えたり、ハンドラ部分をオブジェクトにしてキレイにするくらいで、基本的な構造は何も変えてない(←ここポイント)。あとはApache側とか設定をもりもりいじって、PSGIファイルを書いて、Starletでデプロイして、パフォーマンスが30%くらい悪くなった。さて、犯人は誰でしょう?

まずアプリケーションを組む側が「やっちまったなぁ?」な件:Plack::Builder::mount()を多用しすぎると大分ペナルティがある。 例えば、/foo => MyApp::Handler::Foo, /bar => MyApp::Handler::Barみたいなマッピングで処理を移譲する場合、mountでこう書けなくもないけど、

builder {
enable ...; # ミドルウェア
mount "/foo" => MyApp::Handler::Foo->new;
mount "/bar" => MyApp::Handler::Bar->new;
....
};

これはmount()することによって関数コール一個分余計に呼ぶし、それを何回も繰り返すので当然・・・遅い。今回は元々独立したハンドラが一杯あって面倒くさかったからこう書いちゃったけど、負荷が高いとよくないので、こう書いたほうがよい。

# pseudocodeだからこのまま使うなよ!
my %handlers = (
"/foo" => MyApp::Handler::Foo->new,
"/bar" => MyApp::Handler::Bar->new,
....
);
sub handle_psgi {
my $env = shift;
my $handler = $handlers{ $env->{PATH_INFO} };
if (! $handler) {
return [ 404, [], [] ];
}

$handler->handle_request($env);
}

builder {
enable ...; # ミドルウェア
enable ...;
\&handle_psgi;
}; 

ベンチはkazeburo先生のこれとかを参考に。まぁ当然の結果だけど、約2倍も処理速度がはやくなるね!

この変更をする際に、PSGIファイルから呼ばれる基本のディスパッチ関数をオブジェクトに埋め込んでいたのを一つの関数にまとめた。メソッドコールのオーバーヘッドもいらない。

次はPlack::Requestの話。今回の案件ではほぼ全てのリクエストにクエリストリングがついてきて、それをパースしないといけないんだけど、Plack::Request->query_parametersが重い。思いの外重い。

俺がランチミーティングをしてたらまたkazeburo先生が色々あさってきてくれて、query_parametersの中でuri()を2回呼んでて、なおかつこの結果はキャッシュされてない。URIオブジェクトの生成を愚直に2度行っている事と、クエリストリングをパースしたいだけなのにそもそもURIオブジェクトの生成とか必要ないだろ!ってことで以下のようなパッチを当てると5倍から10倍速くなる:

Optimize ->query_parameters
 
これは現時点で最新版のPlack(1.0016)には入ってない。

ここまで見て、Devel::NYTProfの結果にはもう最適化できそうなところが何も無かった、というところまで落とし込めた。ふー、よかったね!

・・・と思ったんだけど、よくよく考えるとこのクエリのパースってCのほうが絶対速いしCPU使わないよね・・・。ってことでCPANをQueryStringで検索したらそれらしき物がなかったのでXSで書いた:Text::QueryString

ふー、やったぜ、やれやれ。と思っていたらIRCでURL::Encode(::XS)の作者に「それ、俺のモジュールでできるし、URL::Encode::XSを使えば爆速だぜ!」って言われたのでベンチしてみたらText::QueryStringが負けたorz 多分URIデコードの部分だと思う・・・。ともあれ、すでにそういうものが存在するのであれば別にいいや、ってことでText::QueryString 0.03で別にモジュールあるよ!ってドキュメントを書いてオワコンにして、以下のようにURL::Encodeでモンキーパッチしちゃうことに:
use Plack::Request;
use URL::Encode; # URL::Encode::XSを入れておく事

{
no strict 'refs';
*Plack::Request::query_parameters = sub {
my $self = shift;
my $env = $self->env;
my $query = $env->{'plack.request.query'};
if ($query) {
return $query;
}
$env->{'plack.request.query'} =
Hash::MultiValue->new(@{URL::Encode::url_params_flat($env->{'QUERY_STRING'})});
};

これは割とマイクロな最適化だったけど、少しでもCPU使用率減らしたかったので、まぁまぁよいかな、と。

ちなみに結局アプリをホストしているサーバー単位ではApache(mod_perl 1.3)ベースの時に比べると+10%くらいの負荷ペナルティでPSGI化ができた。

というわけでまとめると、Apacheはやっぱり基本的な所では高性能。リクエスト関連のパースはやっぱりガチガチに作り込まれてるだけあってすごく低い負荷でやってのける。Perlだけでそれを代替すれば当然作り込まれたCには必ず負ける。だからPlackにちょろっと移行しただけだとやはり負荷の問題がでてくる・・・だけどそれさえもいくつかの良識的な最適化、優秀なオペレーションエンジニアとの協同作業、ある程度の試行錯誤を許してくれるデプロイ手順の確立、そして「まー、Perl遅いならCで書けばいいや」(もしくはCベースのモジュールで代替しちゃえ)という割り切りで確実・着実にApacheにもほとんどひけをとらないパフォーマンスを発揮できる、ということでした。

以上本日のPlackパフォーマンスTipsでした。 

    このエントリーをはてなブックマークに追加 mixiチェック

Data::STUIDをCPANにあげた。このツールはは前も書いたけど、64ビットの整数IDを作成するための簡単なサーバークライアントツールです。memcachedとそのクライアントみたいなイメージで使えます。

簡単なサーバースクリプトを同梱しておいたので、こんな感じで起動できます:

# バックグラウンドに送るとかは適時ご自分でどうぞ
> stuid-server --host_id=1 --port=9001
> stuid-server --host_id=2 --port=9002
> stuid-server --host_id=3 --port=9003 

で、これらのサーバーをクライアントに渡してやると、適当にIDを取ってきます。 
> stuid-client --server 127.0.0.1:9001 --server 127.0.0.1:9002 --server 127.0.0.1:9003
111499979255802135
サーバーを複数用意しておけば一個が万が一詰まっても、次のヤツに行くようになってます。

もちろん上記のはあくまでちょっと検証するためのスクリプトであって、実際はプログラムの中に埋め込んで使うほうが本来狙っているやり方です :
use Data::STUID::Client;
my $client = Data::STUID::Client->new(
servers => [ qw(127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003) ]
);
print $client->fetch_id, "\n";
IDの生成方法自体はSTFのそれと同じで、だいたい順番にならんだ64ビットの整数が返ってくるのでPKとかにいれてそのIDの順番で作成順序でソートしたのと同じ状態にしつつ、ユニークなIDをふれるという感じです。

あとcpanfile対応済みですので、cartonで簡単にサーバーを立てられますよ!

まだ実戦投入とかする段階ではなくて、準備として書いたものなので、なにかツッコミとかあったら是非お願いします。
    このエントリーをはてなブックマークに追加 mixiチェック

追記:名前が同じだと微妙かな、と思ったので変えた。

追記2:ついでにCPANにうpした

https://github.com/lestrrat/p5-Kage https://github.com/lestrrat/p5-Geest 書いてみた。それだけ。後悔はしてない。

Twiggyとかで動かす。

plackup -s Twiggy -a app.psgi
# または twiggy -a app.psgi とか

別に元のバージョンでよかったしなんの問題もなかったんだけど、ただ自分で書いてみたかった。
    このエントリーをはてなブックマークに追加 mixiチェック

以下のコードはDigest::MD5のオブジェクトAPIの使用方法。
use strict;
use Digest::MD5;

my $md5 = Digest::MD5->new;
$md5->add("foo");
$md5->add("bar");
$md5->add("baz");
warn $md5->hexdigest; # 6df23dc03f9b54cc38a0fc1483df6e21
$md5->add("hoge");
warn $md5->hexdigest; # ea703e7aa1efda0064eaa507d9e8ab7e
warn $md5->hexdigest; # d41d8cd98f00b204e9800998ecf8427e 
最初のhexdigest() の呼び出しは "foo", "bar", "baz"の値を使って計算したMD5の値。次の呼び出しは違う値を作成し、その次もまた値は違う。

これはどういうことになっているかというと、最初のhexdigst()の呼び出しはそれまでにadd()で追加してきたデータを使用してMD5の計算をしているのだが、hexdigest()を呼んだ瞬間に 内部ステートがリセットされて、また一からデータを追加していくことになる。

ということは、最初のhexdigest()は "foobarbaz"から計算したMD5で、その次は"hoge", 最後のは空文字列から計算した値。

Digest::MD5は一旦MD5を計算したら、それまでのステートをそこから先に持ち越さない。

この実装は当然だと思う。なんでかというと このオブジェクトは引数から値を作成するためのオブジェクトであって、何かの状態を表すためのオブジェクトではない。計算をするためのオブジェクトは、その計算を確定するまでステートを保持してもいいけど、その後は持っている必要も意味もないし、それよりか有害だ。$md5->add("hoge")した時にそれまでの"foo", "bar", "baz"から計算するための途中結果とかが残ってて計算結果に影響を与えたら、とか想像するとgkbrじゃない?

これに対して、例えばDBから値をもってきてそこから何か思い計算をする何か・・・例えばあなたの株口座を表すオブジェクトがあって、それを使い回している。時折特定の銘柄の過去一ヶ月の自分の株の損益評価をグラフにしたいからそのシリーズを取得しなければいけないようなオブジェクトならどうか

my $portfolio = Stock::Portfolio->new( user_id => "lestrrat ");

while ( ... ) {
# 時折 NTTの過去一ヶ月の取得株価と任意の日の株価の差額をリストで欲しい
my @profit = $portfolio->get_profits_last_30days( 9432 );

# そのほか $portfolioで色々操作するから $portfolio自体は
# 使い回す。
}
このような計算を必要とするなら過去30日分の株価をオブジェクト内にキャッシュしてるのはわかる。ただしここでも気をつけないといけないのはキャッシュするにしても銘柄コードをキーにしてキャッシュしないといけない。例えばインメモリでキャッシュするにしても以下のように"last_30days"のようなキーでキャッシュしてはいけない

sub get_profits_last_30days {
my ($self, $code) = @_;
...
$self->{last_30days} ||= $self->get_prices_last_30days( $code );
...
}

これだと$codeの値が変わってもlast_30daysの値が変わらない。ひどい罠ですね。以下のように必ずどの銘柄コードのキャッシュなのかわかるように格納しないとまざってしまう危険性が高い。

sub get_profits_last_30days {
my ($self, $code) = @_;
...
$self->{last_30days}->{$code} ||= $self->get_prices_last_30days( $code );
...
}

あと大事なのはキャッシュをするなら、キャッシュがパージされるタイミングがあるのかよく考えること。この例の場合は日付が変わったら当然新しい値が追加されて、一番古い値が消えるだろうから キャッシュパージのタイミングは日付だ。だからキャッシュ内にいつまで有効なデータか書いておくか、memcachedのような「別に消えても大丈夫」なキャッシュだったらキーに日付も含めておけばいい。

上記のようなステート保持が必要ないけど、それでも重い処理ならまずは「変数」として持って、必要な関数に渡しましょう。一番シンプルです!

どうしてもオブジェクトに保存したいならlocalを使うか、ガードオブジェクトを使いましょう。

# 最もシンプル。変数に格納しておく
sub foo {
my $self = shift;
my $data = $self->calculate_some_data; # 重い処理
$self->bar( $data );
$self->baz( $data );
}

# どうしても $selfに入れたい (1) local
sub foo {
my $self = shift;
local $self->{data} = $self->calculate_some_data; # 重い処理
$self->bar; # $self->get_data(); とかでキャッシュされたデータにアクセス
$self->baz;
}

# どうしても$selfに入れたい (2) ガードオブジェクト
use Scope::Guard;
sub foo {
my $self = shift;
my $guard = Scope::Guard->new(sub {
delete $self->{data};
});
$self->{data} = $self->calculate_some_data; # 重い処理
$self->bar;
$self->baz;
}
あともう一つの方法としては関数開始時に格納してあるフィールドを初期化する、とかあるけど、そもそもただのキャッシュで、次に計算する値が同じ物になる確率が低いなら関数を抜けた時にちゃんとクリーンアップされてるほうが筋がいいと思う。

最後に、それでもどうしてもそういうコードになってしまう場合。そういう事もあるかとは思います。現実はセオリー通りにはいかないです。その場合はしょうがないのででっかいコメントブロックを儲けて、なんでそんな事をしないといけなかったか、400文字程度の反省文を該当メソッドの前に書いてください。 そこまですれば後任の人が髪の毛をむしる必要がなくなりますね。

よろしくお願いいたします。
    このエントリーをはてなブックマークに追加 mixiチェック

特にまだ用途はないのだけど、STFの64ビットID生成ロジックにNet::Serverでフロントエンドつけて、さらにCache::Memcached風味な、複数サーバーのうちどれからかIDを取ってくる、という形でユニークIDジェネレーター書いてみた

Data::YUIDでよかったんだけど、コード読んだらなんか微妙なプロトコル がハードコードされてるし、ID払い出すだけでいいんだから、ソケット接続→即IDを返す!でいいような気がしたし、それならクライアントもそれだけ簡素化できるしーと思って思い立って書いてみた次第。

サーバー側は適当に固有のhost_idをふってあげて、何個か立ち上げてあげる:
use strict;
use Data::STUID::Server;

# perl server.pl <id> <port>
# perl server.pl 1 9001
# perl server.pl 2 9002
# とか Data::STUID::Server->run( port => $ARGV[1] || 9001, host => '127.0.0.1', host_id => $ARGV[0], );
で、それらにアクセスするようにクライアントを設定する感じ
use strict;
use Data::STUID::Client;

my $client = Data::STUID::Client->new(
    servers => [ qw(
        127.0.0.1:9001
        127.0.0.1:9002
    ) ]
);

for (1..10_000) {
my $id = $client->fetch_id;
print "Got $id\n";
とりあえずざっくり。
    このエントリーをはてなブックマークに追加 mixiチェック

小ネタ。僕はPerlのガードオブジェクトが大好きです。

例えばPSGIサーバーのリクエスト のログを出したい。ついでにどこのURLにアクセスされた時のログなのかも書き出したい。普通に考えると出力全てにURLをいれておけばいいんだけど、URLが長いとログが見にくい。
[/path/to/this/action?foo=1&bar=2&baz=3&hogehogehogehogehoge=1234] This is a debug log!

それならリクエスト開始と終了を区切ればどこからどこまでかわかるよね!ってことでガードで実装すると良い感じ 
続きを読む
    このエントリーをはてなブックマークに追加 mixiチェック

もしあなたのperl HTTPハンドラが「可能ならgzipしたコンテンツを返したい」と思うなら、Compress::Zlibを使うだろう。だがそれを毎回eval + requireするのは正直無駄だ。なぜならプログラムが起動した時にすでにそれを毎回行うのかどうかすでにわかっているからだ。
sub psgi_response {
my ($self, $content) = @_;
my $req = $self->req;
my $res = $self->res;
if ($req->header('Accept-Encoding') =~ /\bgzip\b/i) { if (eval { require Compress::Zlib }) { $res->header('Content-Encoding' => 'gzip'); $content = Compress::Zlib::memGzip($content);
}
}
... }
こんな場合は定数にするべし
use constant HAVE_ZLIB => eval { require Compress::Zlib };
sub psgi_repsonse {
my ($self, $content) = @_;
my $req = $self->req;
my $res = $self->res;
if (HAVE_ZLIB && $req->header('Accept-Encoding') =~ /\bgzip\b/i) {
$res->header('Content-Encoding' => 'gzip');
$content = Compress::Zlib::memGzip($content);
}
...
}
 これで何が違うかというとCompress::Zlibがなかった場合はHAVE_ZLIBが定数で偽と判断されるので、ifのブロック全体がそもそも評価される対象と見なされない(constant folding)

Compress::Zlibがあった場合はHAVE_ZLIBは定数で真なのでifの評価ではAccept-Encodingヘッダの評価だけにとどまる。

毎回eval + requireをするのと比べると大分かかるコストが違うのがわかると思う。 

どういう評価されるのかわからない時は perl -MO=Deparse ... で評価するといいんだぜ!
    このエントリーをはてなブックマークに追加 mixiチェック


コメントを残せないブログなんて!どうなの!
と、それは置いておいて、Perlでconstant folding・定数とされるのは文字列・数値リテラルか、定数扱いできる関数だけです。

定数扱いできる関数というのは実は決まっていて、以下の条件がそろわないといけない:

続きを読む
    このエントリーをはてなブックマークに追加 mixiチェック

Text::MeCab - すっげぇ久しぶりにバインディングをアップデートした

Test::STF::MockServer - STFと連携するアプリをテストするときのモックサーバーを簡単に立てるためのもの。基本的にはtokuhiromコード

ZMQ まわり - 色々pullreqが来てたのでそれらをマージ。ZMQ::CZMQをZMQ::LibCZMQ1 に動かして、ZMQ::CZMQをPurePerlなラッパーに変更

とあるC++プロジェクト - テンプレートとかひさしぶりに使って楽しんでるけど他人様にお見せできるものになってない。実は二つあるけどなかなか完成までこぎつけない。むーん。

・・・と、こう見てみると最近は主にCとC++書いてるな、俺。C系の言語好き。
    このエントリーをはてなブックマークに追加 mixiチェック

お題: 弊社のgitレポジトリは 社内からなら git:// でcloneできるけど、 社外からはできない(当たり前)。だけどとある事情によりsubmoduleを登録する際にはssh:// ではなく、git://で登録したい。

無理かなー。
無理だろうなー。

と思っていたら。git config で insteadOfってのを定義できるッ・・・・

    [url "ssh://git@git.mycompany.com/"]
        insteadOf git://git.mycompany.com/

こうすると、なんと!git pull とかするときにgit:// で登録されていたsubmoduleが勝手にssh経由でpullされてくるッ・・・・!

gitのこういう隠し機能みたいの、すごいけどなんつーか・・・まぁともかくたすかった!
    このエントリーをはてなブックマークに追加 mixiチェック

現在STF は分散オブジェクトストアとしてピーク時にフロントのディスッパチャー1台につき最大80Mbpsを捌いています。この通常のオブジェクト配信するための動作に関しては裏で実際のオブジェクトを格納しているストレージサーバーもさくさくと動いていて特に問題はないのですが(本当の事を言うとアクセス量が増え続けているので、ストレージは増やし続けないとiowaitがじわじわとあがっていく、という問題はあるけど、それはあくまでも中長期的な問題なので今回の話からは除外)、運用しているとストレージサーバー側でオブジェクトの実体(エンティティ)を補充したり、ストレージサーバー間で移動させたりという処理が必要になります。
この際「このストレージにはいってるオブジェクトを全部なめて、正しい状態に戻す」(リペア)という処理を行う事があります。STFのインスタンスごとに規模が違うのですが、最大規模で1ストレージに付き3000万個程度のオブジェクトが格納されているのでこの「全てのオブジェクト」に対する操作は結構膨大なストレージサーバーへのアクセスを生みます。

例えばワーカーを100プロセス展開しておくと、リペアが起こるまでは平穏なわけですが一旦リペアが始まると突然それぞれのワーカーが一斉に唸りを上げながらストレージを痛めつけ始めるわけです。この際ストレージサーバー全台に対してアクセスがばらけていればこの処理もまだ対応できるのですが、同じストレージサーバーにアクセスが集中すればもちろん死亡フラグが立ちます。

STFに触りはじめて1年半、この間ストレージサーバーは当然壊れますので、リペアはたびたび行う必要がありました。その際アクセス負荷による障害を起こさないようにするのは運用者によるワーカー数の調整が必要になっていました。具体的にはワーカーの数などをちょこちょこ調節するわけです。

STFを触り始めた時からつい最近までこのワーカープロセス数の設定はファイルに記述されており、ファイルの編集→プロセスの再起動という処理を行う必要がありました。もちろん設定ファイルはレポジトリに格納されているのでレポジトリで編集→デプロイでもいいのですが、これはこれでまた面倒な話です。

というわけでこれを自動化していくべく何をしたか:

続きを読む
    このエントリーをはてなブックマークに追加 mixiチェック

前提: STFのワーカーのプロセス数

STFを受け継いだ時点ですでにワーカープロセスの形は決まっていた。いくつかのサーバーにワーカーの「親」プロセスがいて、そいつが必要に応じて本当の処理をするための子プロセスをforkしていく。どの種別のワーカーがどれだけforkされているか、というのはParallel::Scoreboardに記録されてた。それぞれの種別のワーカーがどれだけforkされるべきか、というのは設定ファイル等に書いておく。設定ファイルは(面倒なので)全てのワーカーで共通。

共通設定ファイルにワーカー数が書いてあるということは、例えばワーカー(親)を増やしたりすると、その分だけ単純にワーカーが増える。例えば Replicateというワーカーが4プロセスforkされるように書いてある設定ファイルで、ワーカーを3から4に増やすと、システム全体のReplicateワーカー数が単純に12から16に増える。

だいたいの場合これでも問題ないけれども、そもそも直線的にワーカー数を増やした場合に皮肉な事に裏方のストレージサーバーが耐えられない、ということもありえる。ということは常に総ワーカー数を逆算しながら設定ファイルを書いて例えば「最大30個くらいのワーカーまで耐えられて、で今5サーバー x 4プロセスで20個で、これから4個追加するから1サーバーあたりのプロセス数は・・・」とか考えなくてはならない。

別に親ワーカーの数が10000とかおかしな事にならなければできない事じゃないけど、まぁそれでも計算を間違える可能性もあるし、なにより人間がその計算方法をちゃんと意識しなければいけないのがいけてない。総数から勝手に調節してほしい!

・・・というのがそもそもの発端。

続きを読む
    このエントリーをはてなブックマークに追加 mixiチェック

YAPC::NAに行ってきて「お、これはいいな」と思ったのがsmartcd。smartcdでぐぐっても"Smart CD"しか出てこなくてCDをリッピングする話ばっかヒットするんだけど、これは違うヤツね。

基本的には単純にディレクトリを変更するとフックが走って、各ディレクトリでなんらかの設定を行うためのもの。で、僕は何をやっているかというとPerlのライブラリパスとか、オプション設定とかをこれでプロジェクトごとに設定するようにした。

例えばプロジェクトごとの依存関係は project/extlib に入れたいなーと思ってて、それを毎回設定するの面倒くさいので以下のように設定

  autostash PERL_CPANM_OPT="-l__PATH__/extlib -nq"

これで特に考えずにextlibに一通りはいる。で、一旦ここにインストールしてしまうと、そこへのパスを通さないといけないので、PERL5OPTも設定:

  autostash PERL5OPT="-Mlib=__PATH__/extlib/lib/perl5 -I__PATH__/lib"

これで以下の設定がproject/ ディレクトリに移動すると自動的に適用される。このディレクトリを離れれば設定はクリアされる。

  PERL_CPANM_OPT="-l/path/to/project/extlib -nq"
  PERL5OPT="-Mlib=/path/to/project/extlib/lib/perl5 -I/path/to/project/lib"

これらの設定はディレクトリごとに設定できるので、project2 にcdしたらproject2/extlib を見るようにできる。よく使う設定は テンプレートとして保存しておくこともできる。

あとはこれをうまく設定すれば、ディレクトリやプロジェクト内容が違ってても同じコマンド名をaliasにすることによって、正しい動作を呼び出すようにもできる。例えばproject/ はperl、project2/ はruby のプロジェクトだったとして、runtest というaliasをそれぞれのテスト実行コマンドにひもづけておけば、あんまり意識せずにおなじよーにテストを実行とかできる。

とりあえず自分はパスの設定とかにしか使ってないけど、今のところ結構便利
    このエントリーをはてなブックマークに追加 mixiチェック

balancer.png
至極簡単なハックなんだけど、q4m-balancerというスクリプト書いてみた。シナリオとしては、(1) q4mが複数あって、(2)それぞれに繋がってるワーカーの数・処理能力にバランスの不整合があって (3) キューの中身について処理順番等が関係ない、という状況においてq4mに入っているメッセージの数にかたよりが見られている状態をある程度改善するスクリプト。

入っているメッセージ数が最大のキューと最小のキューの差が 最大 > 最小 * 2 だった場合に最大のほうのキューの中身を40%最小のほうに移す、ってだけ。グラフを見れば分かるとおり、右側のほうにグラフの内容がイーブンになっているのが見えると思う。これを定期的に動かす事によって処理能力の高いワーカーのほうにより処理を任せられる。

まぁくだらないっちゃくだらないですが、ワーカーを遊ばせておくのもあほらしいのでちょっと書いてみた次第。
    このエントリーをはてなブックマークに追加 mixiチェック

ZeroMQ.pmをつい相続してしまってから本家libzmqの変更についていけず大分悩んだんだけど、ここのところ直してリクエストが多かったので一念発起してlibzmqのPerlバインディングを大幅リニューアルしました。まだ正式リリースは出してないので、何か文句を言うなら今のうち!英語での解説はこちら

いままでlibzmqの直接バインディング、定数、Perlっぽいシンタックスシュガーのラッパーを全部同じディストリビューションにいれていたのをばらしました。これまでZeroMQ::Raw とされていたのは ZMQ::LibZMQ2 ならびに ZMQ::LibZMQ3 となりました。ZMQはこれらのバインディングをうまいことオブジェクト風味にするPPモジュール。ZMQ::Constantsはlibzmqのバージョン毎にやたらと変わるので別モジュールで切り出しました。あとすごくざっくりですが CZMQのラッパも書きました。こちらはZMQ::CZMQというもの。

なおZeroMQ → ZMQの変更についてはlibzmqのメンテナから名前を変えてくださいという半強制的なお願いが出ていたので名前の変更はもう大分前から予定してました。


というわけでお父ちゃん疲れたよ。
    このエントリーをはてなブックマークに追加 mixiチェック

STFではキューへのジョブ挿入は基本的にはエラーが起ころうとなんだろうと無視して次の処理に移るように最初から書いていたけど、挿入時にDBが接続を受け付けるけどそこでブロックしてしまった場合の考慮をしてなかったので、厳しめのタイムアウトを設定するとともに、そういうエラーが起こった際にキューがSPOFにならないようにkazeburoさんのアドバイスの元 複数のキューに書き込みをできるようにしました

現時点ではこの機能はQ4MだけでTheSchwartzはまだ実装してない。ドキュメント書かないとな・・・
    このエントリーをはてなブックマークに追加 mixiチェック

小ネタ。GETした内容のMD5が欲しかったので。

    use strict;
    use Furl::HTTP;
    use Digest::MD5;

    my $digest = Digest::MD5->new;
    my $furl = Furl::HTTP->new;
    $furl->request(
        method => "GET",
        url => "http://whatever/text.txt",
        write_code => sub {
            my ($code, $msg, $hdrs, $partial) = @_;
            return unless $code ne 200;

            $digest->add( $partial );
        }
    );
    print $digest->hexdigest;
    このエントリーをはてなブックマークに追加 mixiチェック

STFでMOVEをサポートした。オブジェクト名を変更できるヤツ。あとSTF_ENABLE_STORAGE_METAってのを1にすると管理画面からストレージ毎にコメントをつけられるようになる。

あと次に時間があるときに指定フラグがオンだったらオブジェクトのMD5とエンティティのMD5を記録するようにしようと思う。ただし、これはあくまでもオプションとして実装する予定。
    このエントリーをはてなブックマークに追加 mixiチェック

このページのトップヘ