hideden.hatenablog.com

はてなぶろぐー。URLなげー。

repcachedのメモ。

ある程度でかいサービスでDailyCount的な事をやらなきゃいけなくなって、DBに突っ込むとログのお掃除や負荷が大変だからmemcachedでやりたいけど、落ちたらウザいしなー・・・って考えてたらrepcachedを思い出した。YAPC2008でもmixiのkazeburoさんが紹介してたみたい。まぁ寝坊して聞けなかったんだけど。。。

で、ちょっと色々やってみたメモ。まださほど検証してない。

Installとかはほとんど公式のとおりに。memcachedと共存させるためにバイナリ名かえてみた。

./configure --enable-replication --program-transform-name=s/memcached/repcached/
make
make install

で、起動。console2つ開いて-vつけて情報表示させつつ。

repcached -v -m 64 -c 64 -u memcached -p 11212 -x 127.0.0.1   # repcached-A
repcached -v -m 64 -c 64 -u memcached -p 11213 -x 127.0.0.1   # repcached-B

こうすることでMultiMasterになる。2つのmemcachedの保持するデータは同一になるはず。

本来、Cache::Memcached*のモジュールはserversに複数渡すと特定のhashアルゴリズムでデータを格納するサーバーを選び、重複して登録されることはない。だがrepcachedでレプリケーションするとすべてのrepcachedが同内容になるため、この周りの挙動を確認してみる

あらかじめ

$cache->set("k".$_, "v".$_, 3600) for (1..20);

でデータをセットしておき、

#!/usr/bin/perl

use strict;
use Cache::Memcached;

my $c = Cache::Memcached->new({
    servers => ['127.0.0.1:11212', '127.0.0.2:11213'],
});

for (1..30){
    print $c->get('k'.$_) for (1..20);
    print "\n";
    sleep 1;
}

ってな感じの簡単なスクリプトを実行しつつ、repcachedをCtrl+Cで落としたりして見てみた。

結果はこんな感じ。

Cache::Memcachedの場合

v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20
# Ctrl+Cでrepcached-B停止
v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20   # <-落ちて初回のgetのみundefに
v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20 # <-以降はrehashされてrepcached-Aを見にいくが
                                                    #   複製されてるので全部取れる
# repcached-B起動
v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20 # <-復帰後しばらく(20-30sec)すると2台に戻る



Cache::Memcached::Fastの場合

v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20
# Ctrl+Cでrepcached-B停止
v2v5v7v9v10v12v15v17v19   # repcached-Aにあるものだけ取得できる
v2v5v7v9v10v12v15v17v19
# repcached-B起動
v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20



Cache::Memcached::libmemcachedの場合

v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16v17v18v19v20
# Ctrl+Cでrepcached-B停止
# ここでScript実行停止。たまにSegmentation faultでcoredumpが・・

なんかこんな感じになった。get_multiでも当然同じ感じなので略。また、例ではrepcached-Bを落としているが、repcached-Aを落としてもMultiMasterなので当然同様の結果になる。

Cache::Memcached::FastはPODにもno_rehash optionはサポートせず、サーバーの応答が無くなってもrehashはしない実装になってるって書いてあるので当然こうなる。一部サーバーが落ちている間はおそらくkeyによって保存できない状態になると思われる。

Cache::Memcachedはサーバーが落ちると20-30秒間接続リストから除外し、no_rehashオプション無しの場合は1台としてハッシュを再計算してくれるため、全部取れる。ただし、落ちた最初の1回のみ取得に失敗してundefになる。set_cb_connect_fail等でうまくやれるかなと思ってソースを見たがよくわからなかった。接続中のsocketがいきなり死んだ場合に再取得するような用途では使えないっぽい(未検証)

Cache::Memcached::libmemcacheはなぜか突然終了した。数回CoreDumpも吐いた。ちょっと謎。
複数memcachedサーバーな構成で1台接続中にコケるとそれ以後動かなくなるのか、もしくは接続中だったプロセスのみ落ちて最初から接続できない場合は除外されるのかもしれない。ただサービス側では::Fastを使っているため、::libmemcachedについてはほぼ未検証。

そもそもmemcachedのためのモジュール群なのでrepcachedのMultiMasterな挙動が考慮されてるわけでは無いので当然なのだが、Cache::Memcachedを使えばrepcachedの片側が不意に落ちたとしてもキャッシュ内容を失わずサービスを継続するという挙動を実現できるっぽい。

落ちた後のレプリケーションは自動で行われ、ドキュメントによるとレプリケーション完了までは接続を受け付けないらしいのでdaemontools等による自動再起動でも運用上問題はあまりおこらなそう。

何より、-vでdebug情報を出力するとレプリケーション時のメッセージが

marugoto copying.

とかになってるのがポイント高かった。別に動作とはまったく関係ないけどw

incr等もサポートしてるので

$cache->set("count::20080605::hideden", 1, 3600*48); 
$cache->incr("count::20080605::hideden", 1); 
$cache->get("count::20080605::hideden");

ってな感じで過去ログが不要な『昨日のアクセス数』『本日のアクセス数』なんかを出す部分で使ってみるといい感じかも。ただし、メモリが溢れる状況だとレプリケーションしててもデータは消えていってしまうので、memcached-tools等で監視しつつ運用する等はすべきかも。

mysqlでdriver=memoryとどっちの方が(使い勝手|パフォーマンス)いいんだろうか。ま、memcachedのシンプルさは十分魅力なんでこれはこれでやってみよう。