分散Key/Valueストア、Kaiを使ってみよう!

第2回Kai の基礎 ─Kaiのインストールと基本的な使い方

今回からKaiの使い方を説明させて頂きます、株式会社ゼロの幾田と申します。Kaiの良さを少しでも皆様にお伝えする事ができれば幸いです。

第2回目となる今回は、Kaiのインストール方法や、基本的な使い方を説明します。なお、本連載が対象とするKaiのバージョンは0.4、Erlang のバージョンはR13Bです。

Erlang のインストール

Kaiは、Erlang言語で作成されている都合上、Erlang VM上で動作します。そのため、Kaiをインストールする前に、Erlangの環境を構築する必要があります。

Erlangは古くから存在する枯れた言語であるため、多くのプラットフォームでパッケージが提供されており、比較的簡単に環境を準備する事ができます。

プラットフォームとパッケージコマンドの例は、次のとおりです。

プラットフォームパッケージコマンド例
Fedorayum install erlang
Ubuntu, Debianapt-get install erlang
FreeBSDportinstall erlang
Mac OSXport install erlang

また、Erlangの公式サイトではWindows向けにインストーラ付きのバイナリも提供されています。

ここでは、公式サイトからソースコードをダウンロードし、CentOS 5.3上でコンパイルする方法を説明します。

なお、執筆時のErlangの最新バージョンはR13Bであるため、本連載ではR13Bを用いて説明します。しかし、Kaiの推奨バージョンはR12B以上ですので、ご利用のプラットフォームが提供するErlangパッケージのバージョンがR12B以上であれば、そちらをご利用されても差し支えありません。

もし、パッケージからErlang をインストールするのであれば、インストールの説明をスキップし、Erlang VMの起動まで進んでください。

Erlangのソースコードは、Erlangの公式サイトから取得できます。

ダウンロードページからR13Bのソースコード(53.1 MByte)をダウンロードしてください。

Erlang のコンパイルは、一般的なアプリケーションと同じく、configure、make、make installで行います。

CentOS 5.3を最小構成でインストールした環境では、configureの前に gcc、gcc-c++、make、perl、ncurses-devel、openssl-devel、unixODBC-develをyumでインストールしておく必要があります。

なお、jinterfaceは、configureの引数で無効にしてありますが、有効にする場合は、JDKを事前にインストールしておいて下さい。

また、configure実行時にwxWidgetsがインストールされていない旨のWARNINGメッセージが表示されます。wxErlangのご利用を予定されている方は、wxWidgetsを事前にインストールしておいてください。

$ wget http://www.erlang.org/download/otp_src_R13B.tar.gz
$ tar -zxvf otp_src_R13B.tar.gz
$ cd otp_src_R13B
$ ./configure --enable-threads --enable-kernel-poll --enable-smp-support --enable-hipe --disable-jinterface --prefix=/usr/local/erlang
$ make
$ sudo make install

無事インストールが終わるとerlコマンドでErlang VMが起動し、Erlangの式を受け付けるコンソールに入れます。

$ /usr/local/erlang/bin/erl
Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.1  (abort with ^G)
1> q().   <- 終了コマンド
ok

これ以降は、"/usr/local/erlang/bin" にパスが通っている事を前提に説明を行います。

以上で、Erlangの環境構築が終わりましたので、これでKaiのインストールを行う事ができます。

Kaiのインストール

本連載が対象とするKaiのバージョンは、執筆時の最新リリースが0.4です。そのため本稿では、0.4のソースコードをコンパイルする方法を説明します。

Kaiのソースコードは、SourceForgeから取得できます。

KaiのプロジェクトページのタブメニューからDownload > Browse All Packagesと辿り、kai-0.4.0.tar.gzをダウンロードします。もしくは、KaiのSubversionのリポジトリからsvnコマンドを用いて取得します。

現在のところ、KaiではGNU Autotoolsを使用していないため、コンパイル時にconfigureを使用しません。

また、KaiのMakefileは、GNU makeに依存した書き方をしているため、FreeBSDでコンパイルする場合は、makeではなくgmakeを使用してください。

