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 バインディングが提供されているのですが,同時にプロトコル仕様
が公開されていることもあって,既に第三者によって Ruby や Python,PHP,Java などのバインディングあるいはネイティブ実装のクライアントが公開されています.
自分が見つけたのはこんな感じ.
- Ruby https://github.com/miyucy/handlersocket
- Python http://pypi.python.org/pypi/handlersocket/
- PHP http://lab.klab.org/young/2010/10/handlersocket-plugin-for-mysqlのphpクライアントを公開しました/
- Java http://ameblo.jp/just-do-neet/entry-10682348973.html
そんなわけで (どんなわけで?),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)
で,インデックスのオープンが成功すると err
は null
となり,index
に Index
オブジェクトが渡されます.
インデックスのオープンが失敗すると,err
には Error
オブジェクトが渡されます.
インデックスを取得すると検索や更新を行うことができます.
index.find('=', 1, function(err, result) { ... });
検索を行う find()
のシグネチャは
find(op, keys, [limit, [offset] ], callback)
となっていて,引数は順にオペレータ,インデックス値の配列,取得する行数の上限 (デフォルトは 1),取得する際に読み飛ばす行数 (デフォルトは 0),コールバック関数です.
オペレータは '='
,'>'
,'>='
,'<'
,'<='
を指定することができます.'='
,'>'
,'>='
を指定した場合の結果は昇順,'<'
,'<='
を指定した場合の結果は降順になります.
コールバック関数のシグネチャは
function(err, results)
で,検索が成功すると err
は null
となり,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)
で,検索が成功すると err
は null
となり,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 の組み合わせを楽しんでみてください.