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

第3回 Kaiの詳細(1) ─Kaiの要であるクラスタを極める

この記事を読むのに必要な時間:およそ 7.5 分

Write/Write Conflictについての蛇足

前述の通り,Quorumに不適切な値を設定するなどにより,Write/Write Conflictが発生します。その際,Kaiは,memcachedプロトコルではサポートしきれいない振る舞いをする事があります。

ここで述べる事は,実サービス環境では考慮する必要がほとんど無いため,Kaiの内部動作に興味のある方のみお読み頂いても構いません。

では,Write/Write Conflictを意図的に発生させ,実際にどのような値が戻るか試してみましょう。

何らかの理由によりクラスタが分裂している際に,それぞれのサブクラスタに対してデータを保存すると,Write/Write Conflictが発生するので,それを利用します。

まず始めに,次のような設定ファイルを作成し,それぞれ,kai_wwc1.config,kai_wwc2.config,kai_wwc3.config という名前で保存してください。

リスト4 kai_wwc1.config

[{kai, [
    {rpc_port, 11011},
    {rpc_max_processes, 30},
    {memcache_port, 11211},
    {memcache_max_processes, 10},
    {max_connections, 32},
    {n, 3},
    {r, 3},
    {w, 1},
    {number_of_buckets, 1024},
    {number_of_virtual_nodes, 128},
    {store, ets},
]}].

リスト5 kai_wwc2.config

[{kai, [
    {rpc_port, 11012},
    {rpc_max_processes, 30},
    {memcache_port, 11212},
    {memcache_max_processes, 10},
    {max_connections, 32},
    {n, 3},
    {r, 3},
    {w, 1},
    {number_of_buckets, 1024},
    {number_of_virtual_nodes, 128},
    {store, ets},
]}].

リスト6 kai_wwc3.config

[{kai, [
    {rpc_port, 11013},
    {rpc_max_processes, 30},
    {memcache_port, 11213},
    {memcache_max_processes, 10},
    {max_connections, 32},
    {n, 3},
    {r, 3},
    {w, 1},
    {number_of_buckets, 1024},
    {number_of_virtual_nodes, 128},
    {store, ets},
]}].

N:R:W = 3:3:1であるため,⁠W > N/2」を満たしていない点に注目してください。

次に,ノードを起動します。

$ /path/to/start_kai.sh kai_wwc1 kai_wwc2 kai_wwc3

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

リスト7 kai_test_wwc_set.pl

#!/usr/bin/env perl

use strict;
use warnings;

use Cache::Memcached;

for (1..3) {
     my $mem = Cache::Memcached->new({
        servers => ['127.0.0.1:1121' . $_],
    });
    $mem->set(foo => 'bar' . $_, 0,);
}

W=1であるため,クラスタを構築する前に,データを保存する事ができてしまいます。用意したPerl Scriptを実行し,各ノードにデータを保存してください。

$ perl /path/to/kai_test_wwc_set.pl

これで,各ノードに同じキーでありながら異なる値を持つデータが保存されている状態となりました。

次に,クラスタを構築します。

$ /path/to/remsh.sh kai1_wwc1
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_wwc1@centos)1> kai_rpc:check_node({{127,0,0,1}, 11011}, {{127,0,0,1}, 11012}).
ok
(kai1_wwc1@centos)2> kai_rpc:check_node({{127,0,0,1}, 11012}, {{127,0,0,1}, 11013}).
ok
(kai1_wwc1@centos)3> kai_rpc:check_node({{127,0,0,1}, 11013}, {{127,0,0,1}, 11011}).
ok
(kai1_wwc1@centos)4>
User switch command
 --> q

では,データを取得します。ここでは,実際にどのようなデータが戻るか確認するため, Telnetを使用します。

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
get foo
VALUE foo 0 4
bar1
VALUE foo 0 4
bar2
VALUE foo 0 4
bar3
END
quit
Connection closed by foreign host.

ご覧の通り,KaiではWrite/Write Conflictが発生すると,全ての値を返します。これは,Write/Write Conflictはクライアントで解決するというDynamo の思想を反映した振る舞いです。

ここでは,保存した順番に値が並んでいますが,実際の並び順は,始めの値が自ノードに保存されている値,以降は,レスポンスが早い順であるため,同じノードに接続したとしても,常に同じ並び順で値が返される保証はありません。

一般的なmemcachedクライアントは,データ取得時に複数の値が戻る事を想定していないため,一つの値しか取得する事ができず,基本的には,どの値が戻るか解りません。

前述の通り,Kaiでは,クライアントで全ての値を比較検討し,正しい値を再び保存する事でWrite/Write Conflictを解消する事を期待しているのですが,既存のmemcachedクライアントを使用すると,全ての値を比較検討する事ができません。

よって,実サービス環境では,Write/Write Conflictを発生させない事が非常に重要です。Quorumの設定値は慎重に検討し,サブクラスタへのデータ保存を絶対に行わないようにしてください。

以下は,筆者が動作確認を行ったライブラリの一覧です。

言語クライアントバージョン実装戻り値
PerlCache::Memcached1.26Pure Perl最後の値
 Cache::Memcached::Fast0.14XS(独自実装)最初の値
 Cache::Memcached::libmemcached0.02009libmemcached最後の値
Rubymemcache-client1.7.2Pure Ruby最後の値
 Ruby-MemCache0.0.1Pure Ruby最後の値
Pythonpython-memcached1.44Pure Python最初の値

PerlのCache::Memcached,Pythonのpython-memcachedは,始めに返された値を,その他は最後に返された値を取得するという結果になりました。また,python-memcachedのみ,続けて他のデータを取得できませんでした。

リスト8 kai_test_wwc_get.py

#!/usr/bin/env python

import memcache

mc = memcache.Client(['127.0.0.1:11211'])
print mc.get('foo')

mc.set('foo_py', 'baz')
print mc.get('foo_py')
$ /path/to/kai_test_wwc_get.py
bar1
bar3        <- foo_py を取得しているにも関わらず,foo の値を取得している

意図的に発生させなければ,よほどの事が無い限りWrite/Write Conflictは発生しないので,実サービス環境でpython-memcachedを利用しても差し支えありません。

ただ,念には念を入れて,クライアントを選考する際にWrite/Write Conflictを意図的に発生させ,動作検証する事も無価値ではありません。

Write/Write Conflictをmemcachedのプロトコルだけでは解決できないという問題は,Kai開発者の間で活発な議論が続いており,0.4以降のバージョンで解決される予定です。もし,対処方法にご興味がおありでしたら,KaiのMLなどでご意見を頂ければ幸いです。

著者プロフィール

幾田雅仁(いくたまさひと)

酪農,ゴルフのキャディさん,某大手パソコン通信の下請け,某大手ポータルサイトなどを経て,決済代行を生業とする株式会社ゼロに入社。

p2p?なにそれ?美味しいの?の状態で,Erlang 分散処理勉強会に参加し,Kai のプレゼンを見て感銘を受け,無理矢理開発に参加し,現在に至る。