java で memcached に入れたものを ruby で取り出す (その逆も)

タイトル通り。
memcached つかうほどのものをいじっていないのだけれども面白そうなのでメモ。

以下、javahttp://www.whalin.com/memcached/
rubymemcached-client を前提とする*1

普通につかう。

普通にサンプル通り書くと、java なら

String[] servers = { "localhost:11211", };

SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.initialize();

MemCachedClient mcc = new MemCachedClient();
mcc.set("one", 1);
mcc.set("two", 2);
mcc.get("one");

こんな感じ*2

ruby だと

require 'rubygems'
require 'memcache'

mem = MemCache.new("localhost:11211")

mem["three"] = 3
mem["four"] = 4
mem["three"]

こんな感じ。

普通に使う分にはこれで十分っぽい*3

java で入れたものを ruby から取り出す

でも、ここで java で入れたやつを ruby から取ろうとして、

p mem["one"]

とかやっても、

/opt/local/lib/ruby/gems/1.8/gems/memcache-client-1.5.0/lib/memcache.rb:214:in `load': marshal data too short (ArgumentError)
from /opt/local/lib/ruby/gems/1.8/gems/memcache-client-1.5.0/lib/memcache.rb:214:in `[]'
from mem.rb:8

とかそんな感じで、怒られる。
これは、ruby の memcache-client が値がマーシャルであることを期待しているため*4

該当個所はここらへん。

  ##
  # Retrieves +key+ from memcache.  If +raw+ is false, the value will be
  # unmarshalled.

  def get(key, raw = false)
    server, cache_key = request_setup key

    value = if @multithread then
              threadsafe_cache_get server, cache_key
            else
              cache_get server, cache_key
            end

    return nil if value.nil?

    value = Marshal.load value unless raw # ここでエラー!!!

    return value
  rescue TypeError, SocketError, SystemCallError, IOError => err
    handle_error server, err
  end

ちなみに話がずれるけど、get なんて使ってないのにここでエラーになっているのは、以下のようにエイリアスが定義されているから。

alias [] get

まあということで、

p mem.get["one",true]

とやってあげればOK、とおもいきや、java で入れた値の場合、

"\000\000\000\001"

という値が返ってきてしまう。
これに関しては、javamemcached ライブラリのサイトの http://www.whalin.com/memcached/HOWTO.txt にコメントがあって、

Multi-client Example:
=====================

If you need to support multiple clients (i.e. Java, PHP, Perl, etc.)
you need to make a few changes when you are setting things up:

// use a compatible hashing algorithm
pool.setHashingAlg( SockIOPool.NEW_COMPAT_HASH );

// store primitives as strings
// the java client serializes primitives
//
// note: this will not help you when it comes to
// storing non primitives
mcc.setPrimitiveAsString( true );

// don't url encode keys
// by default the java client url encodes keys
// to sanitize them so they will always work on the server
// however, other clients do not do this
mcc.setSanitizeKeys( false );

ということらしい。ということで、java 側を

String[] servers = { "localhost:11211", };

SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setHashingAlg( SockIOPool.NEW_COMPAT_HASH ); // ここと
pool.initialize();

MemCachedClient mcc = new MemCachedClient();
mcc.setPrimitiveAsString( true ); // ここと
mcc.setSanitizeKeys( false ); // ここ
mcc.set("one", 1);
mcc.set("two", 2);
mcc.get("one");

てな感じでかえてあげると、ruby からも値が取り出せるようになる。

ruby で入れたものをjava からとりだす

ruby の memcache-client の場合、値をマーシャルにして保存しようとするので、そうさせないようにする。

該当部分のコードは以下。

  def add(key, value, expiry = 0, raw = false)
    raise MemCacheError, "Update of readonly cache" if @readonly
    server, cache_key = request_setup key
    socket = server.socket

    value = Marshal.dump value unless raw # ここでマーシャルにしてる!!
    command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"

    begin
      @mutex.lock if @multithread
      socket.write command
      socket.gets
    rescue SocketError, SystemCallError, IOError => err
      server.close
      raise MemCacheError, err.message
    ensure
      @mutex.unlock if @multithread
    end
  end

つまり、ruby

 mem.add("one","1",0,true)

として値を入れてあげれば java から

mcc.get("one");

という形でとりだせる。

ここで、注意しなきゃいけないのは、add の定義のなかで、

 command = "add #{cache_key} 0 #{expiry} #{value.size}\r\n#{value}\r\n"

となっている点。ruby の場合、

 1.size #=> 4
 "1".size #=> 1

なので、値は Fixnum とかじゃまずい*5

 mem.set("one",1,0,true) # 第二引数が String じゃない!

とかやるとエラーになる*6

サイズがあうものを入れると入れられちゃう分、気がつきにくそうで怖い。
つまり、以下はOK。

 mem.set("hoge",1234,0,true) # 第二引数が String じゃないけど、通る!

…。

まとめ

ruby の memcache-client は デフォルトだと値をマーシャルにして入れているということ。
マーシャルじゃなくて保存したいときには、add と get の引数に true を指定してあげればよい。
ruby,php,perlでmemcacheの中身を使い回す | Parse Errorとかをみる限りだと、perl とか php はそのまま入れてるっぽい。

*1:gem install memcached-client で入るやつ

*2:import とかは省略

*3:多分

*4:ちなみに add するときもデフォルトでは、マーシャルにして入れている

*5:だからマーシャルにしてるのかなーとか思ったり

*6:というか反応が返ってこなくなる