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.

カテゴリ:Perl

本当に手前味噌な話ですけど、STFさん。今回も「あー、俺いいソフトウェア書いた」と満足できたので、この記事を書きます。ちなみにSTFとはみんな大好きPerlで書かれた分散オブジェクトストレージです。(github)

tl;dr;

  • ああ、俺いいコード書いた
  • 8ヶ月間最初のスクリプトをキックする以外何もしてないけど、18億個のデータを無事格納しおえた
続きを読む
    このエントリーをはてなブックマークに追加 mixiチェック

The Perl Foundation (TPF)の助成金プログラムの5月ラウンドの募集が始まりました。本文はこちらです

助成金の仕組みはPerlに関連する公共性の高いプロジェクトに対して金銭的サポートをするために作られました。採択されると平均的には数万円〜数十万円の助成金がプロジェクトのゴール完了とともに交付されます。本当に有益で実現性の高い物であれば、100万円程度の助成金まで交付される可能性があります。

助成金利用者にとっては助成金そのものがうれしいだけではなく、コミュニティに支持されたプロジェクトを牽引・実装したという実績になりますので、その後役立つはずです。是非応募の検討をお願いいたします。

なお前回日本からひとつの応募が採択され、助成金が交付される運びになりました。今回も日本からなにか応募されると大変うれしいので、もしなにかPerl関連のプロジェクト(Perlコミュニティを支援するなにか - ドキュメントやサポートプロジェクト・サイトなど、Perl利用者みんなが助かるツール・モジュールなど)のアイデアや、今すでに作業途中のものがあれば是非応募してみてください。参考までに、前回採択されたプロポーザルがこちらです

英文に自信がない方でも 私に連絡をいただければ清書等のお手伝いはできますので@lestrratまで是非ご相談ください。

ひとつ残念な事は募集開始から締め切りまでが10日ほどしかないことです。今回の応募締め切りは5/10です。ただし、前回(3月)のラウンドから始まった試みのひとつとして1年間に募集回数を増やすというのがあるので、今回もし準備が間に合わないようでもすぐ次のラウンドが始まる予定ですので、是非ご相談ください。

皆様のご応募をお待ちしております
    このエントリーをはてなブックマークに追加 mixiチェック

ちょっと前から私が関わらせてもらっているThe Perl FoundationのGrants CommitteeはPerl関連で広い範囲において有用性が認められるプロジェクトにたいして定期的に助成金を交付しています(詳細はこちら)。基本的に誰でも応募可能です(応募方法はこちら)。

この活動ルールにいくつか変更が加えられました(原文はこちら)。なおTPF Grants Committeeには現在自分とMakoto Nozaki(筆頭メンバー)さんの二人の日本人が関わっておりますので、日本からの応募が今までのどの時点よりも通りやすい(便宜を図るという事ではなく応募に不備があった場合などの対応について日本向けの対応がちゃんとできるという意味です)ので、皆様是非ご検討ください。

なお変更内容については以下の通りです:

1. 二ヶ月おきに応募審査が行われる

これまでは四半期ごとでしたが、 もっと頻繁に審査・受付が行われるようになります。

2. 助成金の上限が大幅にあげられました

これまでは$3000が上限だったのですが、これが$10000まであげられました。原文には「上限がなくなった」ともありますがその後に書いてあるとおり諸々の理由により規則上は$10000が上限です。ただしこれは「お金があれば」なので、その時々によって交付可能な助成金の上限は変動します。

重要な事なのでもう一度書きますが、応募方法はこちらです。英語や応募方法に関するヘルプが必要な場合は@lestrratまでご一報ください。皆様のご応募をお待ちしております!また、この制度を活用できそうな人にこの情報が届くように拡散していただけると助かります。

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

The Perl Foundation (TPF)のGrants Committeeのメンバーに選出されて就任依頼を受けたのでこれを謹んで引き受けることにしました!一応アジアからは唯一のメンバーとなっております。

Grants CommitteeはTPFから助成金の交付関連の管理をしているTPF内の委員会です。助成金を申請するにはこちらのルールに従って申請をメールするだけです。審査は年4回。現時点では助成金の上限は3000USドルと なっていますが、この上限はそのうち変更されるという噂を聞いています(そのあたりのルールまわりの審査・議論にも参加できる予定です)。

