====== オンメモリ型分散Key-Valueストア 「okuyama」=====================================================
Javaで実装された、オンメモリ型分散Key-Valueストア「okuyama」を
ダウンロード頂きありがとうございます。

※起動方法は本テキストの「■機能説明とサンプルの実行方法」をご覧ください。
  同時に「okuyama構成図.gif」もご参照ください。
  blog:http://d.hatena.ne.jp/okuyamaoo/


・改修履歴
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.6.2 - (2010/05/09)]]
  ■データノードをmemcacheのノードとして利用可能に
    マスターノードを起動せずに直接データノードをmemcacheのノードとして利用可能。
    以下のように設定を変更しexecDataNode.batを実行するとmemcacheクライアントでアクセスできる。

    設定ファイルDataNode.propertiesの25行目
    ----------------------------------
    KeyManagerHelper.Init=
              ↓上記を下記内容に変更
    KeyManagerHelper.Init=memcache
    ----------------------------------
    として起動するとmemcacheプロトコルで会話が可能となる。
    対応メソッドはマスターノードをmemcacheモードとして起動した場合と同様となる。
    (・set, ・get, ・add, ・delete)(flagに対応)

    ファイルへのデータ永続化が可能
    設定ファイルDataNode.propertiesの30行目、31行目
    ----------------------------------
    KeyManagerJob1.memoryMode=false       
    KeyManagerJob1.dataMemory=true
    ----------------------------------
    上記の設定でトランザクションログは残し、登録されたデータはメモリに保持する
    両方はtrueにすると完全メモリモード(最も高速に稼動)(単体でmemcacheとほぼ同程度の処理速度が出る)
    両方はfalseにすると完全ファイルモード(最も大量のデータ(Valueのサイズ)を保持可能)

    デフォルトでは2560バイトがvalueサイズの最大値となるので、src\org\imdst\util\ImdstDefine.javaの150行目を
    変更しcompile.batを実行しコンパイルすると許容できるデータサイズが変更できる。


  ■Key値からHash値を求めるロジックを変更
    okuyamaでは登録されたKey値はハッシュ値を求めてその値を実際の登録に使用しているが、
    その値の生成ロジックを見直し、よりハッシュ値が分散するように変更
    この変更により、今まで登録したデータは全て破棄する必要があります。
    この変更が受け入れられない場合はsrc\org\imdst\helper\MasterManagerHelper.javaの2660行目、2661行目を
    以下のように変更し、compile.batを実行し再コンパイルを実行。
    --------------------------------------------------------------------
	private int hashCodeCnv(String str) {
		return new HashCodeBuilder(17,37).append(str).toHashCode();
		//return str.hashCode();
	}
               ↓↓↓↓変更(コメントアウトを入れ替え)
	private int hashCodeCnv(String str) {
		//return new HashCodeBuilder(17,37).append(str).toHashCode();
		return str.hashCode();
	}
    --------------------------------------------------------------------


  ■データ登録メソッドsetValue時の処理速度を20%向上
    データノード、スレーブデータノード起動時にsetValueを実行した際の処理速度を
    20%向上。データノードへの登録リクエスト送信中にスレーブデータノードへの送信準備をするように修正。


  ■バグFix
    

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.6.2 - (2010/05/09)]]
  ■Memcaheプロトコルモード時の以下の処理を対応
    1.memcacheのメソッドであるaddに対応
      未登録データの場合のみ登録可能なmemcacheのaddコマンドに対応

    2.memcacheのメソッドであるdeleteに対応
      memcacheコマンドであるデータ削除用コマンドdeleteに対応

    3.memcacheのflag登録に対応
      memcacheコマンドでset、add時に指定するflagに対応
      get時に登録flagを返却

  ■データノード間のデータリカバー時のデータ転送方式を一部変更
    従来はノードダウンからのリカバー時にレプリケーションノードからの1通信で全ての登録データ取得していたいが、
    これでは大きなデータが登録されている場合に、送信側、受信側でメモリにのりきらずにリカバーに失敗する場合が
    あったため、使用可能なメモリの残量を確認しながら、データを分割して転送しリカバリーするように変更
    ※データの保存方式をメモリではなくファイルにしている場合は、特にこの問題は発生する可能性があった。

  ■PHP用クライアント(OkuyamaClient.class.php)にgetByteValueメソッドを追加
    Java用クライアントで登録したバイトデータ(setByteValueで登録したデータ)を取得する際に使用

  ■バグFix

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.6.1 - (2010/04/21)]]
  ■データが存在しない場合のみ保存できるメソッドを追加
    +未登録のキー値である場合のみ登録可能となり、既に登録済みの場合は登録できない。

     *未登録の場合のみ登録可能なメソッドは以下である。
      ・クライアントのメソッド名:setNewValue
      ・引数1:Key値
      ・引数2:Value値
      ・戻り値:String[] 要素1(データ有無):"true" or "false",要素2(失敗時はメッセージ):"メッセージ"

      ・クライアントのメソッド名:setNewValue
      ・引数1:String Key値
      ・引数2:String[] tag値配列
      ・引数3:String Value値
      ・戻り値:String[] 要素1(データ有無):"true" or "false",要素2(失敗時はメッセージ):"メッセージ"

  ■クライアントから接続時に保存出来る最大データサイズをMasterNodeから取得するように変更

  ■バグFix

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.6.0 - (2010/04/08)]]
  ■分散ロック機能を追加
    +任意のデータをロックする機能を追加。
    +分散ロック機能はマスターノード用設定ファイルである、MasterNode.propertiesの9行目の"TransactionMode=true"で
     ロック機能が使用可能となる。
     また、72行目の"TransactionManagerInfo=127.0.0.1:6655"でTransactionManagerノードを指定する必要がある
     そして、TransactionManagerノードが起動している必要があるため、同梱のexecTransactionNode.batで起動する。
     分散ロック機能を使用する場合は、全てのマスターノードが"TransactionMode=true"で起動している必要がある。
     同梱の設定ファイルは全て分散ロック機能で起動する設定となる。
     ※execMasterNode2.batは分散ロック機能あり、memcacheプロトコルモードで起動する。
     また、従来の分散ロック機能なしで起動する場合は、"TransactionMode=false"としてexecMasterNode.batを実行する。

    +仕組みとしては、Clientからロック取得依頼を行った場合、TransactionManagerノードに指定したKey値で
     ロック情報を作り上げる。この際、すでに別Clientから同一のKey値でロックが取得されている場合は、
     指定した時間の間、ロックが解除されるのを待ち、取得を試みる。
     ロックされた値に対して、set,remove系のアクセスを行った場合は、TransactionManagerノードに対して該当の
     Key値が、リクエストを発行したClient以外からロックされているかを問い合わせて、別クライアントがロック
     している場合は、ロックが解除されるのを待ち続ける。
     同クライアントがロックしているもしくは、ロックがない場合は、そのまま処理を続行する。
     ロックのリリースも同じ動きである。
     なお、分散ロック機能を有効にした場合は、無効時と比べ1回通信が多く発生するため、処理速度は落ちる。
     また、TransactionManagerノードがSPOFとなるが、機能していない場合は無視して稼動するが、
     処理速度は極端に劣化する。
     今後、SPOFとならないように改善予定である。

    +以下は説明となる
     *ロックを実施したデータの挙動は以下となる。
      ・ロック可能なKey値(データ)は現在登録済みであっても、登録されていなくても可能である。
      ・1クライアントから同時に複数のデータをロック可能である
      ・ロックしたデータはロックを実施したクライアントからのみロック解除可能である。
      ・ロック中のデータはロックを実施したクライアントからのみ登録可能である。
      ・ロック中のデータはロックを実施したクライアントからのみ変更可能である。
      ・ロック中のデータはロックを実施したクライアントからのみ削除可能である。
      ・ロック中のデータは全クライアントから参照可能である。
 
     *ロック機能使用開始メソッドは以下である。
      ・クライアントのメソッド名:startTransaction
      ・引数なし
      ・戻り値:boolean true:スタート成功 false:スタート失敗
        ※ロック機能有りでTransactionManagerノードを起動していない場合は、スタートに失敗する。
          
     *ロックメソッドへの引数と戻り値は以下である。
      ・クライアントのメソッド名:lockData
      ・引数1:ロック対象Key値
        引数2:ロック継続時間
              (ロック解除を行わない場合でも、ここでの設定時間が経過すると自動的に解除される。
               単位は秒。
               0を設定するとロックを実施したクライアントが解除するまで永久にロックされる。
               ※0指定は推奨しない)
        引数3:ロック取得待ち時間
              (既に別クライアントがロック中のデータへロックを実施した場合に、設定時間の間ロック取得をリトライする。
               単位は秒。
               0を設定すると1回ロックを試みる)
 
      ・戻り値:String配列
               String配列[0]:Lock成否 "true"=Lock成功 or "false"=Lock失敗
 
 	 *ロック開放への引数と戻り値は以下である。
      ・クライアントのメソッド名:releaseLockData
      ・引数1:ロック対象Key値
 
      ・戻り値:String配列
               String配列[0]:開放成否 "true"=開放成功 or "false"=開放失敗

     *ロック機構使用終了メソッドは以下である。
      ・クライアントのメソッド名:endTransaction
      ・引数なし
      ・戻り値なし

    +Java版、PHP版のクライアントからは、ロック、リリース両方が可能
     Memchacheクライアントはロック、リリース機能は利用できないが、Lock中のデータにsetを実行した場合は"待ち状態"に入る。

   │※ImdstKeyValueClientを使用した実装例)─────────────────────────────────┐
   │                                                                                                        │
   │ // クライアントインスタンス作成                                                                        │
   │ ImdstKeyValueClient client = new ImdstKeyValueClient();                                                │
   │ // 接続                                                                                                │
   │ imdstKeyValueClient.connect("127.0.0.1", 8888);                                                        │
   │ // Transactionを開始してデータをLock後、データを更新、取得し、Lockを解除                               │
   │                                                                                                        │
   │ // 引数はLock対象のKey値, Lock維持時間(秒)(0は無制限), Lockが既に取得されている場合の                  │
   │ // 取得リトライし続ける時間(秒)(0は1回取得を試みる)                                                    │
   │ ImdstKeyValueClient imdstKeyValueClient = new ImdstKeyValueClient();                                   │
   │ imdstKeyValueClient.connect(args[1], port);                                                            │
   │ String[] ret = null;                                                                                   │
   │                                                                                                        │
   │ // Lock準備                                                                                            │
   │ if(!imdstKeyValueClient.startTransaction()) throw new Exception("Transaction Start Error!!");          │
   │                                                                                                        │
   │ long start = new Date().getTime();                                                                     │
   │                                                                                                        │
   │ // Lock実行                                                                                            │
   │ // "DataKey"というKey値で10秒間維持するロックを作成。もし既にロックされている場合は、5秒間ロック取得を │
   │ // 繰り返す                                                                                            │
   │ ret = imdstKeyValueClient.lockData("DataKey", 10, 5);                                                  │
   │ if (ret[0].equals("true")) {                                                                           │
   │     System.out.println("Lock成功");                                                                    │
   │ } else if (ret[0].equals("false")) {                                                                   │
   │     System.out.println("Lock失敗");                                                                    │
   │ }                                                                                                      │
   │                                                                                                        │
   │                                                                                                        │
   │ // 以下のコメントアウトをはずして、コンパイルし、                                                      │
   │ // 別のクライアントから更新を実行すると、更新できないのがわかる                                        │
   │ //Thread.sleep(5000);                                                                                  │
   │                                                                                                        │
   │ // 自身でロックしているので更新可能                                                                    │
   │ if (!imdstKeyValueClient.setValue(args[3], "LockDataValue")) {                                         │
   │ 	System.out.println("登録失敗");                                                                      │
   │ }                                                                                                      │
   │                                                                                                        │
   │ // 取得                                                                                                │
   │ ret = imdstKeyValueClient.getValue(args[3]);                                                           │
   │ if (ret[0].equals("true")) {                                                                           │
   │     // データ有り                                                                                      │
   │     System.out.println("Lock中に登録したデータ[" + ret[1] + "]");                                      │
   │ } else if (ret[0].equals("false")) {                                                                   │
   │     System.out.println("データなし");                                                                  │
   │ } else if (ret[0].equals("error")) {                                                                   │
   │     System.out.println(ret[1]);                                                                        │
   │ }                                                                                                      │
   │                                                                                                        │
   │ // 自身でロックしているので削除可能                                                                    │
   │ ret = imdstKeyValueClient.removeValue(args[3]);                                                        │
   │                                                                                                        │
   │ if (ret[0].equals("true")) {                                                                           │
   │     // データ有り                                                                                      │
   │     System.out.println("Lock中に削除したデータ[" + ret[1] + "]");                                      │
   │ } else if (ret[0].equals("false")) {                                                                   │
   │     System.out.println("データなし");                                                                  │
   │ } else if (ret[0].equals("error")) {                                                                   │
   │     System.out.println(ret[1]);                                                                        │
   │ }                                                                                                      │
   │                                                                                                        │
   │ // Lock開放                                                                                            │
   │ ret = imdstKeyValueClient.releaseLockData(args[3]);                                                    │
   │ if (ret[0].equals("true")) {                                                                           │
   │     System.out.println("Lock開放成功");                                                                │
   │ } else if (ret[0].equals("false")) {                                                                   │
   │     System.out.println("Lock開放失敗");                                                                │
   │ }                                                                                                      │
   │                                                                                                        │
   │ long end = new Date().getTime();                                                                       │
   │ System.out.println((end - start) + "milli second");                                                    │
   │                                                                                                        │
   │ // トランザクション開放                                                                                │
   │ imdstKeyValueClient.endTransaction();                                                                  │
   │ // 接続切断                                                                                            │
   │ imdstKeyValueClient.close();                                                                           │
   └────────────────────────────────────────────────────┘

  ■いくつかのバグを修正

  ※今後は、分散トランザクションを実現するように実装を進める。
