ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

MessagePack-RPCとkumofsを試す

先週、デブサミ2010に参加したとき、
一番感銘を受けたのがkumofsのセッションでした


kumofsから学ぶNot only SQLの技術 - 古橋貞之の日記


細かい内容はこのエントリを見ていただければいいのですが、
個人的に気になったのが「非同期処理」のデモでして


あくまでkumofsで使う管理ツールのデモではあったのですが、
その非同期処理コードがあまりにも簡潔だったので、
ちょっと調べてみたくなったのです


Ruby で MessagePack-RPC - 古橋貞之の日記


具体的なコードは後述しますが、
あるサーバから別なサーバのCouchDBを非同期に叩いてみました


結果、当然ながら同期とは比較にならない速さですし、
そのコードも驚くほど簡潔でした(`・ω・´) b


そのテストコードを利用して、前に「Tokyo Tyrant」で試したテストを、
CouchDBに対して非同期バージョンで流してみましたが、
とても現実的な数値ではありませんでした


CouchDB自体、そこまでハードな使い方を想定してないのもあるでしょうね
RubyとCの関係みたいなもので、パフォーマンスを犠牲にして、
RESTfulなインターフェースという利便性を取っているわけで


ならば、ストレージを「Tokyo Cabinet」に変えて、
MessagePack-RPCで非同期処理させたら・・・と、
さくっとコードを直してテストし始めたのですが・・・


・・・人はそれを「kumofs」と呼ぶよね・・・Σ(゚Д゚)ガーン


というわけで、今度はkumofsをインストールすることに



tokyocabinet

./configure --prefix=/usr/local/tokyocabinet

ruby extconf.rb  --with-tokyocabinet-dir=/usr/local/tokyocabinet


【msgpack】

./configure --prefix=/usr/local/msgpack

gem install msgpack

【kumofs】

./configure --prefix=/usr/local/kumofs \
    --with-tokyocabinet=/usr/local/tokyocabinet \
    --with-msgpack=/usr/local/msgpack


ここまでは良かったのですが、
いざmanagerを立ち上げようとしたところ、
TCのライブラリがないと怒られるのですΣ(・ω・ノ)ノ


もう一度configureのログをチェックしてみると、
以下のようなwarningが

tchdb.h: accepted by the compiler, rejected by the preprocessor!

つまり、configureの時点でTCのライブラリに弾かれているっぽいのです


そんな話をTwitterで書いていたのですが・・・

kumofsをconfigureしようとすると、tchdb.hへのアクセスがrejectされてしまう そのままmakeすると当然起動しない tokyocabinetのPathを変えてるのがまずいのかな・・・? TC自体は普通に動いてるんだけど・・・

http://twitter.com/parrot_studio/statuses/9468372839

@parrot_studio makeができたのであれば、おそらく実行時にライブラリを見つけられなかったのでしょう。export LD_LIBRARY_PATH=/path/to/tokyocabinet/lib を実行するとどうでしょう?

http://twitter.com/frsyuki/statuses/9468565975

@frsyuki "export LD_LIBRARY_PATH=/usr/local/tokyocabinet/lib" を実行したところ、kumo-managerが起動しました ありがとうございます

http://twitter.com/parrot_studio/statuses/9468911311

・・・作者さん本人からアドバイスを(ノ´・ω・)ノミ(m´_ _)m
いやぁ、Twitterってすばらしい・・・


あとはドキュメントを参考にlocalhostで起動

cd /usr/local/kumofs/bin
./kumo-manager -v -l localhost &
./kumo-server  -v -m localhost -l localhost:19801 -L 19901 -s /tmp/database1.tch &
./kumo-server  -v -m localhost -l localhost:19802 -L 19902 -s /tmp/database2.tch &
./kumo-server  -v -m localhost -l localhost:19803 -L 19903 -s /tmp/database3.tch &
./kumo-gateway -v -m localhost -t 11211 &
./kumoctl localhost attach

ちなみに、このあとの会話

よくみたらdocにかいてあるし ちゃんと読まないとダメだな・・・ http://github.com/etolabo/kumofs/blob/master/doc/doc.ja.md

http://twitter.com/parrot_studio/statuses/9469011379

@parrot_studio それ今書きましたw

http://twitter.com/frsyuki/statuses/9469144306

@frsyuki あ、そうだったんですか まあ、誰か同じところで引っかかった方の参考になればなによりです

http://twitter.com/parrot_studio/statuses/9469565026

いやぁ、Twitterって(ry


今日はここまでが限界だったので、
kumofsにデータを流し込むテストはまた後日(`・ω・´)ノ

MessagePack-RPC経由でCouchDBを非同期に叩く


参考にしたのは前述のサイト
ほぼそのままに近い

クライアント側
require 'rubygems'
require 'msgpack/rpc'

cli = MessagePack::RPC::Client.new("192.168.xxx.xxx", 5000)

# 同期テスト ------------------------------------
s_time = Time.now
puts "sync start : #{s_time}"

100.times do |i|
  cli.call(:get, '/')
end

e_time = Time.now
puts "sync end   : #{e_time}"
puts e_time - s_time

# 非同期テスト ---------------------------------
s_time = Time.now
puts "async start : #{s_time}"

# 先にメッセージを投げてしまう
reqs = []
100.times do
  reqs << cli.send(:get, '/')
end

# 先に投げたメッセージの返信を待つ
reqs.each do |req|
  req.join.result
end

e_time = Time.now
puts "async end   : #{e_time}"
puts e_time - s_time

cli.close
サーバ側
require 'msgpack/rpc'
require 'net/http'

class CDBServer

  def initialize
    @http = Net::HTTP.new("localhost", 5984)
  end

  # 同期GET
  def get(path = nil)
    path ||= '/'
    cdb_get(path)
  end

  # 非同期GET
  def async_get(path = nil)
    path ||= '/'
    as = MessagePack::RPC::AsyncResult.new

    # スレッドに処理を任せてオブジェクトを返す
    Thread.new do
      as.result(cdb_get(path))
    end

    as
  end

  private

  def cdb_get(path)
    req = Net::HTTP::Get.new(path)

    ret = nil
    @http.start do |w|
      response = w.request(req)
      ret = response.body
    end

    # あえて1秒待たせる
    sleep 1
    ret
  end

end

# 5000番ポートでlisten
svr = MessagePack::RPC::Server.new
svr.listen("0.0.0.0", 5000, CDBServer.new)

# シグナルをキャッチしたら終了
Signal.trap(:TERM) { svr.stop }
Signal.trap(:INT)  { svr.stop }

svr.run
結果
同期 非同期
1回目 100.216915 10.783514
2回目 100.21936 10.781574
3回目 100.223581 10.777062


GETで1秒のsleepを入れてるので、
同期で100回叩けばほぼ100秒なのは当然として、
さすが非同期だと速いですね・・・