Amazon EC2 で手元のホストとファイルを共有する

Amazon EC2 はVMを好きなタイミングで好きなだけ使うことができるので、複数のサーバを使う分散システムの検証環境として非常に使いやすい*1。費用を計算してみても、自宅でクラスタを動かすことを考えれば、十分安く上がりそうである。


しかし、VMを起動するたびに環境を設定したり、新しいテストを実行するたびにファイルを配ったりするのは面倒なので、ファイルを共有したくなる。
通信の遅延がかなり大きいので、EC2上でssh越しにファイルを編集するのも少々つらい。


そこで、手元のホストとホームディレクトリを共有する。さらに、一度転送したデータはキャッシュさせる。
共有ホームディレクトリ環境の管理方法と組み合わせれば、便利に使えるに違いない。

戦略

ファイル共有には NFSv4 over ssh を使う。NFSv4はポート番号を1つしか使わない(portmapperも要らない)ので、ssh越しで使いやすい。
これに FS-Cache を組み合わせて、データをキャッシュさせる。FS-Cache は Linux 2.6.30 から組み込まれている機能で、メモリだけでなくローカルディスクも使ってデータをキャッシュできるようにする。

NFSv4の設定

手元のホストでは、NFSv4サーバをセットアップしておく*2。 /etc/exportsでは127.0.0.1からのmountを許可しておく。


次にEC2でVMを立ち上る。ディスクイメージには "instance-store" 版のAmazon Linuxを使う。"ebs" 版ではキャッシュデータを置くローカルストレージが使えない*3


VMが立ち上がったら、sshでログインする。このときに、以下のように2049番ポートを remote port forwarding しておく:

ssh -R2049:127.0.0.1:2049 ec2-user@ec2-host.compute-1.amazonaws.com


ログインしたら、nfs-utilsをインストールする。また、後述するcachefilesdをコンパイルするために、gccもインストールしておく:

yum install nfs-utils gcc


NFSv4サーバとuidを合わせたユーザーを作っておくと、ユーザ管理が簡単になるので便利だろう。

# uidはサーバ側に合わせる
useradd -m -u 502 -d /home/viver viver


併せてidmapdの設定が必要になるかもしれない。/etc/idmapd.conf で、"Domain" をサーバの idmapd.conf と同じにしておく:

[General]
# The following should be set to the local NFSv4 domain name
# The default is the host's DNS domain name.
#Domain = local.domain.edu
Domain = localdomain


rpc_pipefsをmountし、idmapdを起動する。

mount -t rpc_pipefs rpc_pipefs /var/lib/nfs/rpc_pipefs
service rpcidmapd restart


ここで試しにmountしてみる。mountできなければ、何かが間違っている。NFSv4サーバ/クライアント双方のdmesgを見るなどして修正する。

mount -t nfs4 127.0.0.1:/ /mnt
umount /mnt

キャッシュの設定

次にFS-Cacheの設定をする。
FS-Cacheを有効にするには、cachefilesdというパッケージをインストールする必要がある。現時点ではyumにバイナリパッケージが用意されていないので、Webサイトからダウンロードして自前でコンパイルする。実は./configureさえ無いので、軽くソースを眺める必要があるかもしれない。

wget http://people.redhat.com/~dhowells/fscache/cachefilesd-0.10.tar.bz2
tar jxvf cachefilesd-0.10.tar.bz2
cd cachefilesd-0.10
make
sudo cp cachefilesd /usr/sbin/
sudo cp cachefilesd.conf /etc/


/etc/cachefilesd.conf を編集する。最低限、キャッシュを保存するディレクトリと、SELinuxの設定を書き換える。
キャッシュの保存先はローカルディスク(/media/ephemeral0)とする。SELinuxの設定はコメントアウトする。

#dir /var/fscache
dir /media/ephemeral0/fscache
tag mycache
brun 10%
bcull 7%
bstop 3%
frun 10%
fcull 7%
fstop 3%

# Assuming you're using SELinux with the default security policy included in
# this package
#secctx system_u:system_r:cachefiles_kernel_t:s0


キャッシュを保存するファイルシステムはuser_xattrが有効になっている必要があるらしいので、remountして有効にしておく。

mount -o remount,user_xattr /media/ephemeral0
mkdir /media/ephemeral0/fscache
/usr/sbin/cachefilesd


最後に、キャッシュを有効にしてNFSv4をmountする。キャッシュを有効にするには -o fsc オプションを付ける:

mount -t nfs4 -o fsc 127.0.0.1:/ /mnt
mount --bind /mnt/home/viver /home/viver


いくつかファイルを読み込み、キャッシュの保存先ディレクトリを見てみると、サイズが膨らんでいた。きっとキャッシュが効いているのだろう。

$ du -sh /media/ephemeral0/fscache
261M	/media/ephemeral0/fscache/

