はじめに

このあたりを見て,ポータブルな(サーバに置くだけで動作する?)モジュールについて,ちょこっとだけ勉強を始めました.

この先何回か,ここにあるモジュールを触ってみたメモなんかをエントリしてみようと思います.

今回は,Cache::FileCacheモジュールについてです.恥ずかしながら,これまでデータとかのキャッシュ処理を扱ったことがほとんどなかったので,そのあたりの勉強もかねて.

では以下.

基本

use Cache::FileCache;
 
my $cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 10,
});

キャッシュするには,setメソッドを呼びます.後で値を引き出すための「キー」と値自身を引数に渡します.

my $to_be_cached = 'hogehoge';
$cache->set('keyname', $to_be_cached);

この場合,Cache::FileCacheオブジェクト生成時に指定したdefault_expires_inパラメータの値が表す時間だけ保持されます.

保持する時間を変更したい場合は,第3引数にその値を指定すればOKです.

$cache->set('keyname', $to_be_cached, 60);

キャッシュした値を取得するには,先で指定した「キー」を引数に,getメソッドで呼び出します.保持期限を過ぎていたり,指定した「キー」に対応する値が存在しない場合にはundefが返ります.

my $cached = $cache->get('keyname');   # 保持期限内 ? 'hogehoge' : undef
my $undef  = $cache->get('hogehoge');  # undef

簡単ですね.

イメージ的には,「ファイルシステムレベルでのハッシュ」みたいな感じ,とでも言いましょうか.

とりあえず使ってみる

まずは,キャッシュをセットしたり取得したり,保持期限前後の様子を確認してみます.

sample1.plのソースコードは「おわりに」以降に掲載.)

% ./sample1.pl
 
# about 0 sec.
$VAR1 = {
          'bar' => 'huga',
          'baz' => 'piyo',
          'foo' => 'hoge'
        };
$VAR1 = {
          'c' => '3',
          'a' => '1',
          'b' => '2'
        };
 
# about 5 sec.
$VAR1 = {
          'bar' => 'huga',
          'baz' => 'piyo',
          'foo' => 'hoge'
        };
$VAR1 = {
          'c' => '3',
          'a' => '1',
          'b' => '2'
        };
 
# about 11 sec.
$VAR1 = undef;
$VAR1 = {
          'c' => '3',
          'a' => '1',
          'b' => '2'
        };
 
# about 15 sec.
$VAR1 = undef;
$VAR1 = {
          'c' => '3',
          'a' => '1',
          'b' => '2'
        };
 
# about 21 sec.
$VAR1 = undef;
$VAR1 = undef;
%

ディレクトリ構造とかファイルとか

どのように保存されているかを確認してみます.

ディレクトリ構造

sample2.plのソースコードは「おわりに」以降に掲載.)

% ./sample2.pl
 
# cache_root
 
/var/folders/sC/sCwCvkCpGzSDGTP6+Wood++++TI/-Tmp-/FileCache
 
# tree
 
/var/folders/sC/sCwCvkCpGzSDGTP6+Wood++++TI/-Tmp-/FileCache
`-- filecache_sample
    |-- 9
    |   `-- b
    |       `-- 6
    |           `-- 9b63072850833c63c5200f2c35b3edc4118f705e
    `-- a
        `-- 1
            `-- d
                `-- a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
7 directories, 2 files
 
 
# sha1
 
a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
9b63072850833c63c5200f2c35b3edc4118f705e
 
# changing cache_root
 
# cache_root
 
./tmp
 
# tree
 
./tmp
`-- filecache_sample
    |-- 9
    |   `-- b
    |       `-- 6
    |           `-- 9b63072850833c63c5200f2c35b3edc4118f705e
    `-- a
        `-- 1
            `-- d
                `-- a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
7 directories, 2 files
 
 
# sha1
 
a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
9b63072850833c63c5200f2c35b3edc4118f705e
%