========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.5.2 - (2010/03/28)]]
  ■Memcacheプロトコルに一部対応
    KVSの標準プロトコルになりつつある、memcacheのプロトコルに対応するモードを追加
    MasterNode.propertiesの14行目"MasterManagerJob.Option="を"MasterManagerJob.Option=memcache"とすると
    memcacheプロトコルでアクセス可能である。
    MasterNode2.propertiesがmemcache用の設定ファイルになっている。
    execMasterNode2.batを実行するとmemcacheプロトコルで立ち上がる。
    対応メソッドはsetとgetである。またset,getのflagは0のみ対応している。
    今後対応範囲を増やす予定。

  ■データ保存形式をファイル(DataNode.properties30行目、33行目をfalseとした場合)にした場合に、
    追記型で記憶しているため、ファイルが永遠に肥大化するため、vacuum機能を追加。
    自動的に実行される。

  ■documetディレクトリを追加
    性能評価を実施した資料と、構成図を配置

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.5.1 - (2010/03/17)]]
  ■PHP用のクライアントを作成
	PHPでMasterServerへアクセス出来るようにクライアントを作成。
	Javaのコードを焼きなおしました。
	バイトデータを登録(setByteValue)、取得(getByteValue)するメソッドのみ未実装。
    リリース物etc_client\OkuyamaClient.class.phpになります。
    サンプル実行コードetc_client\PhpTestSock.phpと、実行用batファイルetc_client\PhpAutoTest.batを同梱しました。

  ■ReadMe.txt、ReadMe-UTF.txtを最新の状態に更新

  ■ReadMe.txt、ReadMe-UTF.txtの"[[リリース Ver 0.5.0 - (2010/03/17)]]"の記述ミスを訂正
    訂正箇所は以下
    -------------------------------------------------------------------------------------------------------------------------------------------
    ■TestSockサンプルにScript実行モードのバージョンを追加(引数 "2.3" Script実行)
    ・取得、実行サンプル起動方法
    java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.3 127.0.0.1:8888 20000 "var dataValue; var retValue = dataValue.replace('data', 'dummy'); var execRet = '1';"
                                                                        ^^^
                                                               正しくは 127.0.0.1 8888
    -------------------------------------------------------------------------------------------------------------------------------------------

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.5.0 - (2010/03/17)]]
  ■データ取得時にJavaScriptを実行可能なインターフェースを追加
    ImdstKeyValueClientのgetValueScriptメソッドで実行可能。
    データ取得時にJavaScriptで記述したスクリプトをKey値と同時に渡し、Key値でValue値が取得出来た場合、
    その値にスクリプトを実行しその結果を取得できる。
    スクリプト内で、返却有無の決定及び、返却値(Value)を設定することが出来る。
    スクリプトはデータノードで実行されるため、今まで取得したデータに対して何だかの処理で加工or値の妥当性
    検証などを行っていた場合は、スクリプトで処理を代行し、取得マシンのリソースの節約や、取得マシンの
    スペックを越えるような大規模なデータもデータノードのパワーを使用して処理可能である。

    【スクリプト記述制約】
     スクリプトの制約は以下の名前の変数を宣言する必要がある。
    ・ "dataValue" = key値で取得出来たvalue値が設定される。スクリプト内ではこの変数がvalue値となる。
    ・ "execRet" = 実行結果(retValue変数)をクライアントに返すことを指定
                   (1を代入すると返却される 0を代入すると返却されない)
    ・ "retValue" = 実行結果を格納する。クライアントに返される値

   │※ImdstKeyValueClientを使用した実装例)─────────────────────────────────┐
   │                                                                                                        │
   │ StringBuffer scriptBuf = new StringBuffer();                                                           │
   │ // スクリプトを作成                                                                                    │
   │ scriptBuf.append("var dataValue;");                                                                    │
   │ scriptBuf.append("var execRet;");                                                                      │
   │ scriptBuf.append("var retValue;");                                                                     │
   │ // 取得したValue値に"data"という文字がある場合は"dummy"に置換する                                      │
   │ scriptBuf.append("retValue = dataValue.replace('data', 'dummy');");                                    │
   │ // 返却指定                                                                                            │
   │ scriptBuf.append("execRet = '1';");                                                                     │
   │                                                                                                        │
   │ // クライアントインスタンス作成                                                                        │
   │ ImdstKeyValueClient client = new ImdstKeyValueClient();                                                │
   │ // 接続                                                                                                │
   │ imdstKeyValueClient.connect("127.0.0.1", 8888);                                                        │
   │ // Value取得及び、スクリプト実行を依頼                                                                 │
   │ String[] retValue = imdstKeyValueClient.getValueScript("key1", scriptBuf.toString());                  │
   │                                                                                                        │
   │ // 結果を表示                                                                                          │
   │ // 実行結果が存在する場合は"true"が存在しない場合は"false"が、エラーの場合は"error"が返却される        │
   │ System.out.println(retValue[0]);                                                                       │
   │ // retValue[0]が"true"の場合はスクリプトからの返却値が返却される。"error"の場合はエラーメッセージが返却│
   │ System.out.println(retValue[1]);                                                                       │
   └────────────────────────────────────────────────────┘

  ■TestSockサンプルにScript実行モードのバージョンを追加(引数 "2.3" Script実行)
    ・取得、実行サンプル起動方法
    java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.3 127.0.0.1 8888 20000 "var dataValue; var retValue = dataValue.replace('data', 'dummy'); var execRet = '1';"

  ■クライアントにマスターノードの自動バラランシングモード及び、ダウン時の再接続機能を追加
    ImdstKeyValueClientのsetConnectionInfosメソッドに接続対象となるマスターノードの接続文字列を配列で
    セット(フォーマット"IP:PORT番号"のString配列)し、autoConnectメソッドで接続すると、ノードへの接続が
    出来ない場合、接続後処理途中で切断された場合なども、自動的に再接続し稼動し続けることが出来る。


  ■TestSockサンプルに自動接続モードのバージョンを追加(引数 "1.2"自動接続で登録  "2.2"自動接続で取得)
    ・登録サンプル起動方法
    java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 1.2 "127.0.0.1:8888,127.0.0.1:8889" 20000
    ・取得サンプル起動方法
    java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.2 "127.0.0.1:8888,127.0.0.1:8889" 20000

    ※execMasterNode.batとexecMasterNode2.batを同時に実行した状態で上記を実行して、片側ずつ停止しては、
      再実行を繰り返しても、正しく稼動し続けることが確認できます。

