Bulknews::Subtech RSSフィード

2006/12/16 (土)

Why storing session on memcached? 19:14  Why storing session on memcached? - Bulknews::Subtech を含むブックマーク はてなブックマーク -  Why storing session on memcached? - Bulknews::Subtech

http://d.hatena.ne.jp/tokuhirom/20061216/1166231736

memcached の開発元でもある Six Apart ですが、Vox/LJ ではセッションを memcached にいれてはいません。理由は簡単で memcached は比較的小さなデータを格納し、アプリケーションサーバ群で共有するのに適した設計になっているからで、memcached のデータがとべばセッションが消えるというのもアプリケーションによっては致命的になりえるから。

memcached に限らずキャッシュと名のつくものは、あってもなくても動くような役割でないといけないと思う。

http://www.jsw4.net/info/listserv_archives/mod_perl/04-wk37/msg00055.html で Perrin Harkins が言っているんだけど、

I wouldn't recommend using memcached for storing sessions. Memcached is explicitly unreliable storage. It is built with the premise that you won't put anything into it that you care about losing. If it gets full, it will simply drop data from storage. The mechanism for failover across multiple machines also counts on being able to lose all the data from one machine without it being a big deal.


Memcached would be better suited to acting as a write-through cache for session data that you store in a database.

データベースに書き込む際に memcached に同時に書き込み、読み込み時には memcached -> データベースと fallback する (= Write-through Cache)、というのであれば、いい使い方だとはおもいますが、memcached をメインのストレージにするというアイデアには懐疑的です。

以下は個別項目に対して。

> MySQL でセッションの管理をすると、複数サーバにふりわける仕組みがない(つくればいーけど)

Data::ObjectDriver のパーティション機能でユーザごとにセッションを割り振っています。

> MySQL 使ってるとテーブルロックがかかりまくってサイト全体が刺さる危険性がある

InnoDB を使いましょう。

> MySQL 使ってると、セッションデータを定期的に消してやらなきゃいけないけど DELETE FROM sessions WHERE timestamp >= '2006-12-01 00:00:00'; とかはすごく重かったりして、ここでまた刺さる

定期的に消すのではなくてセッションの復元時に expires をチェックするようにしている。。(未ログインユーザとかの不要セッションでごみがたまる問題はあるかな)

> memcached なら、サーバいくつか置いとけば、フェイルオーバになる

書き込みのときにフェイルオーバすることはできるけど、すでに書いてしまったデータを持ってるノードが落ちても、とりだせはしない。

http://www.danga.com/memcached/ にあるように、memcached はキーのハッシュ値をとって Memcached サーバの台数で modulo をとって割り振るだけ。増やしたから書き込みも Spread するというわけではない。つまり memcached のサーバを増やしてアプリを再起動したら、まず cache warming をしないとキャッシュヒット率が大幅に下がる可能性がある。

> mysql でアプリケーション用の DB にセッションテーブルいれちゃうと、無駄にデカいテーブルがレプられてウザい

テーブル名でレプリケーションを抑制する設定があったような。まあせっかくデータベースにいれるならレプリケーションさせないと意味ないかとは思うけど。


というわけで、「データベース自体をパーティショニングしてスケールさせる必要・需要がなく、いつユーザのセッションがとんでも問題なく、履歴をとっておく必要もないアプリケーションで、メモリを比較的大量につんだマシンをたくさん用意させられる環境」で memcached を使うのはいいと思います。また、「Webのセッションには永続を期待するデータをいれるべきでない=いつ消えても問題ないデータをいれるべき」という設計方針もあるだろうし、それ自体は個人的によいポリシーだともおもうので、それがうまく合致するなら問題がないともいえますね。ただ memcached サーバを追加したらセッションが消えてログインしなおし、というのはやはりなんとなく気持ちがよくない。

データベース(MySQL)を使用していてパフォーマンスに問題が、というのであれば Write-thru cache を実装してみると面白いかも。ちなみに Data::ObjectDriver の Cache はデフォルトで Write-through するようになってます。

2006/12/05 (火)

MobileAgent 07:34  MobileAgent - Bulknews::Subtech を含むブックマーク はてなブックマーク -  MobileAgent - Bulknews::Subtech

404 Blog Not Found:perl - 勝手に添削 - 条件分岐

よりおしゃれな方法として、HTTP::MobileAgentのサブクラスを作るという方法もあります。

おしゃれでもないし、そもそもこのコードでは動かない。

ここでnew()を以上のように定義しているのは、HTTP::MobileAgentのnew()の設計が少し特殊で、そのままでは継承可能となるように設計されていないからで

それには理由があって、USER_AGENT の文字列をみて適切なサブクラスに bless する Factory になっているから。そのインスタンスをもってきて自前のクラスに bless したら、動くわけがない。

use strict;
use warnings;

use HTTP::MobileAgent;
use Test::More 'no_plan';

{
    my $agent = HTTP::MobileAgent->new("DoCoMo/2.0 N2001");
    is $agent->is_docomo, 1;
    is $agent->is_foma, 1;
}

{
    package MyMobileAgent;
    use base 'HTTP::MobileAgent';
    sub new {
        my $class = shift;
        my $self = HTTP::MobileAgent->new(@_);
        bless $self, __PACKAGE__;
    }

    sub is_xhtml {
        my $self = shift;
        return 1 if $self->is_docomo and $self->is_foma;
        return 1 if $self->is_ezweb  and $self->is_win;
        if ($self->is_softbank){
            return 1 if $self->is_type_w or $self->is_type_3gc;
        }
        return 0;
    }
}

{
    my $agent = MyMobileAgent->new("DoCoMo/2.0 N2001");
    is $agent->is_docomo, 1;
    is $agent->is_xhtml, 1;
}

new() のところで HTTP::MobileAgent にきちんと @_ を渡すようにしておいた。これでも実行すると

% perl ~/tmp/mobileagent.t
ok 1
ok 2
not ok 3
#   Failed test at /home/miyagawa/tmp/mobileagent.t line 34.
#          got: '0'
#     expected: '1'
not ok 4
#   Failed test at /home/miyagawa/tmp/mobileagent.t line 35.
#          got: '0'
#     expected: '1'
1..4
# Looks like you failed 2 tests of 4.

となる。

継承をつかってきちんと動かしたければ、MyMobileAgent のサブクラスを動的につくって @ISA に DoCoMo を差し込むようにハックしなければダメ。無理やりかけばこうなるか。

package MyMobileAgent;

my $i;
sub new {
    my $class = shift;
    my $self = HTTP::MobileAgent->new(@_);
    my $pkg = __PACKAGE__ . "::". $i++;
    no strict 'refs';
    @{$pkg."::ISA"} = (ref $self, __PACKAGE__);
    bless $self, $pkg;
}

これで .t はとおるようになった。

そもそも後から機能追加するのに継承を使うというのはイケていない。すべての呼び出しを HTTP::MobileAgent から MyMobileAgent に変えなければならないし、is_xhtml 以外の機能をつけるときに拡張性がない。こういうのはメソッド追加するプラグインでやるのが定石。

package HTTP::MobileAgent::Plugin::XHTML;
use strict;

sub import {
    my $class = shift;
    *HTTP::MobileAgent::is_xhtml = sub { ... }
}

1;

これで、 use HTTP::MobileAgent::Plugin::XHTML; すれば大元のクラスにメソッドが追加できる。