*1:それにしてもInTriggerは便利そうだなぁ…などと。[http://supercell.osuosl.org/:title=Supercell] にも期待。

*2:NFSv4の設定はNFSv3と似ているようで全然違うのでややこしい。

*3:-b "/dev/sdb=ephemeral0" などとオプションを付けると、ebs版でもローカルディスクを使えるとのこと。 http://twitter.com/j3tm0t0/status/36354896789901312

共有ホームディレクトリ環境の管理方法

MacPorts や apt などのパッケージ管理システムでインストールできないアプリケーションやライブラリ、自分で書いたツールなどを、ホームディレクトリにインストールしたいことは良くある。
ホームディレクトリならroot権限が要らないし、rootを持っている場合でも思わぬ操作ミスや設定ミスのリスクを抑えられる利点がある。アンインストールもしやすい。gem や easy_install などのスクリプト言語の管理システムが、OS全体のパッケージ管理システムと競合してしまう問題も回避できる。


このようにホームディレクトリにアプリケーションをインストールするときに、複数のバージョンを同時にインストールしたいことがある。また、異種のOSやCPUアーキテクチャのマシンでホームディレクトリを共有したかったりする*1 *2
以上のような要求があるときに、ホームディレクトリ環境をどうのように構築し、PATH や LD_LIBRARY_PATH などの環境変数をどのように設定すれば良いか? ./configureするときに、いちいち -I や -L を指定するのも面倒くさい。

なかなか一筋縄では解決しない問題だが、ここでは私の管理方法を紹介する。

ホームディレクトリの構造

ホームディレクトリに ~/arch というディレクトリを作り、この中に アーキテクチャ固有のディレクトリ特定のホスト固有のディレクトリ を作る*3

~/arch/
~/arch/linux-x86_64/   # アーキテクチャ固有ディレクトリ
~/arch/freebsd-i386/   # アーキテクチャ固有ディレクトリ
~/arch/fcore/          # ホスト固有ディレクトリ
~/arch/ucore/          # ホスト固有ディレクトリ
~/arch/net/            # 全ホスト共有ディレクトリ(スクリプトなどを置く)

アーキテクチャ固有ディレクトリやホスト固有ディレクトリの構造は共通で、以下のようになっている:

~/arch/linux-x86_64/
~/arch/linux-x86_64/app/
~/arch/linux-x86_64/app/bin/       # ここに PATH を通す
~/arch/linux-x86_64/app/lib/       # ここに LD_LIBRARY_PATH を通す
~/arch/linux-x86_64/app/relink     # bin/ や lib/ にシンボリックリンクを作成するツール
~/arch/linux-x86_64/path.conf      # path.conf。設定ファイル
~/arch/linux-x86_64/ruby-1.8       # アプリケーションなど。ディレクトリの命名規則は:
~/arch/linux-x86_64/ruby-1.9       #    <パッケージ名>-<メジャーバージョン>.<マイナーバージョン>
~/arch/linux-x86_64/msgpack-0.5    # マイナーバージョンが変わったら上書きせずに別のディレクトリに分ける
~/arch/linux-x86_64/msgpack-dev    # 自分で開発中のライブラリは -dev にしておくなど
~/arch/linux-x86_64/.bashrc        # 固有bashrc


アプリケーションをインストールしたら、app/bin や app/lib にシンボリックリンクを張る。この作業は relink スクリプト(後述)で自動化する。
path.conf ファイルは relinkスクリプトの設定ファイルである。

使い分け

基本的にはアーキテクチャ固有ディレクトリにインストールしていく。


ディストリビューション固有の機能を使っていたり、特定の職場やノートPCなどの環境だけで使うツールは、ホスト固有ディレクトリに置く。
私の場合は、例えば Wi-Fiの設定ツールや、colorgcc などをホスト固有ディレクトリにインストールしている。サーバ用マシンには、システム運用のために開発用とは別バージョンの ruby をインストールしてあったりする。


特定のアーキテクチャやホストに依存しないスクリプトやドキュメントは、全ホスト共通ディレクトリに置く。例えば pdumpfsmplexkansit などは全ホスト共通ディレクトリに置いている。

その他に、コマンドプロンプト($PS1)はホスト固有の .bashrc で設定している。

path.conf

path.conf は、.bashrc と relink スクリプトの2つから読み込まれる設定ファイルである。インストールしたアプリケーションのディレクトリ名を書いていく。
内容は↓このようになる:*4

#!/usr/bin/env bash

# path_bin はコマンド用
path_bin ruby-1.8
path_bin ruby-1.9 19
path_bin gtest-1.5

# path_lib はライブラリやヘッダ用
path_lib libev-3.9
path_lib luxio-0.2
path_lib gtest-1.5
#path_lib mpio-0.3
#path_lib msgpack-0.5

# 自分で開発している場合はバージョンを -dev にしてみた
path_bin kumofs-dev
path_bin msgpack-idl-dev
path_lib mpio-dev
path_lib msgpack-dev
path_lib msgpack-rpc-dev


このファイルは、.bashrc と relink スクリプトのどちらから読み込まれたかによって挙動が異なる:

.bashrcから読み込まれた場合
$C_INCLUDE_PATH や $LIBRARY_PATH を設定する
relinkスクリプトから読み込まれた場合
app/bin/ や app/lib/ にシンボリックリンクを作成する

このように挙動を変える仕組みは、.bashrc と relink スクリプトで function bin_path や function lib_path の定義を変えることで実現している。

C_INCLUDE_PATH や LIBRARY_PATH は gcc で使われる環境変数で、これを設定しておくと毎回インクルードパス(-I)やライブラリパス(-L)を指定しなくて済む。


path_bin には、第二引数に suffix を指定することができる。例えば path_bin ruby-1.9 19 と書くと、ruby-1.9/bin/* 以下のコマンドに 19 という接尾辞が付く。
具体的には、例えば rails とコマンドを叩いたときは ruby-1.8/bin/rails が実行され、rails19 と叩いたときは ruby-1.9/bin/rails が実行されるようになる。

~/.bashrcの実装

~/.bashrc の主な仕事は、アーキテクチャやホスト名を判別することと、関数や変数を定義すること、そしてアーキテクチャやホスト固有の .bashrc を読み込むことである。

アーキテクチャの判別

アーキテクチャやホスト名を判別して、$archdir$hostdir 変数を定義する。
具体的には、$archdir は ~/arch/darwin-i386 や ~/arch/linux-x86_64 あるいは ~/arch/sunos-i86pc のようになる。

いろいろな方法を試したが、次の定義がポータブルで良さそうである:

kernel=$(uname -s | tr "[A-Z]" "[a-z]")

if [ $(uname -p | wc -c) -le $(uname -m | wc -c) ]; then
    processor=$(uname -p)
else
    processor=$(uname -m)
fi

export arch=$kernel-$processor
export host=$(hostname -s)
export archdir="$HOME/arch/$arch"
export hostdir="$HOME/arch/$host"
export netdir="$HOME/arch/net"
関数や変数の定義

アーキテクチャを定義したら、それにあわせて各種変数や path.conf 用の関数を定義する:

function dev_path() {
    C_INCLUDE_PATH="$1/include:$C_INCLUDE_PATH"
    CPLUS_INCLUDE_PATH="$1/include:$CPLUS_INCLUDE_PATH"
    #OBJC_INCLUDE_PATH="$1/include:$OBJC_INCLUDE_PATH"
    LIBRARY_PATH="$1/lib:$LIBRARY_PATH"
}

function net_bin_path()   { PATH="$netdir/$1/bin:$PATH";   }
function net_sbin_path()  { PATH="$netdir/$1/sbin:$PATH";  }
function net_dev_path()   { dev_path "$netdir/$1";         }
function arch_bin_path()  { PATH="$archdir/$1/bin:$PATH";  }
function arch_sbin_path() { PATH="$archdir/$1/sbin:$PATH"; }
function arch_dev_path()  { dev_path "$archdir/$1";        }
function host_bin_path()  { PATH="$hostdir/$1/bin:$PATH";  }
function host_sbin_path() { PATH="$hostdir/$1/sbin:$PATH"; }
function host_dev_path()  { dev_path "$hostdir/$1";        }

# path.conf用関数
function path_bin() {
    echo -n ""  # 何もしない
}
function path_lib() {
    arch_dev_path "$1"
}

# PATH と LD_LIBRARY_PATH
export PATH="$netdir/app/bin:$archdir/app/bin:$hostdir/app/bin:$PATH"
export LD_LIBRARY_PATH="$netdir/app/lib:$archdir/app/lib:$hostdir/app/lib"
path.confと固有bashrcの読み込み

最後に path.conf とアーキテクチャやホスト固有の .bashrc を読み込む:

if [ -f $netdir/path.conf ];then
    source $netdir/path.conf
fi
if [ -f $netdir/.bashrc ];then
    source $netdir/.bashrc
fi

if [ -f $archdir/path.conf ];then
    source $archdir/path.conf
fi
if [ -f $archdir/.bashrc ];then
    source $archdir/.bashrc
fi

if [ -f $hostdir/path.conf ];then
    source $hostdir/path.conf
fi
if [ -f $hostdir/.bashrc ];then
    source $hostdir/.bashrc
fi

export PATH
export C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH
export LIBRARY_PATH

relinkスクリプトの実装

#!/usr/bin/env bash

function r {
  echo "$@"
  "$@"
}

function path_bin() {
  if [ -z "$1" ]; then
    echo "usage: path_bin() <app> [suffix]"
    exit 1
  fi
  name="$1"
  suffix="$2"

  for f in "$archdir/$name/bin/"*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/bin/`basename $f`$suffix"
    to="../../$name/bin/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done

  for f in $archdir/$name/sbin/*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/bin/`basename $f`$suffix"
    to="../../$name/sbin/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done
}

function path_lib() {
  if [ -z "$1" ]; then
    echo "usage: path_lib() <app>"
    exit 1
  fi
  name="$1"

  for f in $archdir/$name/lib/*; do
    if [ ! -f "$f" ]; then
      continue
    fi
    ln="$archdir/app/lib/`basename $f`$suffix"
    to="../../$name/lib/`basename $f`"
    rm -f $ln
    r ln -s "$to" "$ln"
  done
}

source "$archdir/path.conf"

ホスト固有のrelinkは、$archdir を $hostdir に変える。

新しいアプリケーションをインストールする具体的な手順

何かアプリケーションをインストールするときは、./configure --prefix= でインストール先のディレクトリを指定する:

# 同じアーキテクチャのホストで共有したい場合
$ ./configure --prefix=$archdir/msgpack-0.5
$ make -j4 install

# # このホストだけにインストールしたい場合
$ ./configure --prefix=$hostdir/msgpack-0.5
$ make -j4 install

その後で path.conf を書き換え、relink スクリプトを実行する:

# path.conf を書き換える
$ echo "path_lib msgpack-0.5" >> $archdir/path.conf  # ライブラリの場合
$ echo "path_bin msgpack-0.5" >> $archdir/path.conf  # コマンドの場合

# シンボリックを張る
$ $archdir/app/relink

# ライブラリをインストールした場合は、シェルを再起動する($C_INCLUDE_PATH や $LIBRARY_PATH を設定し直すため)

異なるバージョンをインストールする具体的な手順

基本的には新しいアプリケーションをインストールする場合と同じだが、path.conf の設定方法が異なる。

コマンドの場合は複数のバージョンを共存させることができる。この場合は、path_bin の第2引数に suffix を指定する:

path_bin ruby-1.8
path_bin ruby-1.9 19

共存させる必要が無い場合は、旧バージョンをコメントアウトしておけば良い:

#path_bin ruby-1.8
path_bin ruby-1.9

ライブラリの場合は、新バージョンが後ろに来るように書く。

path_lib msgpack-0.4
path_lib msgpack-0.5

参考文献

まとめ

ホームディレクトリにアプリケーションやライブラリをインストールする際の環境構築方法について紹介した。アーキテクチャやカーネルが異なるホストでホームディレクトリを共有したり、root権限を行使せずにアプリケーションをインストールできるようにするために役立つ。
基本はアーキテクチャやバージョンごとにインストールディレクトリを分け、シンボリックリンク環境変数で選択するというシンプルな構造だが、その管理タスクを path.conf で自動化している点がミソである。
大学の計算機環境が Mac/Linux/Windows の混成で、友人の間でノウハウを共有しながら管理方法を模索していったところ、このような方法に落ち着いた。今回 カーネル/VM Advent Calendar というイベントがあったので、ちょぃとまとめてみた(長いけど)。
実は問題点は色々ある。読者の皆様のノウハウも共有していただけると幸いである。

Future work

MANPATH も何とかしたい。
path_lib と path_bin を書き分けるのは面倒なので、自動判別して欲しい。
ホームディレクトリにインストールしたサーバアプリケーションも自動起動/自動再起動したいときがある。upstartを使えばできそうだが、D-Busの設定が必要になるかもしれない。
github にあげて欲しい。


Enjoy hacking!!

*1:特に様々なCPUアーキテクチャのマシンを所有している読者の皆様においては、身近な問題だと思われる!

*2:最近は仮想マシンに各種OSを簡単にインストールできるし、i386x86_64のライブラリはリンクできなかったりするので、マシンが1台しかなくても出番は多い。

*3:昔は「グループ固有ディレクトリ」もあり、任意のグループごとに便利スクリプトや各種設定ファイルを共有できるようにしていた。しかし自宅ネットワーク内にいくつもクラスタがあったりするわけではないので、今ではアーキテクチャ固有ディレクトリを使うことにした。一般に、数が増えると管理が大変になる。

*4:path.conf の先頭には #! を書いているが、直接実行することはなく、他のファイルから source で読み込んで使う。エディタで開いたときに確実にシンタックスハイライトされるので付けているだけ。

mplexでソースコードレベルメタプログラミング

Webアプリケーションを作るとき、HTMLを生成するテンプレートエンジンをよく使いますが、これはパラメータに応じて様々なコードを生成する自動生成ツールであると言えます。
mplexは、プログラムを生成するためのテンプレートエンジンです。


実は MessagePack-RPC for C++ の実装に使っています。似たような関数をたくさんオーバーロードするために活用しています。(そろそろ可変長templateを使いたいですねぇ)

昔はeRubyを使っていたのですが、HTML用のテンプレートエンジンはソースコードがあまりに読みにくくなるので自作しました。


mplexを使うと、普通のプログラムの中にRubyのコードを埋め込むことができます:

// クラスを4つ生成
%4.times do |i|
class Test[%i%] {
public:
    %if i % 2 == 0
    int even;    // iが偶数ならメンバ変数を宣言
    %end

    %i.times do |n|
    int member[%n%];    // 複数のメンバ変数を生成
    %end
};
%end

このコードから↓このようなコードが生成されます:


さらに、if文やイテレータには後置構文を使えます:

%4.times do |i|
class Test[%i%] {
public:
    // 後置構文でシンプルに書く
    int even;  %> if num % 2 == 0
    int member[%n%];  %|n| i.times
};
%end


マクロ(メソッド)を定義することもできます。

%# マクロ定義
%def genABC(ns)
namespace [%ns%] {
  % %w[A B C].each do |a|
  % yield a
  % end
}
%end

%# マクロ呼び出し
%genABC("my_package") do |a|
void func[%a%]();
%end


ソースコードにパラメータを埋め込むこともできます。
ソースコードと「context script」を渡すと、ソースコード内に記述した変数に値が埋め込まれます:

# 連想配列を返すcontext script
{
    "Get" => {
        "std::string"  => "key",
        "uint32_t"     => "flags",
    },

    "Put" => {
        "std::string" => "key",
        "std::string" => "value",
    },
}
// selfにcontext scriptが渡される
%self.each_pair do |name, args|
void [%name%]( [%args.map {|type,name| "#{type} #{name}" }.join(", ")%] );
%end

mplex は ./configure && make install もしくは rubygems でインストールできます:

$ gem install mplex
$ git clone https://github.com/frsyuki/mplex.git
$ cd mplex
$ ./bootstrap
$ ./configure && make && sudo make install


MessagePack-RPCのIDLでも、コード生成の部分で使おうとしているところです。


Enjoy Hacking!

MessagePack for PHP@Webホスティング

先日 phpのserializeを使うより高速でサイズもコンパクトに仕上げる「MessagePack」とPHP拡張 でも紹介されている MessagePack for PHP ですが、Webホスティングサービスにも標準で組み込まれ始めているようです:


MessagePackはオープンソースで開発が進んでいるプロジェクトです。そして各言語版はそれぞれの言語のエキスパートによって実装されるという形態になっています(自然にそうなったのですが^^;)。
PHP版はadvectさんによる実装です。


実はPHP版には以前に別の実装があり、advectさんから新しい実装が送られてきたときは 2,3回くらい提案された実装を全部リジェクトした という経緯があるのですが(笑)、最終的に取り込まれている現在の実装は、非常に高品質になっています。
性能的にはまだ酔狂にチューニングできる余地がありそうですが、テストケースが充実しており、機能的には安心して使える品質かと思います。


コードを読むと、どうやらセッションのシリアライザを置き換える機能も実装されているようです。
php.ini に session.serialize_handler = msgpack と書くと、セッションにオブジェクトを保存する際にMessagePackでシリアライズされるようになるようです。


ここで、MessagePackではクラスをシリアライズする場合は「配列」としてシリアライズするという規約があります。つまりメンバ変数の変数名は保存されないというわけです。

しかしPHP版の実装では、デフォルトで「連想配列」としてシリアライズされるようになっています。つまり変数名まで保存されます。これはPHPだけで使う場合には非常に便利なのですが、JavaRubyなど他の言語とインスタンスをやりとりしたい場合に問題になることがあります。
そういう場合には、php.ini に msgpack.php_only = 0 と書くと良いようです。

また、msgpack.error_display = 0 という設定項目もあります。


Enjoy MessagePack!

Kyoto Tycoon に MessagePack-RPC をプラグインして Java から使う

Tokyo Cabinet を始めとする Tokyo シリーズの作者として知られる平林幹雄さんですが、Tokyo シリーズに続く新製品として、Kyoto シリーズがリリースされています。
Kyoto Tycoon(以下KT)は、ネットワーク経由で使えるデータベースサーバで、Tokyo Tyrantの後継製品に当たります*1


KT は HTTP ベースのプロトコルで操作することができますが、別のプロトコルを追加することもできます。
実際に memcached プロトコルのプラグインが標準でバンドルされています。(memcachedプロトコルをKTにプラグインする


と言うわけで、KT を MessagePack-RPC で使えるようにするプラグインを書いてみました。github からダウンロードできます。


MessagePack-RPC を使うと、通信を非同期化したり、他の MessagePack-RPC サーバと同時に通信したりするプログラムを簡単に書けます。そして何より、高速です。

コンパイルしたライブラリを KT の -plsv オプションに指定すると、MessagePack-RPC でアクセスできるようになります:

$ ktserver -plsv /usr/local/libexec/libktmsgpack.dylib -plex 'port=18801'


この MessagePack-RPC をプラグインした KT を、Javaから利用してみます。
MessagePack-RPC の Java 版は、最近のバージョンアップで使い勝手が大きく向上しています:

import java.util.List;
import org.msgpack.rpc.Client;
import org.msgpack.rpc.loop.EventLoop;

public class SimpleExample {
  // RPCのインタフェースを宣言
  public static interface KyotoTycoonClient {
    void set(String key, String value);
    String get(String key);
    List<String> match_prefix(String prefix);
  }

  public static void main(String[] args) throws Exception {
    EventLoop loop = EventLoop.defaultEventLoop();
    Client cli = new Client("127.0.0.1", 18801, loop);
    KyotoCabinetClient kt = cli.proxy(KyotoCabinetClient.class);  // ココがポイント

    // setとget
    kt.set("k1", "v1");
    kt.set("k2", "v2");
    String v1 = kt.get("k1");
    String v2 = kt.get("k2");

    // プレフィックスで検索
    List<String> match = kt.match_prefix("k");

    System.out.println(v1);  #=> "v1"
    System.out.println(v2);  #=> "v2"
    System.out.println(match);   #=> ["k1", "k2"]

    cli.close();
    loop.shutdown();
  }
}


KyotoTycoonClient kt = cli.proxy(KyotoTycoonClient.class); の行がポイントです。
proxy() メソッドにインタフェースを渡すと、そのインタフェースが自動的に実装されたオブジェクト*2が返ってきます。このオブジェクトのメソッドを呼ぶと、リモートサーバの機能を呼び出せます。


この KyotoTycoonClient インタフェースは、kt-msgpack パッケージの中に同梱しています:


インタフェースの定義で返り値を Future にすると、非同期型の呼び出しになります。非同期呼び出しを使うと、遅延を隠蔽して処理時間を短縮することができます。立て続けに複数の呼び出しを行うときに効果的です。
↓このプログラムは、入力データを処理しながら KT から次々にデータを取り出し、最後に結果を表示するプログラムです。

import java.util.Map;
import java.util.HashMap;
import org.msgpack.rpc.Client;
import org.msgpack.rpc.loop.EventLoop;
import org.msgpack.rpc.Future;

public class Test {
  // RPCのインタフェースを宣言
  public static interface KyotoTycoonClient { 
    void set(String key, String value);
    String get(String key);

    // 戻り値をFuture<T>にして、メソッド名の末尾に "Async "を付ける
    Future<String> getAsync(String key);
  }

  public static Future<String> getUserInfo(KyotoTycoonClient kt, String user) {
    return kt.getAsync(user+"/info");
  }

  public static void main(String[] args) throws Exception {
    EventLoop loop = EventLoop.defaultEventLoop();
    Client cli = new Client("127.0.0.1", 18801, loop);
    KyotoTycoonClient kt = cli.proxy(KyotoTycoonClient.class);

    // とりあえずデータをセットしておく
    kt.set("viver/info", "http://d.hatena.ne.jp/viver");
    kt.set("frsyuki/info", "http://github.com/frsyuki");
    kt.set("muga/info", "http://github.com/muga");
    kt.set("msgpack/info", "http://github.com/msgpack");

    // 入力データ:
    String[] input = new String[] { "viver", "frsyuki", "muga", "msgpack" };

    // KTから非同期にデータを取り出していく...
    Map<String, Future<String>> map = new HashMap<String, Future<String>>();
    for(String user : input) {
      Future<String> data = getUserInfo(c, user);
      map.put(user, data);
    }

    // 通信はこの間にバックグラウンドで進行...

    // 最後にデータを出力
    StringBuilder sb = new StringBuilder();
    for(Map.Entry<String, Future<String>> pair : map.entrySet()) {
      sb.append(pair.getKey());
      sb.append(":  ");
      sb.append(pair.getValue().get());
      sb.append("\n");
    }
    // 出力結果:
    //   msgpack:  http://github.com/msgpack
    //   muga:  http://github.com/muga
    //   frsyuki:  http://github.com/frsyuki
    //   viver:  http://d.hatena.ne.jp/viver

    System.out.println(sb.toString());

    cli.close();
    loop.shutdown();
  }
}

*1:今ならKTの説明会が無償^^; [http://fallabs.com/mikio/tech/promenade.cgi?id=113:title=Kyoto Tycoon普及大作戦]

*2:実体はjava.lang.reflect.Proxy。動的コード生成によってリフレクションを排除する実装も進んでいます。

世界の「NoSQL」開発者が東京に集結 - NOSQL afternoon in Japan

NoSQLと呼ばれる新型の分散データストアの開発者が一堂に会するイベント NOSQL afternoon in Japan が、2010年11月1日、楽天タワーで開かれました。
海外からは「Cassandra」のサポートを行う Riptano や、「MongoDB」を開発する 10gen、「Couch DB」の Cloudant、「Hadoop」の Cloudera のエンジニアが登壇し、日本からは HibariOkuyamaROMA、そして kumofs の開発者が講演しました:


ContentsPresenters
HibariJoe NortonGemini Mobile Technologies
OkuyamaTakahiro Iwase
CassandraNate McCallRiptano
ROMAMuga NishizawaRakuten
Mongo DBRoger Bodamer10 gen
kumofsSadayuki Furuhashi
Couch DBAlan HoffmanCloudant
HBASE/HadoopTodd LipconCloudera

インターネット普及による爆発的な情報量の増加に伴い、テラバイト、ペタバイトというBig Dataを日常的に処理する時代を迎えています。このようなBig Dataを扱うためには、SQL対応データベースに限らない、Not Only SQL(以下、NOSQL)と呼ばれるような多様なタイプのデータベース、データ処理技術を駆使することが求められます。新しい技術トレンドであるNOSQLについては、日本において先進的な技術者が主催するHadoopやCassandraの勉強会にて活発に議論されていることと同様に、欧米でもNOSQL Meetupなどと呼ばれる会合において更に幅広い調査研究と情報交換がおこなわれています。


このたび、日本国内においてもNOSQLの分散KVSとなるHibariのオープンソースリリースを行ったGemini Mobile Technologiesと、グローバルなNOSQL論文読書会である「A nosql summer」の主催者であり、InfiniteGraphと Scalityから後援を得ているTim Angladeが幹事となり、概要以下により、「NOSQL afternoon in Japan」として、日米のNOSQL開発者が集う会議を開催することとなりました。


「NOSQL afternoon in Japan」開催のお知らせ


最初は100人くらいの小さなイベントが想定されていたのですが、ふたを開けてみれば400人もの聴衆を集めた一大イベントになりました。
Ustream.tvでも中継され、Twitterでも#nosqlgogoハッシュタグで議論が盛り上がりました。


そのTwitter上の議論はTogetterにまとめられており、プレゼンテーションの録画もUstream.tvで見ることができます:


当日の会場の様子についても Shudo's Notes(2010/11) で写真が公開されています。


各発表者のプレゼンテーション資料も公開されており、Brief reviews of "NOSQL AFTERNOON in JAPAN"NoSQL Afternoon in Japan の発表スライド にまとめられています。


NOSQL AFTERNOON IN JAPANの振り返り

Kumofs and the MessagePack project

私は日本勢として、分散Key-valueストア Kumofs と、高速メッセージングシステム MessagePack について発表してきました。



今回は日本国外のエンジニアの目の前で直接プレゼンテーションができるチャンスだったので、英語でプレゼンテーションしてみました。
英語でプレゼンするのは6月にシリコンバレーに行ってきて、Doug Cutting(現在Cloudera社)とディスカッションしてきてから以来、2回目です。
そのうえ今回は聴衆が何百人もいる会場で、かなり緊張していたのですが、最終的には何とかなりました^^;


Twitterで好感触なコメントもいただきました。

@rogerb roger bodamer
@rogerb
http://10gen.com
Interesting: msgpack (http://msgpack.org) and bson (http://bsonspec.org/) #nosqlgogo
@nemoton Naoki Nemoto
@nemoton
Roger さんが、ガンガンMessagePack をググって、ブックマークしつつ、tweetしまくっている件について。 #nosqlgogo
@nemoton Naoki Nemoto
@nemoton
真後ろに座っているので覗き見してましたw tweetは一回でしたが、興味津々で聞かれてましたね
@timanglade Tim Anglade
@timanglade
Getting a comprehensive #kumofs overview from @frsyuki at #nosqlgogo. Seems to me like it’s another NOSQL gem everybody should know about!
http://twitter.com/rogerb/status/29351647637
http://twitter.com/nemoton/status/29351602317 http://twitter.com/nemoton/status/29352003210
http://twitter.com/timanglade/status/29351687570


その後、「ROMA の Nishizawa-san が Cassandra を MessagePack で使えるようにするパッチを書いているよ」という話を Nate McCall(Riptano)にしたところ、「そういう話は早く Cassandra のチケットに上げてくれ」という反応だったので、今にわかにJava版の開発が活気づいているところです。


今回のプレゼンテーションでは、kumofs と MessagePack の概要について話しました。特に kumofs は他のNoSQLデータストアと比べた独自性について、MessagePack は主にRPCについて、他のシステムには無い優位性を解説することに重点に置いています。

もっと詳しく知りたい方は、以下の資料をどうぞ:


今は新しいシステムの開発にも着手し始めました。MessagePackのお陰で分散システムの開発は飛躍的に簡単になっています。
色々な意味でスピード重視で "MessagePack Platform" を展開していきたいと思っているところです。

高速メッセージングシステムMessagePack - 楽天テクノロジーカンファレンス2010

もはや先月のことですが、楽天テクノロジーカンファレンス2010で発表してきました。
MessagePackについて、かなり詳しく紹介しています。


Ustream.tvの録画はこちら

MessagePackの概要(7ページ目〜)

MessagePack は、It's like JSON, but very fast and small. のフレーズの通り、「JSONみたいに使えるけど速くて小さい」シリアライズ形式です。
JSONがテキスト形式のシリアライズフォーマットであるのに対し、MessagePackは様々な工夫を取り入れたバイナリ形式のシリアライズフォーマットです。


MessagePack-RPC は、MessagePackをプロトコルに使ったRPCシステムです。多言語で利用することができ、RailsのアプリとJavaのシステムを繋げる、JavaのシステムからC++のサーバを呼び出すといった、異種言語間の通信を可能にします。kumofsのI/Oシステムから由来した設計で、高い並列性を発揮します。また、コネクションプーリングや非同期呼び出しなどをサポートし、非常に高機能であると同時に、FutureやSessionなどのコンセプトに裏打ちされた使い勝手の良いAPIを提供します。

MessagePackプロジェクトの成り立ち(13ページ目〜)

新しい分散システムを作るには、プロトコルの実装が必要になります。特に V-FIELDCagraKastor のようなストレージシステムでは、通信のオーバーヘッドが性能に直接影響してくるため、プロトコルの実装も手を抜くわけにはいきません。しかし、プロトコルの実装は非常に面倒です。
通信の問題が解決されれば、スケールアウトの恩恵を受けられるネットワークアプリケーションを、簡単に実装できる様になります。並列性や耐障害性など、本質的なシステムの設計に集中することができます。


また、CPU負荷やメモリ容量がボトルネックになるシステムの場合、サーバはC++で書きたくなります。しかし、効率的に管理タスクを自動化するには、管理ツールはC++よりもRubyなどの言語で実装したくなります。


そこで、様々なシステムの実装に使えるような汎用性があり、かつ多言語で使えるRPCシステムが欲しくなります。これが MessagePack の開発を始めたきっかけです。
MessagePackは最初に kumofs に実装し、後にこれを切り出してオープンソース化しました。

詳解MessagePack-RPC(19ページ目〜)

MessagePack-RPCは、Request, Response, Notify の3種類のメッセージだけからなる、シンプルなプロトコルです。
Request と Response にはメッセージIDが含まれており、これを対応づけることで、クライアント側でRPC要求とRPC応答を紐付けます。まずこれによって Parallel Pipeliningという並列性を高める機能を実現します。

I/Oアーキテクチャ(26ページ目〜)

I/Oアーキテクチャは、イベント駆動型になっています。サーバ側の実装をイベント駆動I/Oで実装するのはよくある構成ですが、MessagePack-RPCではクライアント側でもイベント駆動I/Oを採用しています。


まず、複数のクライアントインスタンスを一つのイベントループに載せることが可能です。これによって、スレッドプログラミングをすることなく、複数のサーバと同時に通信するアプリケーションを書くことができます。
また、サーバとクライアントの実装アーキテクチャが同じであることから、一つのイベントループ上にサーバとクライアントの両方を載せることが可能です。これによって、サーバとクライアントの両方の性質を持つアプリケーション、すなわちサーバ同士が通信するクラスタアプリケーションを簡単に実装することができます。


さらに、イベントループには、MessagePack-RPC以外のプロトコルを扱うイベントハンドラや、タイマーやシグナルのハンドラも載せることができます。これにより、HTTPとMessagePack-RPCの両方に対応したアプリケーションや、kumo-gatewayのようなプロトコルを変換するゲートウェイサーバを簡単に実装することができます。
先日リリースしたMessagePack-RPC Java版では、イベントループに JBoss XNIO を使っています。

FutureとSession(35ページ目〜)

イベント駆動型I/Oを駆使するプログラムは複雑になりがちですが、MessagePack-RPCでは、これをFutureという概念で扱うことによって隠蔽します。
また、Sessionは、コネクションの概念を隠蔽します。コネクションの確立、接続維持、再接続は、MessagePack-RPCのライブラリ内で自動的に行うため、面倒なコードを一切記述することなく、コネクションプーリングなどの高度な機能を利用することが可能です。

MessagePackの未来(64ページ目〜)

MessagePackは、Sedue Search CloudAmebaなうなどで既に利用されています。
コミッタには海外の開発者も含まれており、開発は非常に活発です。


「学生」「余暇の時間に」といったオープンソースの "瞬発力" や "斬新さ" を取り入れるには、MessagePackプロジェクトが「魅力的」である必要があるだろうと考えています。
また一方で、「システムの開発に」「製品に」といった "持続力" や "堅牢さ" を取り入れるためには、「有用」である必要があるだろうと思います。
この両方を実現していくには、コミッタの力が欠かせません。ぜひ開発者にエールを送っていただければ幸いです。
# 開発者はTwitterMessagePack OR msgpack と検索すると見つかります^^;


The MessagePack Project


MessagePackについてもっと詳しく知りたい方は、gumiStudy#7 でお会いしましょう^^;