$ svn co https://kai.svn.sourceforge.net/svnroot/kai/tags/0.4.0/ kai-0.4.0
$ cd kai-0.4.0
$ make   <- FreeBSD であれば gmake

Makefileのターゲットとしてinstallは定義されていませんので、コンパイルしたディレクトリがインストールしたディレクトリとなります。

また、Makefileのターゲットとしてtestが定義されているのですが、開発者向けの内容であるため、本連載では説明しません。もし、ご興味がおありでしたら、ErlangのR12B から付属するCommon Testという標準モジュールを調べてみてください。

以上で、Kaiのインストールは終わりです。

スタンドアローン(単一ノード)で試す

本連載の第1回で述べたように、スケーラビリティやアベイラビリティ、信頼性などのKaiの利点は、複数ノードでクラスタを構築してこそ発揮されるのですが、説明を簡略化する為に、スタンドアローン(単一ノード)で説明します。

スタンドアローンで起動したKaiはmemcachedと似た動作をします。

次のコマンドを実行すると、Kaiが起動します。

なお、コマンドは、必ずKaiをコンパイルしたディレクトリで実行してください。また、TCPポート11211をListenするので、memcachedが既に起動している場合は、予め停止しておいてください。

$ erl -pa ebin -config kai -kai n 1 -kai r 1 -kai w 1 -eval 'application:start(kai).'
Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.1  (abort with ^G)
2009-05-20 21:58:29.439952 [info] () ./kai_hash.erl:183: {update,
                                                                  [{{{192,168,1,2},11011},
                                                                    [{number_of_virtual_nodes,
                                                                      128}]}],
                                                                  []}
1>

この状態で、TCPポート11211をListenしています。Erlang VMのコンソールを終了するとKaiも終了してしまうので気をつけてください。

ここで利用したerlコマンドの引数の意味は, 次の通りです。

引数説明
-paErlang VMが参照するオブジェクトコードのパスを指定できます。
Kaiのコンパイル済みコードファイルは、"./ebin" 配下に存在する為 "ebin" を指定します。
-config設定ファイル名を指定できます。
設定ファイルには、アプリケーションが参照する設定が記述されています。 ここでは、"./kai.config" を使用する為 "kai" を指定します。MemcachedプロトコルをTCPポート11211でListenする設定などが記述されています。kai.configの設定項目については、本連載の第3回で説明します。
-kaiKaiが参照する設定を指定できます。
ここで指定された値は、-configで指定されて読み込まれた設定ファイルの設定値を上書きするため、一時的な設定値を試す際などに使用できます。
必ず "-kai name value" の形式で指定します
n r w本連載第1回で説明のあったQuorumベースプロトコルの値を設定できます。
ここでは、スタンドアローンで動作させる為に、N:R:W = 1:1:1 を指定します。Quorumに設定する値は、本連載の第3回で説明します。
-evalErlang VM起動直後に評価する式を指定できます。
Kaiは、Erlang VMのコンソール上で次の式を評価する事で起動します。
"application:start(kai)."

では、動作確認のため、データ(Key/Value のペア)を保存後に取得してみます。

Telnetを使用する接続する例は、本連載の第1回を参照いただくとして、ここではPerlのCache::Memcachedを使用して説明します。

次のようなPerl Scriptを用意し、kai_test.plという名前で保存してください。

リスト1 kai_test.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More tests => 3;
use Cache::Memcached;

my $mem = Cache::Memcached->new({
    servers => ['127.0.0.1:11211'],
});

$mem->set(foo => 'bar', 0,);
is $mem->get('foo'), 'bar';

$mem->delete('foo');
ok !$mem->get('foo');

my $nested_value = {
    hash_ref  => {qw(a b c d)},
    array_ref => [1..5],
};

$mem->set(baz => $nested_value, 0);
is_deeply $mem->get('baz'), $nested_value;

