PerlからMemcachedを使う(Cache::Memcached::Fast)

Memcached

  • ものすごくざっくりいうと分散メモリキャッシュサーバーというもので、メモリ上にデータを保存出来て取り出せるというものです。ファイルに書かれた設定やDBのデータをメモリ上にのせておくことで高速にデータの取得を行うことが出来るようになります。
  • 詳しくはgihyo.jpにあるkazeburoさんの連載を見ていただけるとよくわかると思います。
  • 以前に書いたGearmanと同様にあくまでメモリ上にデータは保存されるので、使いたい時に立ち上げて終わったら落とすとデータは消えます。
  • 何らかの理由でmemcachedが落ちてしまうとデータが全部消えてしまうので、実システムでは消えてもいいデータを選ぶことやDBでの永続化などを考慮する必要があります。ですが逆に個人で試してみるには、落とせばすっきりデータが消えるので気軽に試すことが出来ます。

インストール

  • Mac環境で試しています。
  • 自分の環境では、いつの間にインストールしていたのか既に/usr/bin以下にインストールされていたのですが、バージョンが1.2.8と古かったため、brewでインストールしてみました。
    • brewのインストールについては省略。
% brew install memcached
  • これで/usr/local/bin以下にインストールされます。

起動

  • 今回は試すだけなのでフォアグラウンドで起動します。
% memcached
  • 起動するとデフォルトでは11211ポートで待ち受けますのでそこに接続していくことになります。
  • vvvオプションを付けて起動すると、色んな情報が出力されるので面白いかもしれません。
% memcached -vvv

Cache::Memcached::Fastをインストール

  • PerlからMemcachedを使う時にはいくつかモジュールの選択肢があるのですが、早くて一番よく使われていそうなので「Cache::Memcached::Fast」を使うことにします。
    • 手順は書くまでもないのですが、こんな感じで。
% cpanm Cache::Memcached::Fast

値をセットして取り出す

  • Cache::Memcached::FastのSYNOPSISを見るとnewするところで大量のオプションが指定されていますが、ここではとりあえず最低限のオプションだけで動かしてみます。
#!perl
use strict;
use warnings;
use Cache::Memcached::Fast;

my $memd = Cache::Memcached::Fast->new({
    servers => [ { address => 'localhost:11211' }],
}); 

# 値を追加 key => value
$memd->add('id' => 'koba04');
$memd->add('age' => 28);

# 値を取得
my $id = $memd->get('id');
my $age = $memd->get('age');

# 値を設定
$memd->set('age' => 57);

# 値を変更
$memd->replace('age' => 28);
  • ものすごい簡単にやるとこんな感じでしょうか。「add」と「set」と「replace」辺りが似ていてややこしいかなと思うので、簡単に違いを書いてみます。
    • add
      • keyがセットされていない場合だけ値をセット出来る。keyが既にある場合は更新されない。
    • replace
      • keyがセットされている場合だけ値をセット出来る。keyがない場合には追加されない。
    • set
      • keyがセットされている場合でもされていない場合でも値をセットする。keyがあろうがなかろうが値をセットしたい場合はこれを使えばよさそう。

ハッシュとか配列とかも入れられます。

  • スカラーだけでなく、配列とかハッシュも入れられるので便利ですね。
# array
$memd->set('color', [qw/red blue green/]);
my $list = $memd->get('color');
print $list->[0];         #=> red

# hash
$memd->set('song', { radiohead => 'creep', travis => 'turn' } );
my $hash = $memd->get('song');
print $hash->{travis};        #=> turn
  • ハッシュの中にさらにハッシュを入れたり配列を入れたりももちろん出来ます。

名前空間を作る

  • このままだとidというkeyを持った値はmemcachedで1つしか持てないことになり不便です。そういう時は、namespaceを指定することで名前空間をつくることが出来ます。
my $memd_user = Cache::Memcached::Fast->new({
    servers       => [{address => 'localhost:11211'}],
    namespace => 'user:',
});
my $memd_item = Cache::Memcached::Fast->new({
    servers       => [{address => 'localhost:11211'}],
    namespace => 'item:',
});
$memd_user->set(1 => 'koba04');
$memd_item->set(1 => 'android');
print $memd_user->get(1) . ':' . $memd_item->get(1);   # => koba04:android