========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.4.0 - (2010/03/15)]]
 ■データノードの動的追加をサポート
   マスターノード、データノード起動中にMasterNode.propertiesのKeyMapNodesRule、KeyMapNodesInfo、
   SubKeyMapNodesInfoに新たなノードの記述を追記し保存すると、自動的にファイルが再読み込みされ、
   データ、スレーブ両ノードが追加される。
   設定ファイルは再保存されるとほぼリアルタイムに反映されるため、保存前に該当ノードを起動しておく必要がある。
    ※元設定                                                ※ノード追加
   ┌─ MasterNode.properties─────────────┐  ┌─ MasterNode.properties────────────────────────────┐
   │KeyMapNodesRule=2                                 │  │KeyMapNodesRule=4,2                                                             │
   │                                                  │  │                                                                                │
   │KeyMapNodesInfo=localhost:5553,localhost:5554     │=>│KeyMapNodesInfo=localhost:5553,localhost:5554,localhost:6553,localhost:6554     │
   │                                                  │保│                                                                                │
   │SubKeyMapNodesInfo=localhost:5556,localhost:5557  │存│SubKeyMapNodesInfo=localhost:5556,localhost:5557,localhost:6556,localhost:6557  │                        │
   │                                                  │  │                                                                                │
   └─────────────────────────┘  └────────────────────────────────────────┘

 ■データノード追加後に新しいノードへデータの移行を行う機能を追加
   データノード追加後に過去データノード台数運用時のデータにアクセスしたタイミングで追加後ノードの
   メインデータノード、スレーブデータノードへデータを自動的に保存するようにし、以後過去のデータ保存ノードへ
   アクセスを行わないように機能を追加。
   ※ノード追加を行うと自動的にデータアクセス時に行われる。

 ■データノードへのアクセスをメインデータノード、スレーブデータノード間でバランシング出来るモードを追加
   MasterNode.propertiesのLoadBalanceModeの設定をtrueにするとバランシングを行う。
   メインと、スレーブで性能が大きく異なる場合はバランシングを行わないほうが良い場合もある。
   振り分けは単純なラウンドロビン方式である。

 ■マスターノードを複数台稼動させ、負荷分散、冗長化出来る機能を追加
   今までは、マスターノードは1台構成だったが、SPOFとなっていた為、複数台起動出来るように機能追加。
   マスターノードは1～n台での構成が可能だが、1台は必ずマスターノード内でのメインにならなければならない。
   理由は、データノードの生存監視と復旧時のリカバリー処理の為である。
   リカバリー処理時は、全てのマスターノードが同調して稼動するため、不整合は発生しない構成となっている。
   MasterNode.propertiesのMainMasterNodeModeをメインの場合はtrueとし、スレーブの場合はfalseとする。
   また、スレーブのマスターノードのネットワーク上の名前と稼動ポート番号をSlaveMasterNodesにカンマ区切りで列挙する。
   ※冗長化しない場合はMainMasterNodeMode=trueとするだけでよい。

  リリース物\(src or classes)\MasterNode.properties(メイン用) リリース物\(src or classes)\MasterNode2.properties(スレーブ用)
 ┌─ MasterNode.properties─────────────┐      ┌─ MasterNode2.properties ────────────┐
 │MainMasterNodeMode=true                           │      │MainMasterNodeMode=false                          │
 │                                                  │      │                                                  │
 │SlaveMasterNodes=127.0.0.1:8889                   │      │SlaveMasterNodes=                                 │
 │                                                  │      │                                                  │
 └─────────────────────────┘      └─────────────────────────┘

   メインのマスターノードで、データノードの監視、復旧を行うが、メインのマスターノードが稼動出来ない状態に
   なった場合は、スレーブのマスターノードの設定ファイルを以下のように書き換えて再保存すると、
   スレーブのマスターノードがメインのマスターノードに変更されて稼動し始める。
 ┌─ MasterNode2.properties   ─────────────┐
 │MainMasterNodeMode=true                               │
 │                                                      │
 │SlaveMasterNodes=(別のマスターノードがある場合は記述) │
 │                                                      │
 └───────────────────────────┘
   ※SlaveMasterNodesに列挙したノードが稼動していなくても、メインノード正しく稼動する。
   ※自動的にスレーブがメインに昇格するように後ほど実装予定。
   ※ImdstKeyValueClientに複数のマスターノードを設定できるようにし、
     バランシングや、接続できない場合の自動別ノード再接続機能などを後ほど実装予定。

 ■起動batファイル追加
   execMasterNode2.bat <=スレーブマスターノード起動コマンド
   execMasterNode.batのみでの稼動は従来と同じように可能