助成金の応募に関しては英語の壁だなんだはあるかとは思いますが、日本発のおもしろい技術はたくさんありますしオープンソース系の自分の仕事に対してなんらかの報酬がつくのは多少モチベーションがあがる材料にもなると思いますので、興味があれば是非応募してみてください!

ちなみに英語が問題ならば多少のお手伝いはできますので @lestrrat にお気軽にご相談ください。
 
    このエントリーをはてなブックマークに追加 mixiチェック

はい、というわけで自分のトークです:



昨年12月頃から関わってるlivedoorBlogのコードを触っていた時の憤りをスライドにぶつけてみました。
追記:スライドに「ログにマーカーをつける」というのは、(コード読んでないけど)多分こちらのエントリにあるLog::Minimal::Indentとだいたい同じ感じのヤツです

ところでWeb上で見かける感想の中でこんなのがありました:
今年個人的に一番衝撃的だったのはやっぱ、livedoor blogのPlack化です。技術的な側面もさることながら、ああいう近視眼的には何のメリットもないし、逆にデメリットの方が大きそうな案件にリソースを割くジャッジができる会社としての姿勢が本当に凄いなと。


実はビジネス的にも意味はあるんだなー。

なかなか書くことができなかったんだけど、その内容というのがこちらと→ ブログのお引っ越し機能を大幅に強化しました! (この中の「記事URLの引き継ぎを可能に」というのがポイント)、あともうひとつ、こちら→ブログ内のURLを自由に設定できるようになりました です

エンジニアの方でしたらちょっと考えるとこれがどういう事なのかわかるかと思います。記事URLが自由になるってことは・・・もうパターン固定のディスパッチングができないのですね。元々のディスパッチング機構はApacheモジュールとして書かれていて、mod_perlの実装はこのApacheモジュールとべったりだった。これをPSGIにしたらApache側と分離したおかげできれいに実装できた、と。

(まぁ本当の事言うとPSGI化してたら「これならできるじゃん!」ということがわかってやったのだけど。)

ということでPSGIに変えたいためだけに変えたってわけではなくて、PSGIにすることによって格段に自由度が増す事がわかっていたのでそれをベースに新機能がリリースできた、というところです。 

実はこの機能のリリースがもう少し早かったらこれについても話せたのだけど、 ギリギリのところで含められなかったので、mod_perl → PSGIまでで話が終わった(時間もなかった)

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

主に自分用メモ。例えば以下のようなコードからDBIハンドルを取得してるとする

use strict;
use DBI;

sub get_dbh {
DBI->connect_cached(....);
}

sub foo {
my $dbh = get_dbh();
$dbh->do(...);
}

sub bar {
my $dbh = get_dbh();
$dbh->do(...);
}

foo();
bar();
.... 
で、まぁSET NAMES utf8を自動的に発行したいなーと思ったので、get_dbhに手を入れる

sub get_dbh {
my $dbh = DBI->connect_cached(...);
$dbh->do("SET NAMES 'utf8'");
return $dbh;
}
そうするとget_dbh()内で DBIハンドル自体はconnect_cached()でキャッシュされた同じハンドルが返ってくるのだが、毎回SET NAMES 'utf8'が発行される。これは無駄だし、例えば一つ手前のステートメントの状態を期待するコードを書くと破綻する:

{
my $dbh = get_dbh();
$dbh->do("INSERT INTO ...");
}

{
my $dbh = get_dbh();
# get_dbh()にSET NAMES utf8がなければ、直前のステートメントはINSERT INTO
# get_dbh()にSET NAMES utf8があると、直前のステートメントは SET NAMES utf8
my $id = $dbh->{mysql_insertid}; # SET NAMES utf8の後なので、insertidは存在しない
}

