repcached
repcached-2.0リリースのお知らせと、超簡単なサンプルコード
repcached-2.0(memcached-1.2.5ベース) をリリースしましたのでお知らせします。
今回の目玉はマルチマスタ構成のサポートです。
以前のバージョンはマスタ/スレーブ構成だったので、必ずマスタへ書き込まなければいけませんでした。そのため、接続先のサーバがマスタなのかどうかをクライアントが判別しなければいけなかったり、keepalivedなどと併用するなどの工夫が必要でしたが、今回のバージョンではその必要がなくなります。両方のサーバに対してデータを書き込むことができるようになったので、かなり使いやすくなったと感じています。
repcachedはパフォーマンスを最重視しているため、レプリケーションは非同期で処理しています。したがって、setと同時にレプリケーションが完了する保証はありません。つまり、サーバAにsetした直後にサーバBからgetした場合、正しい値が返ってこない可能性があります。
しかし、memcachedのクライアントライブラリは負荷分散機能を持っていて、複数のサーバがある場合、キーのハッシュ値を元にして接続先のサーバが選択されます。そのため、同一のキーに対する操作は必ず同じサーバで処理されます。これを図にすると以下のような感じになります。
つまり、クライアントライブラリの負荷分散機能を利用すると、データを格納したサーバと同じサーバが参照されるので、非同期処理に起因するデータの不整合は発生しません。そして、すべてのデータは両方のサーバへ格納されているので、片方のサーバがダウンしてもすべてのデータが保持されます。
ただし、参照しようとしたサーバがダウンしていた時に、自動的にもう片方のサーバへ参照しにいくかどうかは、クライアントライブラリの実装に依存します。例えば、PHP の memcached extension で提供される Memcache::set 関数の場合は、Version3.0.0以上ならば自動的に再接続してくれますが、Version2.2.3ではエラーになってしまいます。そのような場合は、自分で再試行するなどの処理を実装する必要があるかもしれません。セッションハンドラ(session.save_handler="memcache")を利用する場合は、バージョン2.2.3でも再接続してくれます。
※この例は後ほどご紹介します。
ダウンしていたサーバが復旧して最初にやることは、既存のサーバの全てのデータを複製(以下、まるごとこぴー)することです。その間、復旧中のサーバはクライアントからの接続を受け付けないので、誤って中途半端な状態のデータを取得してしまうことはありません。また、まることこぴーの最中でも、コピー元のサーバに対しては正常にアクセスできるので、サービスが停止してしまう心配もありません。
まるごとこぴーが完了次第、レプリケーションを再開し、両方のサーバへアクセスできるようになります。まるごとこぴーの所要時間は、ギガビットイーサの環境で10万件で3秒程度、100万件で30秒程度でした。
簡単な使用例
簡単な使用例として以下のコードを紹介します。セッション変数を利用して、リロードするたびにカウンタが増えていくPHPです。 セッションハンドラとしてmemcacheを使い、データの保存先にrcd1とrcd2というサーバを指定しています。rcd1とrcd2ではrepcachedを起動しておきます。
[count.php]<?PHP $rcd1="tcp://rcd1?persistent=1&weight=1&timeout=1&retry_interval=15"; $rcd2="tcp://rcd2?persistent=1&weight=1&timeout=1&retry_interval=15"; $session_save_path = "$rcd1,$rcd2"; ini_set('session.save_handler', 'memcache'); ini_set('session.save_path', $session_save_path); session_start(); if(empty($_SESSION['count'])){ $_SESSION['count']=1; } else { $_SESSION['count']++; } echo "count=".$_SESSION['count']."\n"; ?>※PHP-5.2.5 + memcache-2.2.3で動作確認
ブラウザでこのページへアクセスすると、リロードする度にカウンタが増えていきます。 rcd1,rcd2のどちらかが動いている限りカウンタは増え続け、両方停止するとリセットします。
このように、repcached-2.0はmemcachedを2台で負荷分散するのと同じ感覚で利用できます。 すでにmemcachedを2台構成で運用しているシステムであれば、repcached-2.0に置き換えるだけで冗長構成になるかもしれません。 さすがにオリジナルのmemcachedと比べると若干パフォーマンスは落ちますが、それでも実用には支障のないレベルに仕上がっていると思いますので、もしよろしければご利用下さいませ。
repcached-1.2をリリースしました
repcached-1.2 (memcached-1.2.4ベース) を公開しましたのでお知らせします。
今回のバージョンアップは以下の2件の不具合修正になります。
- flush_allのレプリケーション時にマスタでメモリリークする可能性があったのを修正しました
- flagsパラメータがレプリケーションされていなかったのを修正しました
ダウンロードやインストール方法などの詳しい情報は、プロジェクトページをご覧ください
flagsパラメータがレプリケーションされない不具合の詳細
memcachedプロトコルでは更新系コマンドにflagsというパラメータがあります。
set key 0 0 5 ↑これ本来であれば、このパラメータもレプリケーションすべきですが、以前のバージョンではバックアップで常に0をセットしてしまっていました。
master: set key 1 0 5 ← Masterはflags=1なのに backup: set key 0 0 5 ← backupはflags=0になっちゃってる
flagsパラメータを利用していないアプリケーションには影響ありませんが、PHPのMemcache::set関数などを使ってオブジェクトを格納しているようなアプリケーションでは致命的な問題となります。例えば、以下のようなコードです。
※環境は php-5.2.5 + memcache-2.2.3.tgz です。
#!/usr/bin/php <?PHP $memcacheM = new Memcache; $memcacheB = new Memcache; $memcacheM->connect('master', 11211) or die ("connect errorM\n"); $memcacheB->connect('backup', 11211) or die ("connect errorB\n"); $tmp_object = new stdClass; $tmp_object->str_attr = 'test'; $tmp_object->int_attr = 123; $memcacheM->set('key', $tmp_object, false, 10) or die ("set error\n"); $get_resultM = $memcacheM->get('key'); echo "Master\n"; var_dump($get_resultM); echo "\n"; $get_resultB = $memcacheB->get('key'); echo "Backup\n"; var_dump($get_resultB); ?>
実行結果は以下のようになります。
【実行結果】
Master object(stdClass)#5 (2) { ["str_attr"]=> string(4) "test" ["int_attr"]=> int(123) } Backup string(66) "O:8:"stdClass":2:{s:8:"str_attr";s:4:"test";s:8:"int_attr";i:123;}"
なんと、Masterからgetした場合はオブジェクトとして扱われているのに対し、Backupでgetすると文字列として扱われてしまっています。PHPのMemcache関数は、データをシリアライズして格納しているかどうかを判断するためにflagsを利用しているようです。もしかすると他のライブラリでも同様の問題があるかもしれませんのでご注意下さい。
repcached-2.0の近況
現在、memcached-1.2.5をベースにしたrepcached-2.0を開発中です。実装のほうはほとんど終わっていまして、今はコードを見直しながらストレステストの準備をしています。早ければ今月中、遅くとも来月の頭にはリリースできると思いますのでご期待下さいませ!
(や)
repcached 1.1をリリースしました+2.0のもくろみ
repcached 1.1 (memcached-1.2.4ベース) を公開しました!
主な変更点は次の通りです。
- memcached-1.2.4の新しいプロトコル (append, prepend, cas) に対応。
- ただし、いまのところcas unique IDのレプリケーションには対応していないので、getsとcasの間にフェイルオーバが発生すると、casが失敗する可能性があります。
- レプリケーション用のテストケースを同梱しました。
prove t.rep
でテストを実行できます。
ダウンロードやインストール方法などの詳しい情報は、プロジェクトページをご覧くださいませ!
そして2.0
1.x系で実装したいと考えていたことが完了し一段落したので、ぼちぼち2.0に向けて動き出しています。
repcached 2.0での目玉機能は、2台でのマルチマスタ構成のサポートです。
このマルチマスタが実装されると、こんないいことがあります:
- クライアントはサーバのフェイルオーバ、フェイルバックを意識する必要がなくなります。 (※クライアントライブラリに依存します)
- 2台のサーバを有効に活用することができます。
repcached 1.xでは、バックアップ機が遊んでいましたが、2.xではちゃんと仕事をしてもらいます。
マルチマスタを実現するアイディアについては、 去年行った社内勉強会『repcachedのお披露目』の資料 (PDF)を見ていただければと思います。
ただいま、鋭意実装中なのですが、
みなさんに公開できるのはもう少し先 (3月ぐらいですかねー) になると思います。
リリースの準備ができましたら、またここでアナウンスしますのでご期待くださいませ!
(ひ)
や
repcached 1.0リリースの補足とちょっとしたHowTo
前のエントリでrepcached 1.0のリリースをお知らせしました。
開発の動機はプロジェクトページにちょっと書いたのですが、たいせつなことをひとつ書き忘れていました。
repcachedを開発しようと思い立ったのは、ひとことでいうと「キャッシュサーバといっても止まらないようにしたい」、別ないい方をすると「サーバ管理者だって夜はぐっすり眠りたい」というものでした。
そしてふたりのエンジニアが自ら社内向けに企画・提案してrepcachedの設計・開発にとりかかり、プロダクション環境で使えるようなレベルのものができあがりました。
これがrepcachedを開発した動機です。
さて、昨日はrepcachedをみなさんに向けて公開しました。
いうまでもなく、repcachedを社内に閉じて外部には公開せずに使い続けるという選択肢もありました。
でもわたしたちはそうしませんでした。
わたしたちはいろいろなオープンソースのプロダクトのお世話になって、今日もおいしいごはんが食べられる生活を過ごせています。とても幸せなことだと思っています。
でも、常々こうも思っていました。
「give and takeでいえば、takeしてばかりではないか?」
「どうしたらgiveすることができるだろうか?」
そして、
「いつかはみなに使ってもらえるようなオープンソースソフトウエアを作りたい」
という想いをもっていました。
これが前のエントリで書き忘れたこと、そしてこのエントリで(ちょっとはずかしいですがw)みなさんに伝えたかったこと ―repcachedを公開した動機― です。
ちなみにrepcached以外にも、いくつか公開しているソフトウエアがあるのですが、これらもすべて同じ想いで公開しているものですのでお使いいただければと思います!
だいぶ前置きが長くなってしまいましたが、後半は、各所でいただいた意見を反映しながら、使い方についてちょっと補足したいと想います。
repcachedのビルド
現時点でのrepcachedのバージョンは1.0、対応するmemcachedのバージョンは1.2.2です。以降の説明では$MCDVER
、$RCDVER
というシェル変数を使っていますが、これらには次のようにバージョン番号を格納している
$ MCDVER=1.2.2 $ RCDVER=1.0
とします。もし、将来、バージョンが変わっても、新しいバージョンを代入すれば以降の手順はそのまま使えると思います。
repcachedはmemcachedに対するパッチという形式で配布していますので、memcachedのアーカイブを入手・展開して、repcachedパッチをダウンロードして適用してください。
$ wget http://www.danga.com/memcached/dist/memcached-${MCDVER}.tar.gz $ tar zxf memcached-${MCDVER}.tar.gz $ cd memcached-${MCDVER} $ wget http://downloads.sourceforge.net/repcached/repcached-${RCDVER}-${MCDVER}.patch.gz $ gzip -cd repcached-${RCDVER}-${MCDVER}.patch.gz | patch -p1
また、memcachedにrepcachedパッチ適用した状態のアーカイブも配布していますので、こちらを利用になっても構いません。
$ wget http://downloads.sourceforge.net/repcached/memcached-${MCDVER}-repcached-${RCDVER}.tar.gz $ tar zxf memcached-${MCDVER}-repcached-${RCDVER}.tar.gz $ cd memcached-${MCDVER}-repcached-${RCDVER}
ソースコードの準備ができたら、つづいてコンパイルです。
レプリケーション機能を有効にするには、configureで--enable-replication
を指定します。ただし、--enable-replication
と、memcachedでスレッドサポートするための--enable-threads
は併用できない点に注意してください。
スレッドサポートと併用できない理由(ちょっと内部実装の話になりますが)はこうです。repcachedの場合、レプリケーション待ちのデータを内部で保持するためにキューを作って使っているのですが、スレッドサポートの場合は複数のスレッドが同時にこのひとつのキューに対して読み書きをするため、排他制御をかける必要があります。実は当初はスレッドサポートで排他制御をするように実装していたのですが、性能評価をした結果、思った以上に性能が低下したので、思いきってスレッドサポートをやめて、一切、排他制御が必要のない実装にすることにした、という経緯があります。
さて、そのほかのconfigureのオプションはオリジナルのmemcachedと同じです。 configureが成功したら、続いてmake、make installを行います。
$ ./configure --enable-replication $ make # make install
起動
インストールができたら起動してみましょう。
repcachedでは2つのオプションが追加されています。
- -x <ip_addr>
- マスタのホスト名かIPアドレスを指定します
- -X <port>
- レプリケーション用のポートを指定します。デフォルトは11212です。 理由がなければオプション無指定でデフォルト値でいいと思います。
-xオプションでマスタを指定します。
このオプションが指定された場合は、まず、指定されたマスタ(の-Xオプションで指定されたポート。デフォルトで11212)に接続しにいきます。
もし、接続できた場合は、自分はバックアップ機として動作します。
一方、接続できなかった(=マスタが存在しなかった)場合は、自分はマスタ機として動作をするようになっています。
では具体的に、2つのホスト、fooとbarでレプリケーションしてみましょう。
まずは、fooでmemcachedを起動します。
(repcachedパッチを適用しても、memcachedプログラムのファイル名は「memcached」のままです)
マスタとしてbarを指定(-x bar
)していますが、この時点ではまだホストbarではmemcachedが起動していない(=マスタが存在しない)ので、fooはマスタ機として動作します。
foo$ memcached -v -x bar replication: master start
続いてホストbarで、マスタとしてfooを指定(-x foo
)してmemcachedを起動します。
さきほど、fooではマスタとなるmemcachedを起動していますので、barのmemcachedはマスタの存在を検知してバックアップ機として振る舞います。
bar$ memcached -v -x foo replication: backup start (master=10.10.2.26:11212) replication: connect
うまくレプリケーション接続が確立できた場合、起動オプションに-vをつけていればマスタ側に「replication: accept」と出力されるはずです。
レプリケーションの確認
この状態で、正しくデータのレプリケーションが行われているはずです。試しに、マスタ(foo)に適当な値をset(例えば、key:test1, data:one)してみて、同じ値がマスタ機(foo)とバックアップ機(bar)の両方から得られるか確認してみてください。
repcachedは、シングルマスタ/シングルバックアップという構成でレプリケーションします。
現時点では、マルチマスタや、シングルマスタ/マルチバックアップという構成では動作しませんので注意してください。
フェイルオーバとフェイルバック
レプリケーションの動作が確認できたら、repcachedのもうひとつの目玉機能であるフェイルオーバを試してみましょう。
マスタ(foo)、バックアップ(bar)でレプリケーションができている状態で、おもむろにマスタ(foo)のmemcachedを終了してみます。
するとバックアップだったbarは、マスタの不在を検知して、自らがマスタとして振る舞うようになります。barのターミナルには次のように表示されているはずです。
replication: close replication: master start
つまり、バックアップ機がマスタに昇格してフェイルオーバが成功したわけです。
注目して欲しいのは、昇格する際にbarに対して外部から管理コマンドを送ったり通知シグナルを送ったりする必要はなく、自動的に昇格したという点です。
さて、いまの状態を確認しておくと、マスタ(bar)だけでバックアップ機はなしという状態です。
あとの実験のため、ここで適当な値(例えば、key:test2, data:two)をマスタに対してsetしておきましょう。
ところで、いつまでもマスタ1台だけの片肺飛行はまずいので、バックアップ機を復帰(フェイルバック)してみることにしましょう。
ホストfooで、次のようにしてmemcachedを起動します。
foo$ memcached -v -x bar replication: backup start (master=10.10.2.27:11212) replication: connect
ここで注目してほしいのは、いちばん最初にfooでmemcachedを起動したときと、オプションが全く同じだということです。
いちばん最初の例では指定マスタが存在しなかったので自らがマスタになりましたが、こんどは指定されたマスタ(bar)が存在するので、バックアップとして動作します。
繰り返しになりますが、repcachedでは、状況(マスタが存在するか/しないか)に応じて、適切な振る舞い(マスタになるか/バックアップになるか)を自動的に判断するように実装しています。
これで再び、マスタ/バックアップ構成に復帰できたわけですが、双方が保持するデータはどのようになっているでしょうか?
試しに、最初にレプリケーションできたときにsetした値(key:test1)と、マスタだけの片肺飛行中にsetした値(key:test2)を、バックアップからgetして値を確認してみてください。マスタと同じ値がバックアップ機からも得られるはずです。
実は、ひとつもデータを持っていない新しいバックアップ(foo)がマスタ(bar)に接続した場合、まず最初にマスタが持っているすべてのデータをバックアップにコピーするようにしています。ですので、まっさらなバックアップ機を復帰した場合でも、マスタとバックアップとで保持しているデータが食い違ってしまうといったことは起こりません。
ちなみに、手元の環境(1000Base-T)でマスタ→バックアップの全コピーにかかった時間は、20万件で2秒、100万件で10秒でした。
おちぼひろい
フェイルオーバ時のクライアントの対応
repcachedはシングルマスタ構成なので、memcachedのクライアントは接続するサーバとしてただひとつのIPアドレスを指定する必要があります。
しかし、repcachedがフェイルオーバした場合、マスタの役割を果たすサーバが変わってしまうので、サーバのIPアドレスも変わってしまいます。
これではフェイルオーバのたびにクライアントの設定を変えなくてはならず、「ラクな運用」を信条とするDSASでは受け入れられません。
この問題については、後日、別エントリでスマートな解決方法を考えてみたいと思っていますのでご期待くださいませ。
パフォーマンスについて
レプリケーションという機能を追加している以上、オリジナルのmemcachedとrepcachedはまったく同じ性能である、というのは嘘になります。
しかし、オリジナルのmemcachedがもつイベントループやIO多重化の機構にうまくとけこむようにrepcachedの処理を実装したので、性能の低下は「ないわけではないがそれほど大きい低下ではない」と考えています。
気になる方は、要求や用途に即した環境においてご自身の手で性能評価を行うのがいちばんだと思います。
が、参考までに記しておきますと、わたしたちが行ったマイクロベンチの結果はプロジェクトページの方に書いてある通りで、ベンチマークプログラムに改善の余地はあるものの、秒あたりのset数、秒あたりのget数共に高い数値ではないかと思っています。