ソースコードをご覧いただければ分かるとおり、memcached専用のクライアント経由でデータを保存・取得・削除できるため、既にmemcachedをご利用されている方であれば、Perl以外の他の言語であっても、容易にクライアントサイドを実装できるのではないでしょうか。

ただしKaiでは、addやreplaceをサポートしていない点と、期限(expiration time)として必ず0を指定する必要がある点を注意してください。

なお、将来的には、getsやcasに対応する予定です。

では、実行してみましょう。

$ /path/to/kai_test.pl
1..3
ok 1
ok 2
ok 3

このような実行結果となれば、memcachedプロトコルを用いたデータの保存・取得・削除が正常に行えています。

保存データを永続化する

Kaiでは、データをディスク上に保存する事で永続化する事もできます。

これにより、一度Kaiのクラスタ全体を停止したとしても、再起動した際に、停止前に保存したデータを再び利用する事ができます。

ただし、後述のようにクラスタ構成によってデータを複製することにより、データの保存先にメモリを指定しても、クラスタ全体としてはデータが永続化されています。

基本的にKaiで保存先にディスクを指定するのは、念には念を入れたい場合や、何らかの事情でクラスタ全体を停止したい場合となります。

では、まず始めに kai.config を元に、次のような設定ファイルを作成し、kai_dets.configという名前で保存してください。

リスト2 kai_dets.config
[{kai, [
    {rpc_port, 11011},
    {rpc_max_processes, 30},
    {memcache_port, 11211},
    {memcache_max_processes, 10},
    {max_connections, 32},
    {n, 1},
    {r, 1},
    {w, 1},
    {number_of_buckets, 1024},
    {number_of_virtual_nodes, 128},
    {store, dets},
    {dets_dir, "/path/to/kai-0.4.0/data"},
    {number_of_tables, 256}
]}].

kai_dets.configが、元となるkai.configと異なる点は次の通りです。

設定名変更前の値変更後の値
storeetsdets
dets_dir "/path/to/kai-0.4.0/data"

Erlangには、Erlang VM上のプロセス間でデータを共有する手段として、etsとdetsというデータストレージがあり、それぞれ、etsはメモリ上、detsはディスク上にデータを保存します。

Kaiは、内部のデータストレージとしてetsとdetsを採用しており、環境変数storeで、どちらのデータストレージを使うのかをノード毎に指定する事ができます。

また、環境変数storeにdetsを指定した場合、必ず環境変数dets_dirでKaiにディスク上のデータの保存先を指定する必要があります。

次に、dets_dirで指定した保存先のディレクトリを作成します。

$ mkdir -p /path/to/kai-0.4.0/data

以上で、データを永続化する準備が整いましたので、Kaiを起動します。

$ erl -pa ebin -config kai_dets -eval 'application:start(kai).'
Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.1  (abort with ^G)
2009-05-24 01:46:49.832265 [info] (<0.43.0>) ./kai_hash.erl:183: {update,
                                                                  [{{{192,168,1,2},11011},
                                                                    [{number_of_virtual_nodes,
                                                                      128}]}],
                                                                  []}
1>

筆者がMac OSX 10.5上でこれを試したところ、EMFILEエラーが発生しました。その場合、プロセスが開けるファイル数の上限に達しているので、設定ファイル内の環境変数number_of_tablesを 256から128などに変更して回避してください。

それでは、本当に保存データが永続化されているか試してみましょう。

まず始めに、起動したばかりのKaiにデータを保存します。次のようなPerl Scriptを用意し、kai_test_dets.plという名前で保存してください。

リスト3 kai_test_dets.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More tests => 1;
use Cache::Memcached;

my $mem = Cache::Memcached->new({
    servers => ['127.0.0.1:11211'],
});

$mem->set(foo => 'bar', 0,);
is $mem->get('foo'), 'bar';

用意したPerl Scriptを実行し、データをKaiに保存してください。

$ /path/to/kai_test_dets.pl
1..1
ok 1

次に、Kaiを停止してから再起動します。

1> q().
ok
$ erl -pa ebin -config kai_dets -eval 'application:start(kai).'

最後に、再起動したKaiから停止前に保存したデータを取得できる事を確認します。