========================================================================================================
========================================================================================================
[New - 機能改善]
[[リリース Ver 0.3.3 - (2010/03/12)]]
 ■データノード同士のデータリカバリ時に従来は起動中のノードのデータを再起動してきたノードに
   無条件でリカバリしていたいが、データの登録、削除に実施時刻の要素を追加し、リカバリ時に実施時刻を
   確認し、新しいノードのデータを適応するように改善。

========================================================================================================
========================================================================================================
[New - 不具合修正&サンプルコード追加]
[[リリース Ver 0.3.2 - (2010/03/10)]]
 ■一定数のKey-Valueを同じTagに紐付けて保存すると正しく取り出せない不具合を修正

 ■TestSockにキー値を指定して削除するモードを追加(引数"8")
   java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 8 127.0.0.1 8888 KeyName1
   上記で127.0.0.1のポート8888番で起動しているマスターノードに接続し、"keyName1"というKey値で保存されて
  いるデータを削除する。
========================================================================================================
========================================================================================================
[New - 削除メソッドを実装&データ保存をメモリ上とファイルを選択できるように機能追加]
[[リリース Ver 0.3.0 - (2010/03/4)]]
 ■削除メソッドを追加
   ImdstKeyValueClientのremoveValueメソッドにて呼び出し可能
   リターン値はgetValueと同様で結果文字列("true" or "false")と削除できた場合は対象の値が格納された配列
   TestSockの"7"番指定で呼び出し可能
   ---------------------------------------------------------------------------------------------------
   java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 1 127.0.0.1 8888 100         <= 100件登録
   java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 7 127.0.0.1 8888 50         <= 50件削除

 ■データ保存形式をメモリとファイルを選択可能
   今までのバージョンでは稼動中はデータは常にKeyとValueの関係でメモリ上に保持されていた。
   登録毎のトランザクション記録ファイルと、定期的なメモリ情報のファイル書き出しで永続性を保っていたが、
   ファイル書き出しモードではKeyのみメモリ上に保持しデータはファイルにレストアすることを実現。
   これによりメモリ情報上での情報を少なくすることが可能であり、テストではJVMオプションで-Xmx256mとした
   DataNodeで400万件以上のデータを格納出来た。
   (※Key値の長さはDataNode格納時は影響がないが、参考に"datasavekey_1"～"datasavekey_4000000"というKey値)
   しかし、今まで制約を設けていなかった格納データ長に制約が出来た。
   現在格納できるValueのサイズは、512byteである。
   これ以上のデータ長を格納する場合は、ImdstKeyValueClientのsetByteValueメソッドを使用することとなる。
   ※512の指定を変更する場合は一度全てのデータファイル(サンプルでは.\keymapfileディレクトリのファイル)を
     全て削除してから、ImdstDefineのsaveDataMaxSizeを変更することで対応可能。
   ※データファイル保存方法は追記型となるので、今後バキュームメソッドを実装予定。
   ※メモリとファイルの切り替えはDataNode.propertiesの
     "KeyManagerJob1.dataMemory=false" <=ファイル
     "KeyManagerJob1.dataMemory=true"  <=メモリ
     で切り替え可能

