NoSQLデータベースを試してみる

第2回RDBMSと組み合わせて使えるmemcached

はじめに

今回からタイプ毎に代表的なNoSQLデータベースを扱っていきます。まず今回は、揮発性key-valueストアの代表としてmemcachedを取り上げます。

なお、利用したコードやプログラムはgithubに置いてあります。適宜参照してください。

どんなところに使える?

memcachedの特徴は、何といってもデータの揮発性(memcachedサーバを停止すると全データが消えてしまうこと)です[1]⁠。そのため、データが消えても影響が小さいところに利用シーンは限られてきます。一方、連想配列(ハッシュ)のように使えて扱いやすいこと、様々なサイトでの事例があること、などから導入はしやすいです。

具体的な利用シーン

memcachedの具体的な利用シーンとしては、以下の事柄が考えられます。

  1. RDBMSから取得したデータのキャッシュ
  2. 消えても大きな影響の無いデータの保存

一般的には(1)のような、あくまでもRDBMSの補助としての利用でしょう。⁠2)の例として、ユーザのセッション情報などを保存することもありますが、データが消えた場合にログイン状態が解除されてしまうなどの影響があるため注意が必要です。

実際に利用してみる

では、実際にmemcachedを利用してみます。まずはmemcachedサーバを起動します。localhostに3台のmemcachedサーバを別々のポートで起動してみましょう。

プロンプト1 複数のmemcachedサーバを別々のポートで起動する
memcached -u nobody -m 16 -p 11211 -d
memcached -u nobody -m 16 -p 11212 -d
memcached -u nobody -m 16 -p 11213 -d

今回はmemcache-clientというライブラリを使ってRubyからmemcachedを利用するので、必要に応じてインストールしてください。

プロンプト2 memcache-clientのインストール
gem install memcache-client

今回はサーバを3台起動しましたが、コンストラクタの第1引数に配列で指定してあげるだけで、その3台のmemcachedサーバがアプリケーションから分散キャッシュとして利用できるようになります(以下のコードのdo_initアクション⁠⁠。とても簡単ですね。

リスト1 memcachedを利用したコード例
class MemcachedController < ApplicationController
  require 'memcache'
  before_filter :do_init

  DEFAULT_EXPIRE = 3600

  def set
    @cache['key1'] = 123                      # 数値
    @cache['key2'] = "あいうえお"              # 文字列
    @cache['key3'] = %w(hoge fuga)            # 配列
    @cache['key4'] = {:foo => 1, :bar => “a”} # ハッシュ
  end

  def get
    p @cache['key1'] # 123
    p @cache['key2'] # "あいうえお"
    p @cache['key3'] # ["hoge", "fuga"]
    p @cache['key4'] # {:bar=>"a", :foo=>1}
  end

  def show
    @user = @cache["user#{params[:id]}"]
    unless @user
      @user = User.find(params[:id])
      @cache.set("user#{params[:id]}", @user, DEFAULT_EXPIRE)
    end

    # do something
  end

  private

  def do_init
    @cache = MemCache.new(
      ['localhost:11211', 'localhost:11212', 'localhost:11213'],
      :logger => Logger.new(STDOUT)
    )
  end
end

データを保存すると、それぞれのキー(key1, key2, key3, key4)はそれぞれ別のポートに立てたmemcachedに分散して保存されていることがわかります。データの取得時も、保存されたそれぞれのサーバからデータを呼び出してくれます。

プロンプト3 コンソールログ
# key2, key3は11211に、key1は11212に、key4は11213に保存されている
set key1 to <MemCache::Server: localhost:11212 [1] (CONNECTED)>: 5
set key2 to <MemCache::Server: localhost:11211 [1] (CONNECTED)>: 19
set key3 to <MemCache::Server: localhost:11211 [1] (CONNECTED)>: 16
set key4 to <MemCache::Server: localhost:11213 [1] (CONNECTED)>: 19

Railsではフラグメントキャッシュなどのキャッシュの仕組みが備わっており、memcachedはそのバックエンドで動作させることが多いです。そのため、明示的にmemcachedを利用することは少ないかもしれないですが、よくある利用方法としては上記のコードのshowアクションのような形になります。

まずキャッシュされているかをmemcachedでチェックし、キャッシュされていなければRDBMSからデータを呼び出し、そのデータをmemcachedに保存します。このときsetメソッドを利用し、expiresを設定しています。[]=メソッドはsetメソッドのaliasで直感的ですが、expiresを設定することができません。

図1 memcachedのよくある利用方法
図1 memcachedのよくある利用方法

どうやって分散しているのか?

memcachedでは、クライアント側の実装によって分散が行われます。

以前は単純にハッシュ値をサーバ台数で割り、その余りでどのサーバに対してデータの保存/読み出しを行うかを決定していましたが、それだとサーバ台数が変更したときにデータの保存/読み出し先が大きく変わってしまい、一時的にではありますがキャッシュミスが頻発し、RDBMSにアクセスが集中していました。

そのため、現在ではConsistent Hashingという考え方でサーバ台数変更時の影響を最小限に留めようというのが一般的です。memcache-clientでも、バージョン1.6からConsistent Hashingに対応しています[2]⁠。サーバ台数を変更した場合もそれにあわせて各サーバにデータの保存/読み出しを振り分けてくれるので、分散させるのはとても簡単です[3]⁠。

手軽に扱うために

開発時など、memcachedにどんなデータが保存されているのか調べたい場合があります。telnetで接続して以下のようにコマンドを発行したり、memcached付属のmemcached-toolを利用することでもわかるのですが、あまり手軽ではありません。

プロンプト4 telnetで接続し、memcachedに保存されたデータの情報を取得する
$ telnet localhost 11211                                                                                                                              
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.

stats items
STAT items:1:number 2
STAT items:1:age 26873
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
END

stats cachedump 1 2
ITEM key2 [19 b; 1275144784 s]
ITEM key3 [16 b; 1275144784 s]
END

そこで、筆者はmemdump.rbという簡単なプログラムを作成し利用しています。以下のようなコマンドで指定したmemcachedサーバ内のキーの一覧を出力できます。

プロンプト5 memdump.rbの使い方
ruby memdump.rb [host] [port]

データキャッシュ時の注意

データをキャッシュするときはなるべく外側の、大きなデータの固まりでキャッシュするようにしましょう。あまり細かい粒度でキャッシュするとキャッシュ管理が複雑になってしまいます。古いデータがいつまでもキャッシュされているような状態になってしまっては意味がありません。

まとめ

memcachedはメモリ上にデータを保持するためサーバを落とすとデータが消えてしまいますが、その分非常に高速に動作します。また、扱いは非常に簡単でスケールさせるのも容易です。重いDB処理のキャッシュとして利用してみてはいかがでしょうか。

おすすめ記事

記事・ニュース一覧