Plack::Session::Store::MySQL
Plack::Session::Store::DBI というのがあって、最初とりあえずそれを使っていたのだけれど、もうちょいクエリの数を減らしておきたいと思い、以下のようなものを書いた。実際のところセッションのストレージに MySQL を使うべきかはよくわからないのですが…
変更点として
- セッションの内容が不変だったら UPDATE を発行しない
- ただしシリアライザがハッシュに関しても常に同じシリアライズ結果を返す必要がある (Data::MessagePack や JSON の場合 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
};