========================================================================================================
========================================================================================================
[New - MasterNode処理部分を最適化&性能評価のテキストを添付]
[[リリース Ver 0.2.2 - (2010/02/24)]]
 ■MasterNodeのロジックを最適化。
   最適化残箇所はまだ残っている。

 ■最適化前と後で、簡単に性能を測定。測定結果をテキストとして添付
========================================================================================================
========================================================================================================
[New - 不具合修正]
[[リリース Ver 0.2.1 - (2010/02/11)]]
 ■自動リカバー時の挙動を修正。
   停止ノード起動時のタイミングによって正しくデータがリカバリーされない不具合を修正。

 ■src\MasterNode.propertiesにコメントを追加。
========================================================================================================
========================================================================================================
[New - 機能追加]
[[リリース Ver 0.2.0 - (2010/02/08)]]
 ■自動レプリケーション及び、自動リカバリー機能を追加
   データノードクラッシュ時もシステムの機能停止を防止。

 [追加機能詳細]
  1. 1つのデータを複数のデータノードに登録するよう機能を追加(自動レプリケーション)
     分散登録を行うことで、自動的にデータの複製が行われ、より安全性の高い分散KVSへと進化しました。

  2. 自動リカバリー機能
     1.の機能を使用している場合、メインデータノードがクラッシュした場合も、メインデータノード復帰後、
     スレーブノード(レプリケーションノード)から自動的にデータを復元します。
     ※スレーブノードがクラッシュした場合も、復帰後自動的にメインデータノードから復元されます。

  3. 上記2つの機能を使用している場合はノード停止時もシステムの停止なしに使用可能
     データノードクラッシュ時もスレーブノード(レプリケーションノード)への自動移行が行われるため、
     使用システムの停止がありません。

  ※上記の使用方法は、src\MasterNode.propertiesを参照してください。
  ※execMasterNode.batはマスターノードを起動します。
  ※execDataNode.batはメインデータノードを起動します。
  ※execSlaveDataNode.batはスレーブデータノードを起動します。
