kumofs-0.4.0リリース - CAS操作をサポート
新たにCAS(Compare-And-Swap)をサポートした、kumofs-0.4.0をリリースしました。
memcachedのテキストプロトコルで、getsコマンドとcasコマンドを新たに使うことができます。
後方互換性は保たれています*1。新機能を利用するには、kumo-gatewayとkumo-serverを更新してください。
CASとは?
CAS(Wikipedia)は Compare-And-Swap の略で、ある値を取得したあと、その値が別のプロセスから更新されていなければ(Compare)変更を適用する(Swap)という操作を、アトミックに実行することができます。
CASを利用することで、kumofs上にキューやカウンタ、連想配列、ロックなどを実装することが可能になります。
例えば kumofs でキューを実現する擬似コードは、次のようになります:
KEY = "myqueue" # 初期値を設定しておく $db.set(KEY, [].to_msgpack) # シリアライズした配列を保存 def pop while true value, unique = $db.gets(KEY) # 現在の値と、何か一意な値が得られる array = MessagePack.unpack(value) # 値をデシリアライズ element = array.pop # 値を変更 success = $db.cas(KEY, array.to_msgpack, unique) # この間に値が変更されていなければ、変更した値を保存 # 保存が成功したら、取り出した値を返す if success return element end # 値が変更されていたら、リトライする end end def push(element) while true value, unique = $db.gets(KEY) array = MessagePack.unpack(value) # ここだけがpopと異なる array.push(element) success = $db.cas(KEY, array.to_msgpack, unique) if success return end end end
このコードでは while true と無限ループしていますが、実際には堅牢性を確保するために(十分に長い)タイムアウト時間を設定した方が良いでしょう。
この pop と push の実装を見ると、値を変更する部分のコードだけが異なることが分かります。そこで共通部分をまとめると、次のようになります:
def atomic(&block) while true value, unique = $db.gets(KEY) object = MessagePack.unpack(value) # ここだけが異なる object = block.call(object) success = $db.cas(KEY, object.to_msgpack, unique) if success return object end end end # atomicを利用してpopを実装 def pop element = nil atomic do |array| # 現在の値を受け取って element = array.pop # 値を変更して array # 変更した値を返す end # この間に値が変更されていたら、リトライされる return element end # atomicを利用してpushを実装 def push(element) atomic do |array| array.push(element) array end end
カウンタも実装してみたくなりますが、ここで実装した atomic を使えば、話は簡単です:
# atomicを利用してaddを実装 def add(num) atomic do |value| # 現在の値を受け取って value = value + num # 値を変更して value # 変更した値を返す end # この間に値が変更されていたら、リトライされる end def sub(num) add(-num) end def incr() add(1) end def decr() sub(-1) end
このようにCASを使うことで、単純なkey-valueストアに上に様々なデータ構造を実装することが可能になります。
kumofsのCAS
kumofsが実装しているCAS操作の意味論は、比較が失敗すれば、値の更新は失敗するというものです。言い換えれば、比較が成功したときに、値の更新が成功するとは限りません。
現象として値の更新が失敗した場合は、何度かリトライする必要があります。上記のatomicのように、アプリケーションでリトライすることを期待しています。
kumofsでは、ノードの数が増減したときに、double-hash-spaceアルゴリズムに基づいて、データの移動を行います。
kumofsはこの移動中でもSet操作やGet操作を処理することができますが、CAS操作については古いデータ(=比較対象のデータ)を必要とするため、移動が完了した後にしか(単純には)受け付けられません。
このためノードの数が増減したあとは、しばらくCAS操作が失敗する期間が発生します。しばらくリトライしていると、その内に成功するようになります。
Have fun with kumofs! The Kumofs Project
*1:gatewayやserverの一部だけをアップデートしても問題が起きません。