2006-12-01 [長年日記]
@ [perl] mod_perl 2.0.3がリリース
されているようです。mod_perlも1年に1回のペースでバージョンアップされてるんですね。
2006-12-06 [長年日記]
@ [perl] DateTime->now(time_zone => 'local')って激遅
Perl界隈の日付操作モジュールのデファクトスタンダードであるDateTimeですが、今まで「タイムゾーンなんてシステムで定義されているものから勝手に取ってきてほしい」という理由で
DateTime->now(time_zone => 'local');
なんてことをしてたのですが、これが激遅なことが発覚しました。time_zone => 'Asia/Tokyo'と明示的に指定する場合とで、性能差が下記のようにはっきり現れます。以下ベンチマークのスクリプトとその実行結果。
#!/usr/bin/perl
use strict;
use DateTime;
use Benchmark qw(cmpthese timethese);
my $result = timethese(5000, {
"now('local')" => sub {
DateTime->now(time_zone =>'local');
},
"now('Asia/Tokyo')" => sub {
DateTime->now(time_zone => 'Asia/Tokyo');
},
});
now('Asia/Tokyo'): 1 wallclock secs ( 1.64 usr + 0.00 sys = 1.64 CPU) @ 3048.78/s (n=5000)
now('local'): 7 wallclock secs ( 4.78 usr + 1.77 sys = 6.55 CPU) @ 763.36/s (n=5000)
というわけで、DateTime->nowするときは、安易な方法に頼らずに明示的にタイムゾーンは指定しましょう、というお話でした。
追記: DateTime::TimeZoneのPODを見ると、time_zone => 'local'を指定した場合は、まずTZなどの環境変数をチェックして、それがなければ/etc/localtimeなどのファイルを見に行くようです。というわけでIOが発生していたので強烈に遅くなっていたようですね。
試しに
$ TZ='Asia/Tokyo' perl さっきのベンチマークスクリプト
と環境変数定義したらtime_zone => 'local' も time_zone => 'Asia/Tokyo' もほぼ同じ速度になりました。
2006-12-11 [長年日記]
@ [cpan] CGI::Session::Driver::memcachedについて外人からメール来た
「なんかパフォーマンスの測定結果とかないの?」ってメールが来ました。「自分で作ればいいじゃん」とツッコミそうになりましたが、ちょうどいい機会なのでちょっと測ってみることに。
以下ベンチスクリプトのcgi_session_bench.pl。ちなみにMySQL、Memcachedともにlocalhostです。MySQLのセッションデータ格納テーブルにはid列にインデックスが張ってあります。
#!/usr/bin/perl
use strict;
use Benchmark qw(cmpthese timethese);
use CGI::Session;
use DBI;
use Cache::Memcached;
use CGI::Session::Driver::memcached;
my $times = shift @ARGV || 5000;
my $dbh = DBI->connect(
$ENV{DBI_DSN} || 'DBI:mysql:cgi_session;host=localhost',
$ENV{DBI_USER} || 'root',
$ENV{DBI_PASS} || '',
{ RaiseError => 1 },
) or die $DBI::errstr;
my $memd = Cache::Memcached->new({
servers => [ 'localhost:11211' ],
debug => 0,
compress_threshold => 10_000,
});
my @data = map { $_ => { $_ x 100 } } ('a' .. 'z');
#
# WRITE
#
my ($mysql_sid, $memcached_sid);
timethese($times, {
"WRITE(mysql)" => sub {
my $mysql = CGI::Session->new('driver:mysql', undef,
{ Handle => $dbh });
$mysql->param(data => \@data);
$mysql->flush();
$mysql_sid = $mysql->id;
},
"WRITE(memcached)" => sub {
my $memcached = CGI::Session->new('driver:memcached', undef,
{ Memcached => $memd });
$memcached->param(data => \@data);
$memcached->flush();
$memcached_sid = $memcached->id;
},
});
#
# READ
#
timethese($times, {
"READ(mysql)" => sub {
my $mysql = CGI::Session->new('driver:mysql', $mysql_sid,
{ Handle => $dbh });
my $d = $mysql->param('data');
},
"READ(memcached)" => sub {
my $memcached = CGI::Session->new('driver:memcached', $memcached_sid,
{ Memcached => $memd });
my $d = $memcached->param('data');
},
});
$ perl cgi_session_bench.pl | sort Benchmark: timing 5000 iterations of READ(memcached), READ(mysql)... Benchmark: timing 5000 iterations of WRITE(memcached), WRITE(mysql)... READ(memcached): 12 wallclock secs (10.89 usr + 0.41 sys = 11.30 CPU) @ 442.48/s (n=5000) READ(mysql): 17 wallclock secs (11.63 usr + 0.23 sys = 11.86 CPU) @ 421.59/s (n=5000) WRITE(memcached): 7 wallclock secs ( 5.63 usr + 0.18 sys = 5.81 CPU) @ 860.59/s (n=5000) WRITE(mysql): 10 wallclock secs ( 6.03 usr + 0.11 sys = 6.14 CPU) @ 814.33/s (n=5000)
つーことで、若干CGI::Session::Driver::memcachedの方が速いってことですね。思ったよりパフォーマンスに差が出ないですなぁ。
2006-12-13 [長年日記]
@ [cpan] CGI::Session::Driver::aggregator
以前セッションのデータを memcached と mysql に書き込む CGI::Session::Driver::memcached_mysql を作成したのですが、社内の人に「これって例えばfileとmysqlとか、違うドライバだったら毎回同じようなモジュール作らなきゃいけないので、どのドライバでも使えるような汎用的なモジュール作れない?」と言われたので作ってみました。
use CGI::Session;
use CGI::Session::Driver::aggregator::Drivers;
use DBI;
$dbh = DBI->connect('DBI:mysql:cgi_session;host=localhost', 'root', '');
$drivers = CGI::Session::Driver::aggregator::Drivers->new;
$drivers->add('file', { Directory => '/tmp' });
$drivers->add('mysql', { Handle => $dbh });
$s = CGI::Session->new('driver:aggregator', $sid, { Drivers => $drivers });
$s->param(hey => 'Blur blur blur!');
というように、$drivers に対してセッションのデータを書き込みたいドライバを追加していくと、データを読み込む時は $drivers->add した順、つまり file からまず読んでくれて、書き込むときは(追加したのとは逆の順序で) driver:aggregator がデータを各ドライバに書き込んでくれるという寸法です。
モジュールのインタフェースに関しては色々悩んだのですが、上記の形が現状のCGI::Sessionのプラグイン機構に一番しっくりくるのでこんな感じに。これでfileでもmysqlでもmemcachedでもなんでもいけるようになりましたとさ。
さきほどCPANに上げたのですが、こちらからもダウンロードできます。
2006/12/14追記:データを書き込む際の順番が逆になっていたのでver 0.03で修正しました。
2006-12-16 [長年日記]
@ セッションにmemcachedを使うかどうか
なんか僕のエントリが元になり軽く議論になっているようですが...
個人的にはmiyagawaさんのVox/LJの方針に賛成で、「消えては困るデータ」はプライマリーのストレージとしてmemcachedを使うのではなく、MySQLに入れる方が安全だと思います*1
MySQL 使ってると、セッションデータを定期的に消してやらなきゃいけないけど DELETE FROM sessions WHERE timestamp >= '2006-12-01 00:00:00'; とかはすごく重かったりして、ここでまた刺さる
というのは、InnoDB+timestampカラムにインデックス張れば解決するのではないかと思いますが、そんな単純な話ではないのでしょうか?
ちなみにmiyagawaさんのいう
データベースに書き込む際に memcached に同時に書き込み、読み込み時には memcached -> データベースと fallback する (= Write-through Cache)
を実現するためにCGI::Session::Driver::aggregator作りました。
ただ、基本的にセッションって確認画面を挟むようなデータ登録画面で使うものだと思いますが、こういうケースだと
1. 入力画面 2. 確認画面 (セッションデータ書き込み) 3. 完了画面 (セッションデータ読み込み)
という感じでReadとWriteが同じ回数なので、Writeで2箇所のストレージに書き込む分 Write-through Cache でもあんま速度的なメリットはないのではないかと思います。ログイン状態を管理するセッションだったらReadが多い分有用だとは思いますが...
とまあ、こんなに色々理由があるんで、並列でもない、サーバが複数でもないベンチマークだけで採用の是非を判断するのは違うかな
これは本当その通りで、もともと質問してきた外人さんも「実際のアプリケーションの世界では(僕がやったベンチとは)また違った結果が出るだろうねぇ」と言っていたし、「これはあくまで一つの観点からの計測でしかない」とは言ってあるので、あのベンチの結果を鵜呑みにはしないと思います*2。セッション管理も用途や状況に応じてストレージ選べないと、ってことで。