前のエントリーで書いた Thundering Herd 問題への対策案 で、重いクエリを排他制御すればいいのではないかというご意見も頂いたので、それをmemcachedで実現するようなモジュールを書いてみた。

下のモジュールではmemcachedのaddを使って制御します。addが成功したときだけ渡されたコールバックを実行し、ロックを得ることができない場合はaddに失敗するので、その場合はsleepして処理をやりなおす。他の排他制御するモジュールと違い、キャッシュ専用なので、排他制御の前にキャッシュにgetを行い、sleep中に既にキャッシュができていないかを確認するようになっている。

コードはなんの確認もしてないのであしからず

package Cache::ExclusiveControl;

use Try::Tiny;
use Time::HiRes;
use Class::Accessor::Lite (
    rw  => [ qw(cache poll_time max_wait parallel) ],
);

sub new {
    my $class = shift;
    my %args = (
        poll_time => 0.1,
        max_wait => 10,
        parallel => 1,
        @_;
    );
    bless \%args, $class;
}

sub get_or_set {
    my ($self, $key, $cb, $expires ) = @_;

    my $value;
    my $loop = 0;

    while  ( 1 ) {
        my $lockkey = $key . "::lock::" . int(rand($self->parallel));
        $value = $memcached->get($key);
        last if $value;

        my $locked = $memcached->add($lockkey, 1, $self->max_wait );
        if ( $locked ) {
            try {
                $value = $cb->();
                $memcached->set( $key, $value, $expires );
            }
            catch {
                die $_;
            }
            finally {
                $memcached->delete( $lockkey );
            };
        }

        $loop++;     
        die "timeout" if $loop >= $self->max_wait / $self->poll_time;
        Time::HiRes::sleep( $self->poll_time );
    }

    return $value;
}

1;

package main;

my $c_ec = Cache::ExclusiveControl->new(
    max_wait => 10, #最大10秒待つ。10秒超えたらdie
    poll_time => 0.05, #lockを確認する間隔
    parallel => 1, #callbackが並行動作できる数
    cache => Cache::Memcached::Fast->new(...)
);

# get_or_set( key, callback, expire );
my $value = $c_ec->get_or_set(
    'cache_may_thunder',
    sub {
        my $sth = $dbh->prepare("selet super heavy sql");
        $sth->execute(..);
        my $row = $sth->fetchrow_hashref;
        return $row;
    },
    3600, #expirer
);

おそらく、addをサポートするキャッシュシステム全てで使えるはず。あと、Cache::Memcached::Semaphore というモジュールはあるのだが、これは排他制御の部分しか提供してくれない。

block動作をするような排他制御システムを使う場合は、lockを獲得した後すぐに重いクエリを動かすのではなく、まずキャッシュへgetを行うのが必須だと思われます。

このブログ記事について

このページは、Masahiro Naganoが2010年12月19日 00:34に書いたブログ記事です。

ひとつ前のブログ記事は「memcachedにおけるキャッシュシステムの Thundering Herd 問題への対策案」です。

次のブログ記事は「Log::Minimalで自動シリアライズサポートしました」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 4.27-ja