========================================================================================================


スペック
 実装言語:Java(jdk1.6にて開発)
 ソースエンコーディング:UTF-8
 動作検証OS:WinsowsXp SP3、CentOS 5.3(final)
 必要ライブラリ:log4j-1.2.14.jar、javamail-1.4.1.jar(JavaMail Ver1.4.1)
 Version:0.5.1(2010/01/07)


■機能説明とサンプルの実行方法
[機能説明]
1.Key-Valueストア
  Key-Valueストアを実現します。
  Keyは文字列、Valueは文字列と、byteデータの両方を登録可能です。

2.Tag機能
  Keyの他にTagを登録できます。
  Tagは文字列となります。
  ストアではKeyはユニークな値として扱われますが、Tagは複数のデータに紐付ける
  ことが出来ます。
  複数データにあらかじめ任意のTagを付けることで、Tag指定により
  一度に関連データを取得可能となります。
  ※現在はTag指定で関連するデータのKey配列が取得できます。


4.オンメモリであり、永続化されたデータ
  データの登録をクライアントが命令し、完了するとそのデータは2台のデータノードに登録されます
  登録のされかたは、Key値はメモリ(※1)とファイル(※2)に、Value値はファイル(※3)にのみ登録されます。
  Value値をメモリ(※1,4)にのみ登録することも可能です。
  上記2つ以外にトランザクションログも同時にファイルに登録しています。
  データノードがダウンしても正しく保存されたKey値をファイル情報から復元するか、
  Key値のファイルへの反映は定期的であるため、その間で保存前にダウンしたもしくは破損している場合は、
  トランザクションログから復旧されます。

  ※1.登録データは各データノード上で1つの同期化されたjava.util.HashMapに格納されます。
      データの登録、取り出しは全てここから行われます。
  ※2.ファイルシステムに保存されるデータは、定期的に保存されるjava.util.HashMapを
      シリアライズ化したデータと、データ登録時のログ情報となります。
      シリアライズデータの登録はデータ登録、取得処理とは非同期にて実行されます。
  ※3.Value値は固定長でLF改行の1ファイルに書き込まれます。
      記録方式は追記型となります。
      Key値はこのValue値の最新の位置を持っています。
  ※4.DataNode.propertiesの"KeyManagerJob1.dataMemory"の値で変更可能
      trueでメモリ保持、falseでファイル保存
      どちらの場合もトランザクションログは保存されるので、不慮のダウンによるデータの復元には影響はない。


