DBIx::SkinnyでCacheを取ってみたり

業務でSkinnyとmemcacheとShardDBを使ってるんですが兎にも角にもキャッシュやジョブキューで非同期処理などパフォーマンス重視の開発するのが大好きな人たちと仕事してるんですが、DBからとってくきた情報をハッシュ化しておいてからmemcacheに突っ込んで、それを取り出してオブジェクト化するっていう処理を書いたんですが、、、

そんなに速くなるのかいな

と思いました。
ベンチマークを書いてみました。

ちなみに言語はPerlで、O/RマッパーはDBIx::Skinnyです。テストで使用するのはSQLiteにしました。

比較対象
  • メモリ上に保存してあるハッシュからDBIx::SkinnyのRowオブジェクトを生成
  • memcacheに保存してあるハッシュをとりだしてDBIx::SkinnyのRowオブジェクトを生成
  • DBにアクセスしてDBIx::SkinnyのRowオブジェクトを生成
予想

速さは上述の順番になる

ベンチ
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw($Bin);
use Path::Class qw/dir file/;
use Data::Dumper;
use Devel::Size qw(size total_size);
use lib dir($Bin, '..', 'lib')->stringify;
use lib dir($Bin, '..', 'extlib')->stringify;
use Shaq::Api::Skinny;
use Shaq::Api::Memcached;

use Benchmark qw(:all);

my $config = {
    memcache => {
        namespace => 'eg_',
    },
    skinny => {
        master => {
            db_class       => 'Proj::DB',
            connect_info   => ['dbi:SQLite:dbname=db/skinny.db'],
            query_log_path => './logs/skinny_master_query.log',
#            log_mode       => 1,
        },
        slave => {
            db_class       => 'Proj::DB',
            connect_info   => ['dbi:SQLite:dbname=db/skinny.db'],
            query_log_path => './logs/skinny_slave_query.log',
#            log_mode       => 1,
        },
    }
};

my $memcache = Shaq::Api::Memcached->new( $config->{memcache} );
my $skinny   = Shaq::Api::Skinny->new( $config->{skinny} );

my $db = $skinny->master_db;

# 実験用テーブルを作成
$db->do(q{drop table if exists users});
$db->do(q{
    create table users (
        id       INTEGER PRIMARY KEY AUTOINCREMENT,
        rid      VARCHAR(10) NOT NULL,
        name     TEXT    NOT NULL,
        birth_on DATE,
        created_at DATETIME,
        updated_at DATETIME
    )
});

# INSERT INTO user (name), VALUES ('nekokak');
# を実行して中身のデータを確認
my $row = $db->create('users',{name => 'nekokak'});
print $row->id, "\n";   # print 1
print $row->rid, "\n";   # print 1
print $row->name, "\n"; # print 'nekokak'
print $row->created_at, "\n";
print $row->created_at->ymd, "\n"; # datetime object
warn size $row;

### skinnyはこうやってオブジェクトからハッシュにできる
### この状態だとmemcacheに保存できる
warn Dumper my $hashref = $row->get_columns;

$memcache->set( 'key' => $hashref );
warn Dumper $memcache->get('key');

cmpthese timethese 10_000, {
	'hash to row' => sub {
		my $row = $db->data2itr( 'users',[$hashref])->first;
	},
	'row from db' => sub {
		my $row = $db->single('users',{id=>1});
	},
	'make hash form memcache to row object' => sub {
        my $hash = $memcache->get( 'key' );
		my $row = $db->data2itr('users',[$hash])->first;
	},
};

キタナイデスネスイマセン

結果
:!perl bench/skinny_cache.pl
users at /home/okamura/p5/Shaq/bench/../extlib/Proj/DB.pm line 7.
1
idAF4AdWfD
nekokak
2010-02-04T22:09:55
2010-02-04
298 at bench/skinny_cache.pl line 61.
$VAR1 = {
          'created_at' => '2010-02-04 22:09:55',
          'updated_at' => '2010-02-04 22:09:55',
          'name' => 'nekokak',
          'rid' => 'idAF4AdWfD',
          'id' => 1
        };
$VAR1 = {
          'created_at' => '2010-02-04 22:09:55',
          'updated_at' => '2010-02-04 22:09:55',
          'name' => 'nekokak',
          'id' => 1,
          'rid' => 'idAF4AdWfD'
        };
Benchmark: timing 10000 iterations of hash to row, make hash form memcache to row object, row from db...
hash to row: 11 wallclock secs ( 1.88 usr +  8.14 sys = 10.02 CPU) @ 998.00/s (n=10000)
make hash form memcache to row object: 10 wallclock secs ( 2.08 usr +  4.44 sys =  6.52 CPU) @ 1533.74/s (n=10000)
row from db: 15 wallclock secs ( 0.17 usr + 15.03 sys = 15.20 CPU) @ 657.89/s (n=10000)
                                        Rate row from db hash to row make hash form memcache to row object
row from db                            658/s          --        -34%                                  -57%
hash to row                            998/s         52%          --                                  -35%
make hash form memcache to row object 1534/s        133%         54%                                    --

ふむー。予想と違う。どこかで何かを勘違いしているのかな、DBから取り出す処理が一番時間がかかるのは物理的にファイルアクセスするから磁気テープに向かってハードディスクのアームがえっちらほっちらしてるから、つーのはわかるんですが、メモリ上に保存してある(と僕は思っている)ハッシュからオブジェクトをつくるより、memcacheからデータ取り出したほうが高速に動いているのはなぜだろう?

結果

俺はまだまだ修行がたりない。
ベンチマークがなぜそうなるのかを考えます。

とりあえず明日もmemcache使ってキャッシュ処理頑張って書こう

ソースコードの置き場所
http://github.com/okamuuu/Shaq/blob/master/bench/skinny_cache.pl