Hatena::Groupsubtech

冬通りに消え行く制服ガールは✖夢物語にリアルを求めない。

 | 

Sep 27, 2011 (Tue)

Plack::Session::Store::MySQL 21:01 はてなブックマーク - Plack::Session::Store::MySQL - 冬通りに消え行く制服ガールは✖夢物語にリアルを求めない。

Plack::Session::Store::DBI というのがあって、最初とりあえずそれを使っていたのだけれど、もうちょいクエリの数を減らしておきたいと思い、以下のようなものを書いた。実際のところセッションのストレージに MySQL を使うべきかはよくわからないのですが…

変更点として

  • セッションの内容が不変だったら UPDATE を発行しない
    • ただしシリアライザがハッシュに関しても常に同じシリアライズ結果を返す必要がある (Data::MessagePackJSON の場合 canonical オプションがあるので有効にする)
  • セッションキーの存在を確認せず、ON DUPLICATE KEY UPDATE する (アトミックになる + クエリの数が減る)
package Plack::Session::Store::MySQL;
use utf8;
use strict;
use warnings;
use parent qw(Plack::Session::Store::DBI);

sub fetch {
    my ($self, $session_id) = @_;
    my $table_name = $self->{table_name};

    my $sth = $self->_dbh->prepare_cached("SELECT session_data FROM $table_name WHERE id = ?");
    $sth->execute( $session_id );

    my ($data) = $sth->fetchrow_array;

    $self->{$session_id}->{_prev_data} = $data;

    $sth->finish;

    $data ? $self->deserializer->( $data ) : ();
}

sub store {
    my ($self, $session_id, $session) = @_;
    my $table_name = $self->{table_name};

    my $data = $self->serializer->($session);

    if ($self->{$session_id}->{_prev_data} ne $data) {
        my $sth = $self->_dbh->prepare_cached("INSERT INTO $table_name SET id = ?, session_data = ? ON DUPLICATE KEY UPDATE session_data=VALUES(session_data)");
        $sth->execute($session_id, $data);
    }
}

1;
__END__

Sample schema:

    CREATE TABLE session (
        `id`           CHAR(72) NOT NULL,
        `session_data` BLOB,
        `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB;

use Plack::Builder;
use Data::MessagePack;
use Plack::Session::State::Cookie;
use Plack::Session::Store::MySQL;

my $MessagePack = Data::MessagePack->new();
$MessagePack->canonical;

builder {
    enable
        "Plack::Middleware::Session",
        state => Plack::Session::State::Cookie->new(),
        store => Plack::Session::Store::MySQL->new(
            get_dbh      => sub { DBI->connect_cached(config->param('dsn_session'), 'foo', 'bar') },
            table_name   => 'session',
            serializer   => sub { $MessagePack->pack(+shift) },
            deserializer => sub { eval { $MessagePack->unpack(+shift) } || +{} },
        );
        
        $app
};
 | 

スポンサード リンク

書いてる人

cho45 (佐藤広央) (www.lowreal.net)

Perl, JavaScript, Ruby, HTML, CSS, Web etc


スポンサード リンク