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/
共有ホームディレクトリ環境の管理方法
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 をインストールしてあったりする。
特定のアーキテクチャやホストに依存しないスクリプトやドキュメントは、全ホスト共通ディレクトリに置く。例えば pdumpfs や mplex、kansit などは全ホスト共通ディレクトリに置いている。
その他に、コマンドプロンプト($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
参考文献
- ソースから自前ビルドしたソフトウエアの効率的な管理方法 - (ひ)メモ
- bashrc/vimrc再び。 - ここにタイトルを入力|
まとめ
ホームディレクトリにアプリケーションやライブラリをインストールする際の環境構築方法について紹介した。アーキテクチャやカーネルが異なるホストでホームディレクトリを共有したり、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を簡単にインストールできるし、i386とx86_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だけで使う場合には非常に便利なのですが、JavaやRubyなど他の言語とインスタンスをやりとりしたい場合に問題になることがあります。
そういう場合には、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(); } }
世界の「NoSQL」開発者が東京に集結 - NOSQL afternoon in Japan
NoSQLと呼ばれる新型の分散データストアの開発者が一堂に会するイベント NOSQL afternoon in Japan が、2010年11月1日、楽天タワーで開かれました。
海外からは「Cassandra」のサポートを行う Riptano や、「MongoDB」を開発する 10gen、「Couch DB」の Cloudant、「Hadoop」の Cloudera のエンジニアが登壇し、日本からは Hibari、Okuyama、ROMA、そして kumofs の開発者が講演しました:
Contents | Presenters | |
---|---|---|
Hibari | Joe Norton | Gemini Mobile Technologies |
Okuyama | Takahiro Iwase | |
Cassandra | Nate McCall | Riptano |
ROMA | Muga Nishizawa | Rakuten |
Mongo DB | Roger Bodamer | 10 gen |
kumofs | Sadayuki Furuhashi | |
Couch DB | Alan Hoffman | Cloudant |
HBASE/Hadoop | Todd Lipcon | Cloudera |
インターネット普及による爆発的な情報量の増加に伴い、テラバイト、ペタバイトという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開発者が集う会議を開催することとなりました。
最初は100人くらいの小さなイベントが想定されていたのですが、ふたを開けてみれば400人もの聴衆を集めた一大イベントになりました。
Ustream.tvでも中継され、Twitterでも#nosqlgogoハッシュタグで議論が盛り上がりました。
そのTwitter上の議論はTogetterにまとめられており、プレゼンテーションの録画もUstream.tvで見ることができます:
- NOSQL aftern in Japan - Togetter
- Ustream録画プレゼンテーション Part 1(Hibari、Okuyama、Cassandra)
- Ustream録画プレゼンテーション Part 2(Mongo DB、kumofs)
- Ustream録画プレゼンテーション Part 3(Couch DB、HBASE/Hadoop)
当日の会場の様子についても Shudo's Notes(2010/11) で写真が公開されています。
各発表者のプレゼンテーション資料も公開されており、Brief reviews of "NOSQL AFTERNOON in JAPAN" や NoSQL Afternoon in Japan の発表スライド にまとめられています。
Kumofs and the MessagePack project
私は日本勢として、分散Key-valueストア Kumofs と、高速メッセージングシステム MessagePack について発表してきました。
今回は日本国外のエンジニアの目の前で直接プレゼンテーションができるチャンスだったので、英語でプレゼンテーションしてみました。
英語でプレゼンするのは6月にシリコンバレーに行ってきて、Doug Cutting(現在Cloudera社)とディスカッションしてきてから以来、2回目です。
そのうえ今回は聴衆が何百人もいる会場で、かなり緊張していたのですが、最終的には何とかなりました^^;
Twitterで好感触なコメントもいただきました。
http://twitter.com/rogerb/status/29351647637
roger bodamer
@rogerb
http://10gen.comInteresting: msgpack (http://msgpack.org) and bson (http://bsonspec.org/) #nosqlgogo Naoki Nemoto
@nemotonRoger さんが、ガンガンMessagePack をググって、ブックマークしつつ、tweetしまくっている件について。 #nosqlgogo Naoki Nemoto
@nemoton真後ろに座っているので覗き見してましたw tweetは一回でしたが、興味津々で聞かれてましたね Tim Anglade
@timangladeGetting a comprehensive #kumofs overview from @frsyuki at #nosqlgogo. Seems to me like it’s another NOSQL gem everybody should know about!
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について、他のシステムには無い優位性を解説することに重点に置いています。
もっと詳しく知りたい方は、以下の資料をどうぞ:
- kumofs
- MessagePack
今は新しいシステムの開発にも着手し始めました。MessagePackのお陰で分散システムの開発は飛躍的に簡単になっています。
色々な意味でスピード重視で "MessagePack Platform" を展開していきたいと思っているところです。
高速メッセージングシステムMessagePack - 楽天テクノロジーカンファレンス2010
もはや先月のことですが、楽天テクノロジーカンファレンス2010で発表してきました。
MessagePackについて、かなり詳しく紹介しています。
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-FIELD や Cagra、Kastor のようなストレージシステムでは、通信のオーバーヘッドが性能に直接影響してくるため、プロトコルの実装も手を抜くわけにはいきません。しかし、プロトコルの実装は非常に面倒です。
通信の問題が解決されれば、スケールアウトの恩恵を受けられるネットワークアプリケーションを、簡単に実装できる様になります。並列性や耐障害性など、本質的なシステムの設計に集中することができます。
また、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 CloudやAmebaなうなどで既に利用されています。
コミッタには海外の開発者も含まれており、開発は非常に活発です。
「学生」「余暇の時間に」といったオープンソースの "瞬発力" や "斬新さ" を取り入れるには、MessagePackプロジェクトが「魅力的」である必要があるだろうと考えています。
また一方で、「システムの開発に」「製品に」といった "持続力" や "堅牢さ" を取り入れるためには、「有用」である必要があるだろうと思います。
この両方を実現していくには、コミッタの力が欠かせません。ぜひ開発者にエールを送っていただければ幸いです。
# 開発者はTwitterで MessagePack OR msgpack と検索すると見つかります^^;
MessagePackについてもっと詳しく知りたい方は、gumiStudy#7 でお会いしましょう^^;