「キャッシュルート」となるディレクトリの下に,オブジェクト生成時にnamespaceパラメータで指定した値(名前空間)のディレクトリができ,その下に,キーと値とのペアを表すファイルが入っています.名前空間ディレクトリ(?)と各ファイルとの間には,ファイル名にちなんだ名前のディレクトリが,数階層掘られています.

「キャッシュルート」ディレクトリは,オブジェクト生成時に指定することも可能です.

$cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 10,
    cache_root         => './tmp',
});

次に示すのは,キャッシュするディレクトリを先のように./tmpと指定した場合の,ls -lコマンドのbefore/afterです.

before:

% ll
total 24
-rw-r--r--@ 1 iwata  staff  673  2  5 01:20 sample.yml
-rwxr-xr-x  1 iwata  staff  681  2  5 02:56 sample1.pl
-rwxr-xr-x@ 1 iwata  staff  946  2  5 03:03 sample2.pl
%

after:

% ll
total 24
-rw-r--r--@ 1 iwata  staff  673  2  5 01:20 sample.yml
-rwxr-xr-x  1 iwata  staff  681  2  5 02:56 sample1.pl
-rwxr-xr-x@ 1 iwata  staff  942  2  5 03:07 sample2.pl
drwxrwxrwx  3 iwata  staff  102  2  5 03:06 tmp
%

ファイル名と中身

先の実行結果より,キャッシュファイルの名前は,「キー名のSHA1ハッシュ」ということがわかるかと思います.

また,キャッシュファイルを覗いてみると,次のような感じのものが確認できます.バイナリファイルですが,それっぽい部分もありますね.

% less ./tmp/filecache_sample/a/1/d/a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
^E^G^B^@^@^@^B
^Dvar1^D^Q^MCache::Object^C^@^@^@^F^E^@^@^@^E_Size      Kk^MS^@^@^@^K_Expires_At^E^@^@^@^D_Key  Kk^M^W^@^@^@^K_Created_At^D^C^@^@^@^C
^Dhuga^@^@^@^Cbar
^Dpiyo^@^@^@^Cbaz
^Dhoge^@^@^@^Cfoo^@^@^@^E_Data  Kk^M^W^@^@^@^L_Accessed_At

cache_depthオプションとdirectory_umaskオプション

Cache::FileCacheオブジェクトを生成する際のその他のオプションについてもざっと確認してみます.

sample3.plのソースコードは「おわりに」以降に掲載.)

% ./sample3.pl
 
# tree -p
 
./tmp
`-- [drwxrwxrwx]  filecache_sample
    |-- [drwxrwxrwx]  9
    |   `-- [drwxrwxrwx]  b
    |       `-- [drwxrwxrwx]  6
    |           `-- [-rw-r--r--]  9b63072850833c63c5200f2c35b3edc4118f705e
    `-- [drwxrwxrwx]  a
        `-- [drwxrwxrwx]  1
            `-- [drwxrwxrwx]  d
                `-- [-rw-r--r--]  a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
7 directories, 2 files
 
 
# changing cache_depth and directory_umask
 
# tree -p
 
./tmp
`-- [drwxrwxrwx]  filecache_sample
    |-- [drwxrwxrwx]  9
    |   `-- [drwxrwxrwx]  b
    |       `-- [drwxrwxrwx]  6
    |           |-- [drwx------]  3
    |           |   `-- [drwx------]  0
    |           |       `-- [drwx------]  7
    |           |           `-- [-rw-r--r--]  9b63072850833c63c5200f2c35b3edc4118f705e
    |           `-- [-rw-r--r--]  9b63072850833c63c5200f2c35b3edc4118f705e
    `-- [drwxrwxrwx]  a
        `-- [drwxrwxrwx]  1
            `-- [drwxrwxrwx]  d
                |-- [-rw-r--r--]  a1dd4114c98069523cdf1d90ff3a43222670b8c8
                `-- [drwx------]  d
                    `-- [drwx------]  4
                        `-- [drwx------]  1
                            `-- [-rw-r--r--]  a1dd4114c98069523cdf1d90ff3a43222670b8c8
 
13 directories, 4 files
 