有効期限をセットする

  • setやaddやreplaceなどの値をセットする関数に秒単位で指定することが出来ます。
# 1日有効なキャッシュ
$memd->set('today', 'fine', 60 * 60 * 24);

まとめてセット、取得したい

  • 複数の値を一度にまとめてセットして欲しい場合は、addやsetやreplaceなどに〜_multiというメソッドがあるのでそれを使うことが出来ます。
$memd->set_multi(['key1', 'value1'], ['key2', 'value2', 60 * 24 * 24];
print $memd->get('key1') . ':' . $memd->get('key2');   # => value1:value2
  • 逆にまとめて取得したい場合は、get_multiが使えます。
$memd->set_multi(['a' => 'A'], ['b' => 'B']);
my $res = $memd->get_multi('a','b');
print $res->{a} . ':' . $res->{b};          # => A:B
prepend, append
  • すでにセットされている文字列の先頭や末尾に値を追加することが出来ます。
$memd->set('id', 'ba');
$memd->prepend('id', 'ko');
$memd->append('id', '04');
print $memd->get('id');    # koba04
incr(decr)
  • idのようなインクリメントしていく値を作りたい場合に便利です。
$memd->set('id', 0);
print $memd->incr; #  => 1
# 増やす値を指定
print $memd->incr('id', 10);  # => 10
  • 最初に書いたとおり、もし落ちちゃうとインクリメントの情報も消えてしまうので、実際に使うには何らかの方法でどこまで進んだのかを復元できる仕組みが必要になるかと思います。
cas, gets
  • 値を取得してその値に基づいて再度セットする際、取得した値が他のクライアントから変更されていないことをチェックしたい場合に使用します。getsで値を取得してcasでセットします。
  • casというのはどうやらCheck And Set か Compare And Swapのようです。
$memd->set('money', 1000);
my $res = $memd->gets('money');
# 結果は配列のリファレンスで1番目にcas値、2番目に値が入っている
my $cas = $res->[0];
my $money = $res->[1];
if ( !$memd->cas('money', $cas, $money + 200) ) {
    die '誰がか更新している!';
}
print $memd->get('money'); # => 1200

# casの値の変化を確認してみる。
my $res_after = $memd->gets('money');
my $res_after2 = $memd->gets('money');
$memd->set('money', 2000);     # 値を更新
my $res_after3 = $memd->gets('money');

print "before => $res->[0], after => $res_after->[0], after2 => $res_after2->[0], after3 => $res_after3->[0]";
# before => 202, after => 203, after2 => 203, after3 => 204
  • casは値が更新されたタイミングで更新されることがわかります。
delete, flush_all
  • 値を削除する場合はdelete、全部消したい場合はflush_allを使います。
$memd->delete($key);
$memd->flush_all;

newオプション

  • 先程も書いたようにnewのオプションはたくさんあります。簡単な例だと上記のようにserversとnamespaceだけでOKだと思います。実稼動させたいときはちゃんとPOD読んで設定するのがいいと思います。
  • 日本語を扱い時は下記のようにutf8オプションを付けておくと、decodeされた文字列をセットするときにはencodeされた状態でセットしてくれて、memcachedから値を取り出すときはdecodeされた文字列として取り出してくれるので便利です。
my $memd = Cache::Memcached::Fast->new({
    servers       => [{address => 'localhost:11211'}],
    namespace => 'user:',
    utf8            => 1,
});

というわけで

  • ここに書いてあるようなことはググったりPOD読めばすぐにわかるわけですが自分で試して覚えたかったので書いてみました。
  • データを保存しておく方法としてはファイルに書き出したりDBに入れたりといった方法がありますが、個人的にどちらも事前の準備や後始末、値のセット、取得がちょっと面倒かなと思ったりすることがあります。
  • スカラー、ハッシュ、配列程度のデータをちょっと保存しておきたい場合にmemcachedを使うと、上記のように簡単に扱うことが出来て気軽でいいかなと思います。終わればmemcachedを落とせばいいだけですし。
  • Gearmanと組み合わせてみると気軽にWeb APIを使ったようなウェブアプリも作れそうですね。気が向いたら例でも書いてみます。