HandlerSocket の Node.js クライアント node-handlersocket リリース

先週,本家 (英語の) InfoQ や PublicKey で紹介されて話題となった HandlerSocket.

InfoQ
InfoQ: MySQL/HandlerSocket and VoltDB: Contenders to NoSQL
InfoQ
InfoQ: NoSQL への挑戦者 - MySQL/HandlerSocket と VoltDB
Publickey
NoSQLとしてMySQLを使うDeNAが、memcachedよりも高速な75万クエリ/秒を実現


この HandlerSocket は C++ で書かれたクライアントライブラリと,その Perl バインディングが提供されているのですが,同時にプロトコル仕様

が公開されていることもあって,既に第三者によって RubyPythonPHPJava などのバインディングあるいはネイティブ実装のクライアントが公開されています.
自分が見つけたのはこんな感じ.


そんなわけで (どんなわけで?),Node.js 向けの Pura JavaScript クライアントライブラリ,node-handlersocket を作ってみました.

JavaScsript 弱者 && できたてほやほやなので品質的には自信ないです.突っ込み歓迎です.


npm からインストールすることもできるはず.

npm install node-handlersocket


使い方はこんな感じ.

var hs = require('node-handlersocket');

var con = hs.connect();
con.on('connect', function() {
  con.openIndex('test', 'EMPLOYEE', 'PRIMARY', [ 'EMPLOYEE_ID', 'EMPLOYEE_NO',
    'EMPLOYEE_NAME' ], function(err, index) {
    index.find('=', 1, function(err, results) {
      console.log(results[0]);
      con.end();
    });
  });
});

他言語のクライアントは専ら本家の Perl クライアントと同じような API になっているようですが,Node.js は非同期な API にしなくちゃということで,独自の API になってます.
できるだけ Node.js の標準モジュールと同じような API にしたつもり.
以下簡単に API を紹介.


まずは HandlerSocket サーバに接続します.

var con = hs.connect();

デフォルトでは localhost の 9998 番ポートに接続します.変更する場合は

var con = hs.connect({ host : 'hostname', port : 9999 });

のようにします.host だけ,port だけの指定も可.
connect() の戻り値は Connection オブジェクトで,Node.js の EventEmitterインスタンスです.
そんなわけで (どんなわけで?),

con.on('connect', function() {
  ...
});

で接続の完了を待ちます.


次にインデックスをオープンします.

  con.openIndex('test', 'EMPLOYEE', 'PRIMARY', [ 'EMPLOYEE_ID', 'EMPLOYEE_NO',
    'EMPLOYEE_NAME' ], function(err, index) {
    ...
  });

openIndex()シグネチャ

  • openIndex(database, table, index, columns, callback)

となっていて,引数は順にデータベース名,テーブル名,インデックス名 (主キーは 'PRIMARY'),後で検索で取得したり更新対象となるカラム名の配列,コールバック関数です.
コールバック関数のシグネチャ

  • function(err, index)

で,インデックスのオープンが成功すると errnull となり,indexIndex オブジェクトが渡されます.
インデックスのオープンが失敗すると,err には Error オブジェクトが渡されます.


インデックスを取得すると検索や更新を行うことができます.

    index.find('=', 1, function(err, result) {
      ...
    });

検索を行う find()シグネチャ

  • find(op, keys, [limit, [offset] ], callback)

となっていて,引数は順にオペレータ,インデックス値の配列,取得する行数の上限 (デフォルトは 1),取得する際に読み飛ばす行数 (デフォルトは 0),コールバック関数です.
オペレータは '=''>''>=''<''<=' を指定することができます.'=''>''>=' を指定した場合の結果は昇順,'<''<=' を指定した場合の結果は降順になります.
コールバック関数のシグネチャ

  • function(err, results)

で,検索が成功すると errnull となり,results には openIndex()columns で指定したカラム値の配列 (レコード) からなる配列 (レコードの配列) が渡されます.検索結果が 0 件の場合,results は空の配列となります.
検索が失敗すると,err には Error オブジェクトが渡されます.


更新を行う update() と削除を行う remove() は検索と似ていて,それぞれのシグネチャ

  • update(op, keys, [limit, [offset] ], values, callback)

  • remove(op, keys, [limit, [offset] ], callback)


となっていて,values 以外は find() と同じです.
values には openIndex()columns で指定したカラム値の配列を指定します.
コールバック関数のシグネチャ

  • function(err, rows)

で,検索が成功すると errnull となり,rows には更新または削除した行数が渡されます.
混信または削除が失敗すると,err には Error オブジェクトが渡されます.


挿入を行う insert()シグネチャ

  • insert(values, callback)

となっていて,values には openIndex()columns で指定したカラム値の配列を指定します.
コールバック関数のシグネチャは更新および削除と同じです.


ドキュメントはこれから頑張ります.誰か 翻訳 頼む
詳細は本家 HandlerSocket のドキュメントを参照してください.


他言語クライアントの場合,複数のリクエストをパイプライニングするために execute_multi メソッドがあったりしますが,node-handlersocket は全部非同期のため,コールバックされる結果を待たずに連続してメソッドを呼び出すだけでパイプライニングされます.
また,レスポンスが全部揃う前に個々のレスポンスごとにコールバックされるので効率面で多少有利かも.


API というかコールバックは標準の fs モジュールなどに合わせてあるので,制御フローを簡素化するライブラリと組み合わせることも簡単じゃないかと思います.
例えば Node.js のパッケージマネージャ npm の作者である Isaac Schlueter さんによる a tiny flow control library 「Slide」

と組み合わせると,

var hs = require('../lib/node-handlersocket'), asyncMap = require('slide/async-map');

var indexDefs = [
  ['test', 'EMPLOYEE', 'PRIMARY', [ 'EMPLOYEE_ID', 'EMPLOYEE_NO', 'EMPLOYEE_NAME' ]],
  ['test', 'DEPARTMENT', 'PRIMARY', ['DEPARTMENT_ID', 'DEPARTMENT_NO', 'DEPARTMENT_NAME']],
  ['test', 'ADDRESS', 'PRIMARY', ['ADDRESS_ID', 'STREET']]
];

var con = hs.connect();
con.on('connect', function() {
  asyncMap(indexDefs, function(indexDef, callback) {
    con.openIndex.apply(con, indexDef.concat(callback))
  }, function(err, indices) {
    ...
  });
});

という感じで,indexDefs に定義したインデックスが全部オープンし終わると,(最後の) コールバックに Index の配列が渡されるみたいな.この場合の openIndex() もパイプライニングされます.試してないけど. 試しました.


ということで,注目の HandlerSocket と注目の Node.js の組み合わせを楽しんでみてください.