%

簡単なベンチマーク

次の4種類の処理を,それぞれ10000回実行して比較してみました.

sample4.plのソースコード,YAMLファイルsample.ymlは「おわりに」以降に掲載.)

b1
YAMLモジュールを使ってYAMLファイルを読み込み,ハッシュリファレンスに変換する
b2
YAML::Syckモジュールを使ってYAMLファイルを読み込み,ハッシュリファレンスに変換する
b3
ハッシュリファレンスのキャッシュがない場合のみ,YAMLモジュールを使ってYAMLファイルを読み込んでハッシュリファレンスに変換,キャッシュする
b4
ハッシュリファレンスのキャッシュがない場合のみ,YAML::Syckモジュールを使ってYAMLファイルを読み込んでハッシュリファレンスに変換,キャッシュする

実行したMacBookのスペックについては,次あたりを参照してください.

実行結果は次のようになりました.

% ./sample4.pl
Benchmark: timing 10000 iterations of b1, b2, b3, b4...
        b1: 75 wallclock secs (72.25 usr +  0.70 sys = 72.95 CPU) @ 137.08/s (n=10000)
        b2:  3 wallclock secs ( 2.16 usr +  0.28 sys =  2.44 CPU) @ 4098.36/s (n=10000)
        b3:  3 wallclock secs ( 2.62 usr +  0.64 sys =  3.26 CPU) @ 3067.48/s (n=10000)
        b4:  4 wallclock secs ( 2.59 usr +  0.64 sys =  3.23 CPU) @ 3095.98/s (n=10000)
     Rate    b1    b3    b4    b2
b1  137/s    --  -96%  -96%  -97%
b3 3067/s 2138%    --   -1%  -25%
b4 3096/s 2159%    1%    --  -24%
b2 4098/s 2890%   34%   32%    --
%

このことから,次のことがわかるんじゃないかな,と.

  • YAMLモジュールを使って毎回読み込むよりは,Cache::FileCacheによってキャッシュした方が効率がよい
  • YAML::Syckモジュールが使える場合,Cache::FileCacheによるキャッシュは逆効果っぽい

おわりに

以上,Cache::FileCacheモジュールを使ってみたメモでした.Cache::Cacheモジュールのサブクラスにあたるため,そのドキュメントもざっと読んだところ,他のサブクラス(例えばCache::MemoryCacheとか)も同じようなメソッドで操作できるようですね.今後試してみることにします.

ソースコード:sample1.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
 
use YAML::Syck;
use Cache::FileCache;
use Data::Dumper;
 
sub p { print "\n", @_, "\n"; }
sub d { print Dumper @_; }
 
my $cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 10,
});
 
my $var1 = {qw/foo hoge bar huga baz piyo/};
my $var2 = {qw/a 1 b 2 c 3/};
 
$cache->set('var1', $var1);
$cache->set('var2', $var2, 20);
 
sub cache_test {
    my @plan = @_;
    my $sec = 0;
    for my $s (@plan) {
        $sec += $s;
        sleep($s);
        p "# about $sec sec.";
        d $cache->get('var1');
        d $cache->get('var2');
    }
}
 
cache_test 0, 5, 6, 4, 6;
 
__END__

sample2.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
 
use YAML::Syck;
use Cache::FileCache;
use Data::Dumper;
 
sub p { print "\n", @_, "\n"; }
sub d { print Dumper @_; }
 
my $cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 60,
});
 
my $var1 = {qw/foo hoge bar huga baz piyo/};
my $var2 = {qw/a 1 b 2 c 3/};
 
$cache->set('var1', $var1);
$cache->set('var2', $var2);
 
my $cache_root = $cache->get_cache_root();
p '# cache_root';
p $cache_root;
 
p '# tree';
p `tree $cache_root`;
 
p '# sha1';
p sha1_hex 'var1';
p sha1_hex 'var2';
 
#
# cache_rootを変更してみる
#
p '# changing cache_root';
$cache->set_cache_root('./tmp');
 
