読者です 読者をやめる 読者になる 読者になる

memcached の ネームスペース

職場の自転車部サイクリング大会が雨で中止になって暇なので、兄がPHPで書いたmemacheクラスをperlリファクタリングした。
memcachedにネームスペース機能を付けると、まとめてデータ削除できたりして便利だよねーって話。

memcachedAPCは、ネームスペースのような機能をサポートしていない。あるオブジェクトをキャッシュしようと思えば、キーに紐付けて保存することになる訳だが、このままだと、例えば、あるテーブルに対するクエリの結果をキャッシュしようとした場合に、プライマリキーを利用してキーとし、クエリの結果オブジェクトをキャッシュに保存していく、といった使い方になる。ここで問題となるのが、複数のテーブルの行をキャッシュしていった場合に、あるテーブルに対応したキャッシュだけを削除したい場合にどうするのか?という点。キーを指定して保存/取得/削除する事しか出来なければ、複数のアプリケーションから同じmemcachedを使っていた場合等にも、キャッシュの特定の部分を纏めて削除する、といった事が出来ず使い勝手が悪い。

この問題を解決する為に、以下の方法で擬似的にネームスペースを実装してやることが出来る。

http://d.hatena.ne.jp/e-kuroda/20080524

そのままだとあんまり面白くないので、{set,get}_multi相当のメソッドを足した。
引数のvalidationははしょったけど、そこちゃんとやれば割と使えるかも。
テストしして気がついたんだけど、C::M::Fastのflush_allが効かないんですが...なんですかこれ?

package MyMemcache;

use strict;
use warnings;
use feature qw/:5.10/;
use base qw/ Class::Data::Inheritable Class::Accessor::Fast /;
use Carp qw/ croak /;
use Digest::MD5 qw/ md5_hex /;
use Storable qw/ nfreeze /;
use Cache::Memcached::Fast;

__PACKAGE__->mk_classdata( opt => {
    servers      => [ qw/ 127.0.0.1:11211 / ],
    cache_expire => 0,
});

__PACKAGE__->mk_accessors( qw/ memd / );

sub new {
    my ($class, %args) = (shift, (ref $_[0] eq 'HASH')? %{$_[0]} : @_);

    bless {
        memd => Cache::Memcached::Fast->new({
            servers => $class->opt->{servers},
            %args,
        }),
    }, $class;
}

sub save_cache {
    my ($self, $namespace, $key, $value, $expire) = @_;
    my $class = ref $self;

    return $self->memd->set(
        $self->_create_cachekey($namespace, $key),
        $value,
        $expire // $class->opt->{cache_expire},
    );
}

sub save_cache_multi {
    my ($self, $namespace, @items) = @_;
    my $class = ref $self;

    return $self->memd->set_multi(
        map [
            $self->_create_cachekey($namespace, $_->[0]),
            $_->[1],
            $_->[2] // $class->opt->{cache_expire},
        ], @items
    );
}

sub load_cache {
    my ($self, $namespace, $key) = @_;

    return $self->memd->get(
        $self->_create_cachekey($namespace, $key),
    );
}

sub load_cache_multi {
    my ($self, $namespace, @keys) = @_;

    return $self->memd->get_multi(
        map { $self->_create_cachekey($namespace, $_) } @keys
    );
}

sub delete_cache {
    my ($self, $namespace, $key) = @_;

    if (! defined $key) {
        return $self->memd->incr($namespace, 1);
    } else {
        return $self->memd->delete(
            $self->_create_cachekey($namespace, $key)
        );
    }
}

sub flush_cache {
    shift->memd->flush_all;
}

# private //////////////////
sub _create_cachekey {
    my ($self, $namespace, $key) = @_;

    my $namespace_key = $self->memd->get($namespace) // do {
        $self->memd->set($namespace, 0);
        0;
    };

    $key = nfreeze($key) if ref $key;
    return md5_hex(join ":", $namespace, $namespace_key, $key);
}




package main;

my $m = MyMemcache->new();

$m->save_cache('hogeNmaespace', 'fooKey', 'foo');
$m->save_cache('hogeNmaespace', 'barKey', 'bar', 1);
$m->save_cache('fugaNmaespace', 'fooKey', 'foofoo');
sleep 1;
say $m->load_cache('hogeNmaespace', 'fooKey'); # foo
say $m->load_cache('hogeNmaespace', 'barKey'); # undef
say $m->load_cache('fugaNmaespace', 'fooKey'); # foofoo

$m->delete_cache('hogeNmaespace');
say $m->load_cache('hogeNmaespace', 'fooKey'); # undef
say $m->load_cache('hogeNmaespace', 'barKey'); # undef
say $m->load_cache('fugaNmaespace', 'fooKey'); # foofoo

$m->delete_cache('fugaNmaespace');
say $m->load_cache('fugaNmaespace', 'fooKey'); # undef


$m->save_cache_multi('hogeNmaespace', ['fooKey', 'foo'], ['barKey', 'bar']);
say join ',', values %{ $m->load_cache_multi('hogeNmaespace', qw/ fooKey barKey /) };
                                               # foo, bar
$m->save_cache('pet', {lama => 'neko'}, 'x');
say $m->load_cache('pet', {lama => 'neko'});   # x