5.分散型
  「okuyama」はマスタノード、データノード、クライアントの3つで構成されます。
  それぞれの役目は以下です。
  マスタノード:・設定されたアルゴリズム(※1)に従って、クライアントからのデータ操作依頼を適切な
                 データノードに依頼します。
               ・1つのデータを2台のデータノードにレプリケーションします
                 取得時に該当データノードがダウンしてる場合も、レプリケーション先のデータノードから取得します。
                 また、データノードが2台とも稼動している場合は、処理を分散し負荷分散を行います。
                 データ登録時に1台のデータノードがダウンしている場合ももう一台のノードに保存し処理を続行します。
               ・複数台での冗長化が可能である。
                 複数台で稼動する場合は、MasterNode内でのMainノードを決定する必要がある。
               ・停止なしでの動的なデータノードの追加を実現します。
                 データノードを追加した場合も、それまでに登録したデータへのアクセスは同じように可能です。
               ・常にDataNodeの生死を監視し、ダウンからの復旧時にデータを稼動ノードから自動リカバーさせます。
                 リカバー中はデータの不整合が発生しないように同期化を実現します。
                 ※1.管理するデータノードの数に依存する簡単なアルゴリズムです。


  データノード:・複数台での構成が可能
               ・キーとデータの組み合わせでデータを保存します。
                 データの登録、抽出、削除インターフェースを持ちます。
               ・自身では他ノードへのデータの振り分けなどは行ないません。

  クライアント:・マスタノードへの通信を行う実際のプログラムインターフェースです。
	           ・マスターノードの情報を複数セットすることで自動分散や、マスターノードダウン時の
                 別ノードへの自動再接続をおこないます。
               ・JavaとPHPそれぞれのクラインプログラムがあります。
                 使用方法は以下の項もしくはリリース物のサンプルプログラムTestSock.javaもしくは、
                 etc_clietn\PhpTestSock.phpを参照してください。
                 クライアントのソースファイルは
                 Javaはsrc\org\imdst\client\ImdstKeyValueClient.java
                 PHPはetc_client\OkuyamaClient.class.php

               インターフェースとしては、
               1.setValue(Key値, Value値)               :[Key(文字列)とValue(文字列)の組み合わせでのデータ登録]
               2.setValue(Key値, Tag値配列 Value値)     :[Key(文字列)とTag(文字列(配列))とValue(文字列)の組み合わせでのデータ登録]
               3.getValue(Key値)                        :[Key(文字列)でのValue(文字列)取得]
               4.getTagKeys(Tag値)                      :[Tag(文字列)でのKey値群(Key値の配列)取得]
               5.setByteValue(Key値, byte値)            :[Key(文字列)とbyte配列の組み合わせでのデータ登録](PHPは未実装)
               6.setByteValue(Key値, Tag値配列 byte値)  :[Key(文字列)とTag(文字列(配列))とbyte配列の組み合わせでのデータ登録](PHPは未実装)
               7.removeValue(Key値)                     :[Key(文字列)でデータを削除]
               8.getValueScript(Key値,JavaScriptコード) :[Key(文字列)とJavaScriptコードを渡し、取得されたvalue値にJavaScriptを実行し値を返す]


  それぞれのノード間の通信はTCP/IPでの通信となります。
  また、クライアントとマスタノード間の通信は試験的にBase64にてエンコーディングした文字列を使用しています。