$cache->set('var1', $var1);
$cache->set('var2', $var2);
 
$cache_root = $cache->get_cache_root();
p '# cache_root';
p $cache_root;
 
p '# tree';
p `tree $cache_root`;
 
p '# sha1';
p sha1_hex 'var1';
p sha1_hex 'var2';
 
__END__

sample3.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
 
use YAML::Syck;
use Cache::FileCache;
use Digest::SHA1 qw/sha1_hex/;
use Data::Dumper;
 
sub p { print "\n", @_, "\n"; }
sub d { print Dumper @_; }
 
my $cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 60,
    cache_root         => './tmp',
});
 
my $var1 = {qw/foo hoge bar huga baz piyo/};
my $var2 = {qw/a 1 b 2 c 3/};
 
$cache->set('var1', $var1);
$cache->set('var2', $var2);
 
my $cache_root = $cache->get_cache_root();
p '# tree -p';
p `tree -p $cache_root`;
 
#
# cache_depth,directory_umaskを変更してみる
#
p '# changing cache_depth and directory_umask';
$cache->set_cache_depth(6);
$cache->set_directory_umask(077);
 
$cache->set('var1', $var1);
$cache->set('var2', $var2);
 
p '# tree -p';
p `tree -p $cache_root`;
 
__END__

sample4.pl

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
 
use YAML;
use YAML::Syck;
use Cache::FileCache;
use Benchmark qw/:all/;
use Data::Dumper;
 
sub p { print "\n", @_, "\n"; }
sub d { print Dumper @_; }
 
my $cache = Cache::FileCache->new({
    namespace          => 'filecache_sample',
    default_expires_in => 600,
    cache_root         => './tmp',
});
 
my $yamlfile = './sample.yml';
 
 
my ($b1, $b2, $b3, $b4);
#
# b1: YAML
#
$b1 = sub {
    my $hash = YAML::LoadFile($yamlfile);
    return $hash;
};
#
# b2: YAML::Syck
#
$b2 = sub {
    my $hash = YAML::Syck::LoadFile($yamlfile);
    return $hash;
};
#
# b3: Cache::FileCache || YAML
#
$b3 = sub {
    my $key = 'sample4_b3';
    my $hash = $cache->get($key);
    unless (defined $hash) {
        $hash = YAML::LoadFile($yamlfile);
        $cache->set($key, $hash);
    }
    return $hash;
};
#
# b4: Cache::FileCache || YAML::Syck
#
$b4 = sub {
    my $key = 'sample4_b4';
    my $hash = $cache->get($key);
    unless (defined $hash) {
        $hash = YAML::Syck::LoadFile($yamlfile);
        $cache->set($key, $hash);
    }
    return $hash;
};
 
 
my $results = timethese(
    10_000,
    {
        b1 => $b1,
        b2 => $b2,
        b3 => $b3,
        b4 => $b4,
    },
);
cmpthese($results);
 
__END__

sample.yml

---
LOCATION:
  PROTOCOL:  http
  DOMAIN:    denske.local
  SUBDOMAIN: www.
  PATH:      /path
 
PAGE:
  COMMON_CSS: [_base, _layout, _additional]
  COMMON_JS:  [_base, _additional]
 
DSI:
  DBI:  1
  YAML: 1
 
DB:
  TYPE:   mysql
  NAME:   sample
  HOST:   localhost
  USER:   sample
  PASSWD: samplepasswd
  TABLE_PREFIX: sample_
 
CACHE:
  ENABLED:   1
  NAMESPACE: samplesample
  EXPIRES:   600
 
COOKIE:
  EXPIRES: +14d
  DOMAIN:  
  PATH:    /
  SECURE:  0
 
MAIL:
  SERVER_SMTP: localhost:587
  ADDRESS:
    FROM: noreply@example.com
    CC:
  SIGNATURE: |
    --
    your email signature.
 
SESSION_KEY_NAME: sample_session
 
CONTENT_TYPE_DEFAULT: text/html; charset=utf-8

こちらもあわせてどうぞ

コメントをどうぞ