なので、一回SET NAMES 'utf8'を発行したら、それを覚えていたい。だがDBIハンドルはハッシュのように扱えるものの、適当なキーを入れようとすると怒られる:
if (! $dbh->{MySuperSpecialFlag}) { # ERRRRRRORRRRRR
$dbh->{MySuperSpecialFlag} = 1; # ERRRRRRORRRRRR
$dbh->do("SET NAMES 'utf8'");
ところがprivate_で始まるキーなら使える!これで勝つる!
sub get_dbh {
my $dbh = DBI->connect_cached(...);
if (! $dbh->{private_my_super_special_flag}) {
$dbh->{private_my_super_special_flag} = 1;
$dbh->do("SET NAMES 'utf8'");
}
return $dbh;

ということで万事解決。
本件はちらっとつぶやいたら生き字引の@tokuhiromが覚えてたので助かりました。
    このエントリーをはてなブックマークに追加 mixiチェック

現時点ではあんまり具体的に何をしたか書けないけどブログにでっけぇ変更を入れた。システム全体の根本的な部分なので一発でキレイにとはいかなかったが、まぁ変更の規模に対して考えると妥当な感じだろう。本番化には1週間かかった。なので大分開放感がある。

具体的な事はもっと後になったら言えるが、とりあえず今の段階では Apacheモジュールを殺し、1000行近いRewriteRuleを殺し、PSGI側でミドルウェアを9個作成してPSGI側でApacheが今までやってたことを全て吸収した。

(追記 8/23 12:20)あとログがltsvになったよ!その先の事はモリス先生がやってるよ!このログもプロキシ側でログを取るのにアプリ側からの情報を組み込むとかしなきゃいけんかったので地味に面倒くさかった。わかってしまえば簡単だったけど。

パフォーマンスは正直言って、Apache版と比べて色々追加でやってることもあるので10%程落ちた。だがこれを持ってものすごい自由度を手に入れたし、あの目がくらむRewriteRuleの山とおさらばすることができた。さようならRewriteRule! 君のことを初めてviで開けてみたときから大嫌いだったよ!あと、Apacheモジュールもまぁ、悪くはなかったけど、手を入れるのが億劫だったのでやる気が起きなかった。近いうちにgit rmしてやる。

ちなみにパフォーマンス改善は相変わらずkazeburoさん無双。こっちもバグ修正とかあったりしてる間にアクセスは来続けるので、同時に見ててくれるのが大変助かります。今回もHTTPヘッダ一個調節するだけでLAが1/4になるとか見てて「ほほぉ」と思いながら作業できました。

あと今回も大量の(環境変数の指定で適時畳み込まれたり有効にしたりできる)デバッグログを入れて、アプリケーションの流れを完全に追える形を作ったおかげで問題が起こっても追うのは自分的にはかなり簡単でした。最後の最後で色々取りこぼしがあった際に大変助かった。

というわけでしばらくぼーっとしたい。 
    このエントリーをはてなブックマークに追加 mixiチェック

ほぼ一年前に「STFワーカーの自律分散と適応スロットリング」という記事を書いた。

STFというAmazon S3っぽいツールの裏方のワーカーをどう「サービスの邪魔をせずに」「しかし可能な限り大量のデータを速やかに処理するか」という命題をどう解消した、と言う話。

あれからほぼ1年たって「そういえばもう大分STFなにもしてねぇな」と気づいたのでこのエントリを書き始めた。

あれ以来ワーカーの過負荷によるアラートが来たことない。あれ?と思ったらワーカーが止まっててキューがいっぱいいっぱいになっちゃった、というのもない。足りないワーカーは勝手に補充されるし、ストレージの負荷が高い時は勝手に自動的にスロットリングされる。要はもうこの1年近く俺は管理画面でフラグをちょいと変えたりグラフを見たりする意外ほぼなんにもしてない。勝手に動いている。

この協調メカニズムを作るのにMySQLだけを使って書いたのが自分的にポイントが高い。データを高度に共有するならともかく、リーダーエレクション程度ならZooKeeperなんていらねぇよ!

というわけで本当にただの自画自賛エントリでした。STFわりといいと思うんだけど、なかなか「使って!」っていう機会がないのが悲しい。
    このエントリーをはてなブックマークに追加 mixiチェック

Perl徹底攻略 (WEB+DB PRESS plus)
Perl徹底攻略 (WEB+DB PRESS plus) [大型本]

書き下ろしの章があったり、連載当時perl 5.10とかの話だったのがperl 5.18対応になってたりとわりとアップデートされています。自分が書いた物としては非同期プログラミングの章とか入ってます。あと一応この数年間監修は全部やってきて全ての章に関わってはいるのでなかなか感慨深いですね。

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

(追記)なんか最新版ではなおってたらしいです!

現時点のLWP::UserAgentで meta nameに":"が使用されている(例:twitter:cardに対応している)サイトのコードを取得しに行くと色々妙な事が起こる。何が問題かというと、LWP::UserAgentはコンテンツのデコードを行ったりする際にHTMLの中身を解析してメタタグから情報を引っ張ったりして文字コード判定のヒントを仕入れたりしてる際、HTML::HeadParserというモジュールがそのようなメタデータをHTTP::Headerオブジェクトに格納しようとしているのが問題を起こしている。

例えば、
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
という指定があれば
my $res = $lwp->get("...");
print $res->header('Content-Type');
には上記のメタタグの値が入る。内部的には以下のようにメタタグの中身をHTTP::Headerオブジェクトに格納している
$headers->push_header("Content-Type", "...");

同様にhttp-equivでなくとも、name属性がある場合は "X-Meta-...."というヘッダにその値を格納するようになる。なので以下のようにtwitter:card用のメタタグが入っていた場合:
<meta name="twitter:card" content="summary" />
この値をヘッダに入れようとするわけだが、このname属性、コロンが入っている。ということはヘッダ名は・・・"X-Meta-Twitter:Card"。ぎゃー!これRFC違反!ヘッダの名前に":"を入れちゃいけない!そんなところでHeadParserの処理がおかしなところでとまってしまうのであります。

これを回避するには現時点ではLWP::UserAgentのparse_head属性を偽にする必要がある
my $lwp = LWP::UserAgent->new(parse_head => 0);
# もしくは
# $lwp->parse_head(0);
でもこれだとHTMLヘッドセクション内のヒントを使うな、って話になるんだけどまれに必要な時もあるし、それに段々twitter cardを実装しているサイトも増えてるし、おかげで僕は先日何時間も無駄にしたし・・・・ということでHTML::HeadParser::Liberalというモジュールを書いた。

使用方法はuseするだけ。
use HTML::HeadParser::Liberal;
これでtwitter:cardもパースして、X-Meta-Twitter-Cardというヘッダ名でHTTP::Headersオブジェクトからアクセスする事ができます。


このモジュールはHTML::HeadParserの内部にかなり強引に手を入れて、 twitter:cardのような書式のメタタグ名があったら ":"を無理矢理"-"に変えてヘッダに格納するようにする、というだけ。将来的に他にも必要な調節があったら入れるつもりだけど、とりあえずはこれだけです。

ちなみに効果はグローバルです。というわけでtwitter:cardもいいからとりあえずパースしろよ!って時にはどうぞ。 そのうちHTML::HeadParser本体に修正が入る事を祈る・・・
    このエントリーをはてなブックマークに追加 mixiチェック

Perl5 Census Japan 2013に回答いただいた皆様、ご協力ありがとうございました!知らなかった人のために説明しておくと、私が2013年4月7日から19日までの間アンケート形式で日本でのPerlの利用状態等を知りたいと思い回答を募りました。回答数は394でした。

なるほどねー、へー、と思いつつデータを見ていました。取り急ぎ今回はシンプルな回答の集計結果をお知らせしようと思います。これからさらに面白い解析は是非このエントリの最後にあるデータを使ってみていただけると嬉しいです。

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

Test::Moreのsubtestのテストはどう書くのが一番きれいなのか

コードを見る限り、ガードオブジェクト使うとteardown部分は気が楽ですよ。以下のような使い方をすればガードオブジェクトはスコープを抜けた瞬間に必ず実行されるのでteardownのタイミングなんて気にする必要さえない。

use Scope::Guard;

subtest "A context" => sub {
my $subject;
my $setup = sub {
$subject = Bar->new;
return Scope::Guard->new(sub {
undef $subject;
});
};

subtest 'foo_method' => sub {
subtest 'given xxxx arguments' => sub {
my $guard = $setup->();
....
# teardownの明記はいらない
};
};
};
で、これをsubtestごとにやるってのが常態化するなら、全部t/Util.pm とかにまとめちゃえばいい(けどそのあたりはお好みで)。ちなみにScope::Guard使わなくても自分で簡単にガードオブジェクトは作れるので、依存関係を増やしたくない場合でもOK。
    このエントリーをはてなブックマークに追加 mixiチェック

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チェック
カテゴリー
記事検索
メッセージ

名前
メール
本文
アーカイブ

このページのトップヘ