[サンプル稼動方法]
 ※Windows環境

   前提条件:1.構成
              1台のマシン上で稼動するようなサンプル設定ファイルが同梱されています。
              それぞれのノード台数
              マスタノード:1台
              データノード:2台(2インスタンス×2(マスター、スレーブ))

            2.各ノードの使用ポートは以下となります。
              マスタノード:8888
              用途:クライアントからの要求待ち受け
              変更する場合:srcディレクトリ配下のMasterNode.propertiesの7行目を変更
                           7行目=MasterManagerJob.Init=8888<=この番号

              データノード:5553、5554　5556、5557
              用途:マスタノードからの要求待ち受け
              変更する場合:メインデータノード
                           srcディレクトリ配下のDataNode.propertiesの7行目、13行目を変更
                           7行目=KeyManagerJob1.Init=5553<=この番号
                           13行目=KeyManagerJob2.Init=5554<=この番号
                           スレーブデータノード
                           srcディレクトリ配下のSlaveDataNode.propertiesの7行目、13行目を変更
                           7行目=KeyManagerJob1.Init=5556<=この番号
                           13行目=KeyManagerJob2.Init=5557<=この番号

 1.コンパイル
   簡易的なコンパイル用バッチファイルを用意しています。
   本ファイルと同一ディレクトリにある、compile.batを実行してください。
   前提:javac.exeにPATHが通っている

 2.MasterNode起動
   簡易的なMasterNode起動用バッチファイルを用意しています。
   本ファイルと同一ディレクトリにある、execMasterNode.batを実行してください。
   設定ファイルはclasses\MasterNode.propertiesを参照しています。
   停止方法はCtrl+Cもしくは本ファイルと同一ディレクトリにServerStopというファイルを作成する
   ※ServerStopファイルが存在するとサーバはMasterNodeは起動しません。
   前提:1.java.exeにPATHが通っている
        2.メモリ上限を128MBとしています

 3.DataNode起動
   簡易的なDataNode起動用バッチファイルを用意しています。
   本ファイルと同一ディレクトリにある、execDataNode.batを実行してください。
   2つのデータノードが同時に起動します。
   設定ファイルはclasses\DataNode.propertiesを参照しています。
   停止方法はCtrl+Cもしくは本ファイルと同一ディレクトリにServerStopというファイルを作成する
   ※ServerStopファイルが存在するとサーバはDataNodeは起動しません。
   前提:1.java.exeにPATHが通っている
        2.メモリ上限を256MBとしています

 3.SlaveDataNode起動
   簡易的なスレーブ用DataNode起動用バッチファイルを用意しています。
   本ファイルと同一ディレクトリにある、eexecSlaveDataNode.batを実行してください。
   2つのデータノードが同時に起動します。
   設定ファイルはclasses\SlaveDataNode.propertiesを参照しています。
   停止方法はCtrl+Cもしくは本ファイルと同一ディレクトリにServerStopというファイルを作成する
   ※ServerStopファイルが存在するとサーバはDataNodeは起動しません。
   前提:1.java.exeにPATHが通っている
        2.メモリ上限を256MBとしています

 ※execMasterNode2.batを実行すると、スレーブMasterNodeが起動します。
   ポート番号は8889を使用します。
   設定ファイルはclasses\MasterNode2.propertiesを参照しています。

   ・起動サンプルでの構成図
                ┌──────┐      ┌──────┐
                │ マスター   │      │ スレーブ   │
                │ ノード     │      │ マスター   │
                │ Port:8888  │      │ ノード     │
                │            │      │ Port:8889  │
                └───┬──┘      └───┬──┘
                        │┌─────────┘
            ┌─────┴┴───┐
            │                    │
      ┌───────┐ ┌───────┐
      │┌─────┐│ │┌─────┐│
      ││データ    ││ ││データ    ││
      ││ノード    ││ ││ノード    ││
      ││Port:5553 ││ ││Port:5554 ││
      │└─────┘│ │└─────┘│
      │┌─────┐│ │┌─────┐│
      ││スレーブ  ││ ││スレーブ  ││
      ││データ    ││ ││データ    ││
      ││ノード    ││ ││ノード    ││
      ││Port:5556 ││ ││Port:5557 ││
      │└─────┘│ │└─────┘│
      └───────┘ └───────┘

 4.接続サンプル
   簡易的な接続、登録、取得、削除サンプルを用意しています。
   本ファイルと同一ディレクトリにある、TestSock.classを実行してください(jdk1.6にてコンパイル済み)。
   引数なしで実行すると使用方法が出力されます。
   例)
     # 以下の例は自動的にインクリメントするKey値でValue文字列を1000回登録している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 1 127.0.0.1 8888 1000

     # 以下の例はキー値をkey_aでバリュー値value_bを登録
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 1.1 127.0.0.1 key_a value_b

     # 以下の例は自動的にインクリメントするKey値でValue文字列を1000回取得している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2 127.0.0.1 8888 1000

     # 以下の例はキー値をkey_aでvalueを取得
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.1 127.0.0.1 8888 key_a

     # 以下の例はマスターノードIP127.0.0.1,ポート8888とIP127.0.0.1,ポート8889に接続をバランシングして
     # 自動的にインクリメントするKey値でValue文字列を1000回取得している
     # execMasterNode2.batを起動していると、元のexecMasterNode.batのプロセスを終了しても正しく稼動し続ける
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.2 "127.0.0.1:8888,127.0.0.1:8889" 100

     # 以下の例はキー値をkey_aで取得したValue値にJavaScriptを実行し結果を取得
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 2.3 127.0.0.1 8888 key_a "var dataValue; var retValue = dataValue.replace('b', 'scritpChangeRet'); var execRet = '1';"

     # 以下の例は自動的にインクリメントするKey値と適当な4パターンのTag値でValue文字列を100回登録している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 3 127.0.0.1 8888 100

     # 以下の例はTag値「tag1」に紐付くKey値とValue値を1回取得している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 4 127.0.0.1 8888 1 tag1

     # 以下の例はKey値「wordfile」で「C:\temp\SampleWord.doc」ファイルを1回登録している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 5 127.0.0.1 8888 1 C:\temp\SampleWord.doc wordfile

     # 以下の例はKey値「wordfile」のバイトデータを取得し「C:\SampleWord.doc」ファイルとして1回作成している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 6 127.0.0.1 8888 1 C:\SampleWord.doc wordfile

     # 以下の例はKey値「key_a」のデータを削除して、Valueを取得している
     java -cp ./;./classes;./lib/javamail-1.4.1.jar TestSock 8 127.0.0.1 8888 key_a

     PHPに関しては、etc_client\PhpAutoTest.batを参照してください。

[今後]
 今後はバグFixと分散トランザクション(ロック機構)を実現していきます。

