ふと思って、Ruby でデータを memcached に入れて、それを Perl から取り出せるのかな〜というのが気になったので試してみました。データの形式として、文字列、ActiveRecord、ハッシュをそれぞれ試してみます。予想では、ActiveRecord のデータはさすがに Perl では読み込めないだろうけど、それ以外は読み込めるんじゃないかなぁと。
まずは Ruby を使って memcached にデータを set します。
で、これを Perl で読み込んでみます。
・・・何か変です。データが受け取れてはいるもののぐちゃっとしています。。文字列なんかは正しく受け取れているように見えますが、これも微妙に違います。こんなコードで検証してみました。
データが正しく受け取れていれば(つまり 'fugafuga' が正しく受け取れていれば)、undef ではなく、1 が得られるはずです。この辺の原因を探るために、Ruby の memcache-client のソースコードをチェックしてみたところ、理由がわかりました。
# /usr/lib/ruby/gems/1.8/gems/memcache-client-1.7.5/lib/memcache.rb の一部
まず、[] を set にエイリアスしていますが、その set の中で、Marshal.dump してシリアライズしています( raw が false のとき)。そしてデータを取り出すときには get しますが、その中で Marshal.load してデシリアライズしているようです( raw が false のとき)。なので、Ruby で set したものを Perl で普通に受け取ってもそれはシリアライズされたデータなのでそのままでは利用できません。かといって、Perl で Storable モジュールを使えば復元出来そうですが、うまく出来ませんでした。。微妙に形式が違うのかも?しれません。
そこで、そもそもシリアライズしないようにしてみます。これは raw に true を代入してあげればおkです。こうすれば、文字列の場合にはデータの set, get どちらもきちんと行うことが出来ました。でも ActiveRecord やハッシュの場合にはデータの set でエラーになってしまいます。。まぁ、、そのためのシリアライズですよね。(´Д⊂)
さて、このような場合には、汎用のデータ形式を使うことで解決できます。XML や JSON といった形式です。例えば JSON 形式を利用してみましょう。
このように Ruby で データを作成し、それを JSON 形式に変換します。そのデータをmemcached に set します。このとき、第4引数 (raw) を true にして、シリアライズを行わないようにしていることに注意してください。ちなみに第3引数は expires ですが、これを 0 に設定した場合にはそのデータが消えることはありません(データはサーバが起動している限りずっと保持される)。
その後、Perl でデータを受け取って、JSON からハッシュへと変換することが出来ました。このようにすれば異なる言語間でもデータのやり取りをすることが出来ますね。スバラシス。
まずは Ruby を使って memcached にデータを set します。
#!/usr/bin/ruby
require 'rubygems'
require 'activerecord'
require 'memcache'
class Hoge < ActiveRecord::Base
establish_connection(
'adapter' => 'mysql',
'host' => 'localhost',
'database' => 'test',
'username' => 'USER',
'password' => 'PASS',
'encoding' => 'utf8'
)
set_table_name :hoge
end
cache = MemCache::new '127.0.0.1:11211'
# 文字列
cache['hogehoge'] = 'fugafuga'
# ActiveRecord で取得したデータ
hoge = Hoge.find(1)
cache['activerecord'] = hoge
# ハッシュ
hash = {}
hash['hoge'] = 100
hash['fuga'] = 200
cache['foo'] = hash
で、これを Perl で読み込んでみます。
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
use Data::Dumper;
my $cache = Cache::Memcached->new({
servers => ['127.0.0.1:11211']
});
warn Dumper $cache->get('hogehoge'); # fugafuga';
warn Dumper $cache->get('activerecord'); # $VAR1 = o: Hoge:@attributes{" name"\\"id"1:@attributes_cache{';
warn Dumper $cache->get('foo'); # $VAR1 = {" fugai hogeii';
・・・何か変です。データが受け取れてはいるもののぐちゃっとしています。。文字列なんかは正しく受け取れているように見えますが、これも微妙に違います。こんなコードで検証してみました。
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
use Data::Dumper;
my $cache = Cache::Memcached->new({
servers => ['127.0.0.1:11211']
});
my $hash = {
fugafuga => 1
};
my $hogehoge = $cache->get('hogehoge');
warn Dumper $hash->{$hogehoge}; # undef
データが正しく受け取れていれば(つまり 'fugafuga' が正しく受け取れていれば)、undef ではなく、1 が得られるはずです。この辺の原因を探るために、Ruby の memcache-client のソースコードをチェックしてみたところ、理由がわかりました。
# /usr/lib/ruby/gems/1.8/gems/memcache-client-1.7.5/lib/memcache.rb の一部
alias [] get
def []=(key, value)
set key, value
end
# set
def set(key, value, expiry = 0, raw = false)
raise MemCacheError, "Update of readonly cache" if @readonly
value = Marshal.dump value unless raw
with_server(key) do |server, cache_key|
logger.debug { "set #{key} to #{server.inspect}: #{value.to_s.size}" } if logger
if @check_size && value.to_s.size > ONE_MB
raise MemCacheError, "Value too large, memcached can only store 1MB of data per key"
end
command = "set #{cache_key} 0 #{expiry} #{value.to_s.size}#{noreply}\r\n#{value}\r\n"
with_socket_management(server) do |socket|
socket.write command
break nil if @no_reply
result = socket.gets
raise_on_error_response! result
if result.nil?
server.close
raise MemCacheError, "lost connection to #{server.host}:#{server.port}"
end
result
end
end
end
#get
def get(key, raw = false)
with_server(key) do |server, cache_key|
logger.debug { "get #{key} from #{server.inspect}" } if logger
value = cache_get server, cache_key
return nil if value.nil?
value = Marshal.load value unless raw
return value
end
rescue TypeError => err
handle_error nil, err
end
まず、[] を set にエイリアスしていますが、その set の中で、Marshal.dump してシリアライズしています( raw が false のとき)。そしてデータを取り出すときには get しますが、その中で Marshal.load してデシリアライズしているようです( raw が false のとき)。なので、Ruby で set したものを Perl で普通に受け取ってもそれはシリアライズされたデータなのでそのままでは利用できません。かといって、Perl で Storable モジュールを使えば復元出来そうですが、うまく出来ませんでした。。微妙に形式が違うのかも?しれません。
そこで、そもそもシリアライズしないようにしてみます。これは raw に true を代入してあげればおkです。こうすれば、文字列の場合にはデータの set, get どちらもきちんと行うことが出来ました。でも ActiveRecord やハッシュの場合にはデータの set でエラーになってしまいます。。まぁ、、そのためのシリアライズですよね。(´Д⊂)
#!/usr/bin/ruby
require 'rubygems'
require 'activerecord'
require 'memcache'
class Hoge < ActiveRecord::Base
establish_connection(
'adapter' => 'mysql',
'host' => 'localhost',
'database' => 'test',
'username' => 'USER',
'password' => 'PASS',
'encoding' => 'utf8'
)
set_table_name :hoge
end
cache = MemCache::new '127.0.0.1:11211'
cache.set('hogehoge', 'fugafuga', 0, true)
hash = {}
hash['hoge'] = 100
hash['fuga'] = 200
cache.set('foo', hash, 0, true) # エラー
hoge = Hoge.find(1)
cache.set('activerecord', hoge, 0, true) # エラー
さて、このような場合には、汎用のデータ形式を使うことで解決できます。XML や JSON といった形式です。例えば JSON 形式を利用してみましょう。
このように Ruby で データを作成し、それを JSON 形式に変換します。そのデータをmemcached に set します。このとき、第4引数 (raw) を true にして、シリアライズを行わないようにしていることに注意してください。ちなみに第3引数は expires ですが、これを 0 に設定した場合にはそのデータが消えることはありません(データはサーバが起動している限りずっと保持される)。
#!/usr/bin/ruby
require 'rubygems'
require 'activerecord'
require 'memcache'
class Hoge < ActiveRecord::Base
establish_connection(
'adapter' => 'mysql',
'host' => 'localhost',
'database' => 'test',
'username' => 'USER',
'password' => 'PASS',
'encoding' => 'utf8'
)
set_table_name :hoge
end
cache = MemCache::new '127.0.0.1:11211'
hash = {
'key1' => 'data1',
'key2' => 'data2'
}
cache.set('json', hash.to_json, 0, true)
その後、Perl でデータを受け取って、JSON からハッシュへと変換することが出来ました。このようにすれば異なる言語間でもデータのやり取りをすることが出来ますね。スバラシス。
#!/usr/bin/perl
use strict;
use warnings;
use Cache::Memcached;
use JSON;
use Data::Dumper;
my $cache = Cache::Memcached->new({
servers => ['127.0.0.1:11211']
});
my $json = $cache->get('json');
warn Dumper from_json($json); # $VAR1 = {
# 'key2' => 'data2',
# 'key1' => 'data1'
# };
