dalliの挙動を検証してみた
Rails4でmemcachedをキャッシュストアで使う定番gemといえばdalliとRailsGuideに書いてある。ただ、このgemが実際どうゆう挙動をするのかググってみてもあまり日本語の情報が見つからなかったので試してみた。
環境
Ruby-2.0.0p353
Rails-4.0.2
dalli-2.7.0
正常時のパフォーマンス
Requests per second: 8.65 [#/sec] (mean) Time per request: 578.181 [ms] (mean) Time per request: 115.636 [ms] (mean, across all concurrent requests)
memcachedサーバが2台ある場合、あるキーは2台のサーバに分散してストアされてしまうのか?
Railsでは下記のようにconfig.cache_storeの設定をconfig/environments/環境名.rbに記述するが、この時に複数台のmemcachedサーバを書くことができる。
config.cache_store = :dalli_store, "cache1.hakutoitoi.com", "cache2.hakutoitoi.com"
ここで疑問に思ったのが、あるキー(ここでは仮にkey1とする)がcache1.hakutoitoi.comにストアされてその後のアクセスでcache2.hakutoitoi.comにもストアされてしまうかどうか。もしサーバが正常に動いているのにこれをやられてしまうとキャッシュヒット率が落ちてしまい悲しいことになる。
公式ドキュメントには下記のように書いてある。
uses the exact same algorithm to choose a server so existing memcached clusters with TBs of data will work identically to memcache-client.
TB級のデータならmemcached clustersと全く同じサーバ選択のアルゴリズムを使うってことらしいので、多分key1はずっと同じサーバにストアしてくれるはず。
実際にapache benchを使って1000回連続でリクエストしてみたがcache2.hakutoitoi.comにストアされることはなかった。
ab -n 1000 -c 10 "http://dalli-test.hakutoitoi.com/"
書き込まれた形跡はなし。
telnet cache2.hakutoitoi.com 11211 stats items END
memcachedサーバが2台ある場合に1台落ちたらどうなるのか?
どちらかのサーバが落ちた場合にどうゆう挙動になるのか、考えられるのは2つのパターン
- cache1にストアされるリクエストはキャッシュしない
- cache1が落ちてるのでcache2にストアする
これに関してはfailoverという設定を追加することでどちらの挙動になるか選択できる。
config.cache_store = :dalli_store, "cache-1.hakutoitoi.com", "cache-2.hakutoitoi.com", { failover: true }
上記の設定を追加すると、cache1-hakutoitoi.comが落ちていてもcache2にストアしてくれて、cache1が落ちてる事を知らせるログが出る。
I, [2014-02-10T07:55:18.694561 #19929] INFO -- : cache1.hakutoitoi.com:11211 failed (count: 3) Errno::ECONNREFUSED: Connection refused - connect(2)
パフォーマンスは半分くらいに落ちた。
$ ab -n 10 -c 5 "http://dallitest.hakutoitoi.com/" Requests per second: 4.67 [#/sec] (mean) Time per request: 1071.762 [ms] (mean) Time per request: 214.352 [ms] (mean, across all concurrent requests)
failover: falseの場合延々とキャッシュしないので下記ログが出続けてパフォーマンスは大きく悪化する。
I, [2014-02-10T07:35:14.837841 #18979] INFO -- : cache1.hakutoitoi.com:11211 failed (count: 4) Timeout::Error: execution expired E, [2014-02-10T07:35:14.838389 #18979] ERROR -- : DalliError: No server available
$ ab -n 10 -c 5 "http://dallitest.hakutoitoi.com/" Requests per second: 0.39 [#/sec] (mean) Time per request: 12678.444 [ms] (mean) Time per request: 2535.689 [ms] (mean, across all concurrent requests)
memcachedサーバが全て落ちたらどうなるのか?
つなげるmemcachedサーバがないとRailsアプリはどうなるのか?
結果はキャッシュをしないで正常なレスポンスを返すだった。
I, [2014-02-10T08:22:35.956360 #20576] INFO -- : cache1.hakutoitoi.com:11211 failed (count: 0) EOFError: end of file reached I, [2014-02-10T08:22:36.126920 #20576] INFO -- : cache1.hakutoitoi.com:11211 failed (count: 1) Errno::ECONNREFUSED: Connection refused - connect(2) W, [2014-02-10T08:22:36.127159 #20576] WARN -- : cache1.hakutoitoi.com:11211 is down I, [2014-02-10T08:22:36.130691 #20576] INFO -- : cache2.hakutoitoi.com:11211 failed (count: 0) EOFError: end of file reached I, [2014-02-10T08:22:36.308573 #20576] INFO -- : cache2.hakutoitoi.com:11211 failed (count: 1) Errno::ECONNREFUSED: Connection refused - connect(2) W, [2014-02-10T08:22:36.308748 #20576] WARN -- : cache2.hakutoitoi.com:11211 is down E, [2014-02-10T08:22:36.309556 #20576] ERROR -- : DalliError: No server available
最悪の結果であるページエラーにならなくてよかったが、データベースにアクセスが集中して落ちる可能性が高いので迅速な復旧作業が必要だろう。