次のようなPerl Scriptを用意し、kai_test_dets_get.plという名前で保存してください。

リスト4 kai_test_dets_get.pl
#!/usr/bin/env perl

use strict;
use warnings;

use Test::More tests => 1;
use Cache::Memcached;

my $mem = Cache::Memcached->new({
    servers => ['127.0.0.1:11211'],
});

is $mem->get('foo'), 'bar';

用意したPerl Scriptを実行し、データを取得してください。

$ /path/to/kai_test_dets_get.pl
1..1
ok 1

このような実行結果となれば、停止前に保存したデータの取得が正常に行えています。

Erlang VMのコンソールをDetacheする

今のままでは、Erlang VMのコンソールを終了するとKaiも終了してしまい、少々使い難いため、Erlang VMのコンソールをDetacheしてバックグラウンドで動作させる方法を説明します。

erlコマンドには、-sname、-detached、-remsh という引数があり、これらを使用してErlang VMの操作を行います。

それぞれの引数の意味は次のとおりです。

引数説明
-snameErlangノードの名前を指定できます。名前を付ける事で、DetacheしてしまったErlang VMのコンソールを操作できます。
-detachedErlang VM起動後、すぐにコンソールをDetacheします。必ず-snameと共に使用する必要があります。
-remsh操作する対象のErlangノードの名前を指定できます。-detachedと同様に-snameと共に使用する必要があります。

では、Kaiをバックグラウンドで動作させてみましょう。

次のコマンドを実行すると、Kaiを起動後すぐにErlang VMのコンソールをDetacheします。

$ erl -sname kai -pa ebin -config kai -eval 'application:start(kai).' -detached

-sname kaiを指定すると、kai@`hostname -s` という名前がErlangノードに付きます。筆者の環境では、hostname -sの結果がcentosであるため、kai@centosという名前が付きました。

毎回、このようなコマンドを実行するのは効率が悪いため、次のような起動スクリプトを作成しておくと便利です。

この起動スクリプトをstart_kai.shという名前で保存してください。

リスト5 start_kai.sh
#!/bin/sh

ROOT='/path/to/kai-0.4.0'
ERL='/usr/bin/env erl'

for n in $@
do
${ERL} -sname ${n} \
       -pa ${ROOT}/ebin \
       -config ${ROOT}/${n} \
       -eval 'application:start(kai).' \
       -detached
done

変数ROOTには、Kaiをコンパイルしたディレクトリをフルパスで指定してください。フルパスで指定する事により、任意のでディレクトリで起動スクリプトを実行できるようになります。

起動スクリプトの第一引数は、Erlangノード名だけではなく、読み込む設定ファイルも指定している点に注意してください。

次に、バックグラウンドで動作するKaiを停止してみましょう。次のコマンドを実行すると、Erlangノード "kai@centos" に接続し、終了コマンドを送信します。

$ erl -sname kai_controller -remsh kai@centos
Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.1  (abort with ^G)
(kai@centos)1> q().      <- プロンプトが接続元 kai_controller@centos ではなく、接続先となる事に注意
ok
(kai@centos)2>
User switch command      <- q(). で接続先が終了してしまい、コンソールが操作できない為、Ctrl+G でメニューを呼び出す
 --> q                   <- q で kai_controller@centos を終了させる

このコマンドもスクリプトにしておくと便利です。erlコマンドの引数に習って、remsh.shという名前で保存してください。

リスト6 remsh.sh
#!/bin/sh

ERL='/usr/bin/env erl'

${ERL} -sname kai_controller -remsh $1@`hostname -s`

なお、これらの運用に関する詳細な説明は、本連載の最後で橋本さんにより解説される予定です。

クラスタを構築する

Kaiは、複数のノードでクラスタを構築し、データを複製して保持する事ができます。

今回は、気軽にお試しいただけるよう、一台の物理マシン上で複数のノードを起動する事でクラスタを構築しますが、複数台の物理マシンでクラスタを構築する方法も基本は同じです。

ここでは、3ノード構成のクラスタの例を説明します。

まず始めに、ノードごとに設定ファイルを準備する必要があるのですが、設定ファイルを作る度にエディタを起動するのは効率が悪いので、次のようなヘルパースクリプトを作成しmake_kai_config.shという名前で保存してください。

リスト7 make_kai_config.sh
#!/bin/sh

for n in $@
do
cat <<__END_OF_CONFIG__ > kai${n}.config
[{kai, [
    {rpc_port, 1101${n}},
    {rpc_max_processes, 30},
    {memcache_port, 1121${n}},
    {memcache_max_processes, 10},
    {max_connections, 32},
    {n, 3},
    {r, 2},
    {w, 2},
    {number_of_buckets, 1024},
    {number_of_virtual_nodes, 128},
    {store, ets}
]}].
__END_OF_CONFIG__
done

次に、このヘルパースクリプトを使用して、ノードごとにkai1.config、kai2.config、kai3.configという名前の設定ファイルを作成します。

$ /path/to/make_kai_config.sh 1 2 3

スタンドアローンの例とは異なり、Quorumに設定するの値がデフォルトのN:R:W = 3:2:2に戻っている点と、全てのノードが一台の物理マシン上で起動するため、Listenするポート番号を変更してある点に注意してください。

また、今回は全てのノードがストレージとしてetsを指定していますが、各ノード毎に指定するストレージは任意であるため、用途にあわせて自由に設定してください。

次に、3つのノードを起動します。

$ /path/to/start_kai.sh kai1 kai2 kai3

このままでは、それぞれのノードが個々にスタンドアローンで起動している状態ですので、それぞれのノードに互いの存在を教えてクラスタを構築します。

あるノードAに、あるノードBの存在を教えるErlangの式は、"kai_rpc:check_node(NodeA, NodeB)" となります。

NodeA、NodeBは、"{IPAddress, Port}" という形式、IPAddressは、"{127,0,0,1}" という形式で指定します。なお、アドレスの区切りが "."(ドット)ではなく "," ⁠カンマ)である点に注意してください。

$ /path/to/remsh.sh kai1
Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.1  (abort with ^G)
(kai1@centos)1> kai_rpc:check_node({{127,0,0,1}, 11011}, {{127,0,0,1}, 11012}).
ok
(kai1@centos)2> kai_rpc:check_node({{127,0,0,1}, 11012}, {{127,0,0,1}, 11013}).
ok
(kai1@centos)3> kai_rpc:check_node({{127,0,0,1}, 11013}, {{127,0,0,1}, 11011}).
ok
(kai1@centos)4>

「ここでは、kai1に接続し、クラスタの構築を行いましたが、kai2やkai3に接続しても同様の事が行えます。

最後に、それぞれのノードが互いの情報を保持しているのか確認します。あるノードAが保持しているノードの一覧を取得するErlangの式は、"kai_rpc:node_list({IPAddress, Port})" となります。

(kai1@centos)4> kai_rpc:node_list({{127,0,0,1}, 11011}).
{node_list,[{{192,168,1,2},11012},
            {{192,168,1,2},11011},
            {{192,168,1,2},11013}]}
(kai1@centos)5> kai_rpc:node_list({{127,0,0,1}, 11012}).
{node_list,[{{192,168,1,2},11012},
            {{192,168,1,2},11011},
            {{192,168,1,2},11013}]}
(kai1@centos)6> kai_rpc:node_list({{127,0,0,1}, 11013}).
{node_list,[{{192,168,1,2},11012},
            {{192,168,1,2},11011},
            {{192,168,1,2},11013}]}
(kai1@centos)7>
User switch command
 --> q

このように、一つのノードが、自身も含めたクラスタを構成する全てのノード情報を保持していれば、クラスタの構築は終わりです。

次回の予告

以上で、Kaiを活用するための下準備ができました。

次回は、いよいよ他のKey/Valueストアと一線を画するクラスタの詳細を説明する予定です。どうぞご期待ください。

おすすめ記事

記事・ニュース一覧