-
終わりました
結局、だめでした。転職活動を開始して、今回やろうと思っていた同人ソーシャルゲーム開発なるものは、終了することにしました。一時期ゲーム屋をやめようと思いましたが、それすらもできないようなので、また似たような試みをすることになるかもしれませんが、ひとまず、このプロジェクトは終了させます。
-
最古層のコミュニティー
http://lists.freebsd.org/pipermail/freebsd-performance/2011-December/004368.html
FreeBSDのメーリングリストを眺めたりしているのですが、このスレッドが熱いですね。議論は今も続いています。内容としてはlinuxとベンチマークで比較されて大敗したけどどうしよう、というもの。
はたから見て、おいおいこんなに喧嘩して大丈夫かと思ったりしたのですが、
http://wiki.freebsd.org/BenchmarkAdvice
みたいに、ベンチマークの取り方についてwikiにまとめるとか、建設的な方にまとまっていって、ちょっと感動的でした。node.jsという最新のコミュニティーとは若干違った空気が感じられて、面白いです。
12月 23, 2011に公開 with 8リアクション
-
redisとnode.js
対象読者
- node.js/npmはインストール済み
- macなどの*nixを持っている
- redisはよくわからない

redisのいいところ
- 小さい! 持ち運びしやすい! 良い意味で何もない!
- 純粋KVSより少し豊かなほどよい甘み
- 簡素ですので、パフォーマンスなどに関して色々「狙いやすい」
- あの、express作者さんもよく使ってらっしゃいますよ
僕みたいなコンシューマーゲーム難民に是非オススメしたい、組み込み系の薫りとwebっ子っぽいカジュアルな感覚が同居したシンプルDBとなっております。
redis本体
ダウンロードページから最新版(この文章を書いている時点では2.4.4)をダウンロードして、下記のように一行makeと打てば終わりです。
make続けてインストールするか否かはお任せします。この段階で、「展開ディレクトリ/src/redis-server」がありますので、起動しておきます。これで、redisの利用準備完了です。redisがさっぱりという方はここで、「展開ディレクトリ/src/redis-cli」を使って、実際に動作を試されることをおすすめします。
node_redis
client一覧にあるように、node.jsのredisクライアントは「node_redis」に星がついています。これが標準っぽいので、これを使いましょう。いつのものように、
npm install redisで、okです。
hiredis
上記node_redisのgithubページに行くといきなりhiredisというのが現れますが学習当初は不要だと思います。hiredisはredisの応答をパースする高速ネイティブライブラリでなくても動きます。もちろん、
npm install hiredisで、さっくりインストールできるので、使わない手はないのですが。
examples!
準備はできたので、公式チュートリアルを眺めたり、ここなんかをいじったり、走らせたりすると、それほど苦もなくnode_redisはわかると思います。あ、redis超入門は基本的にこれで終わりです。redisコマンド自体は単純ですからね。
よく引っかかるポイント
ここで、僕が当初引っかかったポイントを纏めます。
- redisの公式ドキュメントは実はreplになっているので、redisを立ち上げたり、インストールすること無く、ブラウザだけでredisの学習ができる(下図参照)
- 実はget/setの2コマンドだけで、大体KVSとして使えてしまう
- 実はJSON.stringify/parseを組み合わせて文字列を投入すれば、結構複雑なデータもredisに任せられる
- その他、色々あるコマンドやデータ型は複数のプロセス間で連携するためにあるだけなので、redisに慣れるまで放置でも問題なし

個人的には特に3.が盲点でした。これに気づいて、あ、もうこれで大体大丈夫じゃん、ということで、何でもredisにお願いする流れになってしまいました。
ざっと、こんなもんです。ええっと、それから、実際にredisとつきあいはじめると単純なコマンドの動きではない所が気になり始めますので、それについて、下記でまとめたいと思います。
中身
redis本体/node_redisともに、それほどドキュメントがありませんので、ハックが欠かせません。逆に、ハックがしやすい簡素なシステムであることがredisの良さだとも思うのですが。
というわけで、ハックのお役立ち情報をまとめます。
仕組みがわかれば怖くない
ちょっと、commands.jsを御覧ください。ここにはredisのコマンド名が列挙されております。コマンド「名」だけです。はい。実際にredis.get/setのようにredisクライアントのメンバ関数としてアクセスする関数は全てこの配列から生成されております。
実は、redisの通信プロトコルは極めて簡素でして、コマンド名と引数が決まったら、その実装は大体共通みたいなんです。というわけで、ここらへんにある、
commands.forEach(function (command) { RedisClient.prototype[command] = function (args, callback) { if (Array.isArray(args) && typeof callback === "function") { return this.send_command(command, args, callback); } else { return this.send_command(command, to_array(arguments)); } }; RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command]; Multi.prototype[command] = function () { this.queue.push([command].concat(to_array(arguments))); return this; }; Multi.prototype[command.toUpperCase()] = Multi.prototype[command]; });を見ていただければわかるように、すべての関数がcommandsからsend_command関数を使って生成されています。要するに、node_redisの関数はほとんどsend_commandなんです。ですから、node_redisで困ったことが起きたり、挙動を確認したいということになったら、
- 通信開始/終了までの流れをよく確認する(認証とか、ここは色々罠がある)
- send_commandの動きを確認する
の、2つでほとんど解決できます。
nodeだからpiplining!
redis高速化の肝として、ある時期からpipeliningが推奨され始めました。要するに応答を待たずにコマンドを送りまくろうというものです。これ、他の言語なら結構大変(java版クライアントのjedisを少し眺めたことがありますが、パイプライン処理用に手のこんだことをしているみたいです)なのですがnode.jsなら、デフォでpipeliningです。非同期IOのnodeですから、そもそも待ちません。むしろ、nodeに慣れ親しんだ方なら、redis本家のpipelinineの文章が何を言いたいのか当たり前すぎて分からないかもしれません。こんなコードを見てもらえるとpipeliningと非pipelining(=通常)の違いがわかってもらえるのではないでしょうか。
// 非pipelining = redis的に普通の処理 redis.incr('x', function() { redis.incr('x', function() { redis.incr('x', function() { redis.incr('x', function() { //hoge }); }); }); }); }); // redis的なpipelining redis.incr('x', function() {}); // この場合コールバックは不要ですがなんとなく書きました redis.incr('x', function() {}); redis.incr('x', function() {}); redis.incr('x', function() {//hoge});pipeliningと非pipeliningの書き分けがとても自然です。時代はnode.jsであることをここらへんでも感じさせてくれますね。ただ、今後計画されているredis clusterなどによってDB処理待ちの仕方が影響を受けると思われる(僕の脳内では……)ので、その点はご注意を。
まだまだredisがわからない!
意外にredisのソースをハックすると楽に答えにたどり着ける場合があります。例えば、僕はreplicationについて疑問に思ったことがあったのですが、なかなかドキュメントが見つかりませんでした。あるのかもしれませんが、僕の英語力では無理でした。でも、grepしたらあっさり。replication.cなんてまんまなファイルの冒頭にあり、grepを何回かしたらなんとなくわかりました。redisのソースは読みやすいです。そこらへんの作者さんの思いというのはここらへんで読めます。
unit test時の注意点
おまけです。テスト時に個人的にはまった点を残しておきます。みなさんも、お忘れなく。
- redisClient.on(‘ready’)で待ちましょう
- クライアントを作っては捨てることを繰り返す場合はend()を
- クライアントを使いまわすというやり方もあります
ちなみに僕はredisのmockを自作して、ほとんどそれで済ませています。
さて、次の方……
い、いなくね? う、見落としてるだけかなあ。誰かー。誰かー。
12月 18, 2011に公開 with 11リアクション
-
例外を伝播させた理由とdomains期待age
僕は組み込みC/C++の世界からやってきましたので、例外をそれほど多用しておりませんでした。コールバックもjs程気楽に使えませんでしたので、逆にnode.jsの世界にやってきてついつい調子に乗って関数をたくさん書いておりました。しばらくして、僕は途方に暮れました。今回はそのお話とdomainsに対する期待をまとめます。domainsのそもそもの目的などを誤解しているかもしれませんが、とりあえず。
try〜catchで囲めない
僕が躓いたのは下記のような状況でして、例外をどう補足するかという問題です(一応確認となりますが、下記状況では末尾のcatchで中央部分から発生した例外を補足できません)。
try { db.insert('foo', 'bar', function(result) { db.find('taro-id', function(result) { //... db.insert('jiro-friends', result, function(result) { throw new Error('hoge'); }); }); }); } catch(e) { expect(e.message).toBe('hoge'); }node.jsは非同期IOをコールバックで処理するので、非同期処理ライブラリ(flowとかasyncとかのキーワードでnpmを探すと色々出てきます)が幾つかあります。僕が一番お世話になったのが、stepというやつで、だいたいこんな感じで書きます。
step( function() { db.insert('foo', 'bar', this); }, function(err, result) { if (err) {throw err;} db.find('taro-id', this); }, function(err, result) { if (err) {throw err;} db.insert('jiro-friends', result, this); }, function(err, result) { if (err) {throw err;} throw new Error('hoge'); }, function _catch(err, result) { expect(e.message).toBe('hoge'); } );各関数をstepがtry/catchで囲んでくれていて、次の関数にリレーしてくれます。それにより上記コード末尾の_catch関数で例外が受け取れるという寸法です。でも、これは2つ問題(疑問)があって、
- コールバックがfunction(err, result)という引数の関数に限定される(fsがこのスタイルなので、悪くはない)
- 全関数内でif(err){throw err;}を書かなければならない
このstepのやり方がほんとうに良いのか悩みました。他にもこのような非同期ワークフロー処理はいくつかあるのですが、それらは例外について気にしないという態度で一貫しておりまして、それこそが簡素で望ましい対応だと思うのですが、僕は躊躇しておりました。
ログ
皆さんweb apiのバグ追跡をどうなさっておられますでしょうか。web業界は素人なので、世の中の相場はわかっていないのですが、とりあえず僕はログに頼っています。現在のお仕事ではnode.jsは使っておらず、スレッドベースのシステムですので、try〜catchが使えます。ですからなにか起きたときに、関連する情報をまとめて吐き出して、障害発生時に確認すると大体原因がわかるという寸法です。web api障害を報告してくれるログシステムは僕的には絶対に欲しいのですが、グローバルのエラーハンドラであるprocess.on(‘uncaughtException’, function(e));などで捉えると、その発生位置はスタックトレースからわかるのですが、それを引き起こしたのがどんなweb api呼び出しだったかさっぱり分かりません。スタックが断裂しておりますので。
僕のやりたいことは以下のようなことでして、
http.createServer(function(req, res) { var webApiName = req.urlを何やら加工, webApiFlow = dispatchWebApi(webApiName); try { webApiFlow(); } catch (e) { log(webApiName + reqなどからの情報 + eの情報); } });でも、これができない。
そこで、step.jsを改造して、こんなのを自作しました。
http.createServer(function(req, res) { var webApiName = req.urlを何やら加工, webApiFlow = dispatchWebApi(webApiName); async([ webApiFlow(); ], function _catch(e) { log(webApiName + reqなどからの情報 + eの情報); }); });async内のasync内で例外が発生しても正しく補足できるように、入れ子にもサポートしています。でもこれも我流だし、なんだか大げさな解決手段をとっている気がして、あまり気分良くは思っておりませんでした。
domains
と思ったら、例外に対する取り組みが0.8に向けて始まったとのこと。僕が@koichikさんのプレゼンシートを誤解していなければ、上記のような苦労も要らなくなる日が来るようです。期待せずに入られません。もし、domainsが僕の期待通りに仕上がったら、移住先として、slideを検討しています。先ほどの疑問としてあげた2つが下記のように解決されるのではと勝手に期待しています。
- コールバックがfunction(err, result)という引数の関数に限定される→slideに引数を適当にバインドする仕組みがあると思われる
- 全関数内でif(err){throw err;}を書かなければならない→domainsで例外問題は解決!
非同期フローと例外処理の問題で悩まれている方々への、ひとつの事例紹介となれば幸いです。
12月 16, 2011に公開 with 20リアクション
-
ライブラリとアプリケーションの同時開発とnode-dev
140文字で収まり切らないので、ここに書きます。
node-devでテストを2つ待機させた状態にしてます。よくゲームエンジンとアプリケーションを同時開発するということをゲーム屋さんはすると思いますが、node-devの場合、依存ファイルを見て再起動するので、テスト実行が最短で済みます。
詳細
大体以下のようなディレクトリ構成。
- game-engine
- lib
- spec
- game-app
- src
- spec
で、
- ターミナル1
- cd game-engine
- node-dev [お好きなTDD/BDD] spec
- ターミナル2
- cd game-app
- node-dev [お好きなTDD/BDD] spec
としておけば、game-engine側に変更があった場合は、game-appとgame-engineが両方走り、game-appのみに変更があった場合はgame-appのテストが走るという寸法です。
以上、コネタでした。
12月 13, 2011に公開 with 1リアクション
- game-engine
-
hat benchmark
benchmark.jsを利用し、@substack氏のhatを計測して見ました。
いきなり結果
default hat x 361,823 ops/sec ±2.35% (58 runs sampled) hat 256 x 196,071 ops/sec ±0.51% (54 runs sampled) Math.random x 54,525,799 ops/sec ±2.54% (50 runs sampled) Fastest is Math.random動作環境は

利用コード
var suite = new require('benchmark').Suite(), hat = require('hat'); suite.add('default hat', function() { hat() }).add('hat 256', function() { hat(256); }).add('Math.random', function() { Math.random(); }).on('cycle', function(event, bench) { console.log(String(bench)); }).on('complete', function() { console.log('Fastest is ' + this.filter('fastest').pluck('name')); }).run({ 'async': true });はじめてなので、benchmark.jsの使い方を間違っていたら、ごめんなさい。
12月 11, 2011に公開 with 4リアクション
-
browserifyで自分のライブラリを参照する
browserifyがブラウザ上で使う参照パスと、サーバー内で利用する参照パスは同じにしたいものです。ですが、これが、browserfiyを利用する自ライブラリ内となると循環参照みたいな話になってとても厄介です。それでも、サーバーコードなら、NODE_PATHを使えば良いのですが、browserifyはどうすれば良いのでしょう。
basedir
以前の記事でbrowserifyはresolveパッケージ(node.js標準のrequire関数ではありません)を利用していると書きましたが、そのなかを読むと、basedirを指定すれば良いことがわかります。参照側はこれでよし。でも、よく読むと、「basedir/node_modules/捜索対象」というディレクトリ構造を期待しているように思えます。
俺ライブラリ/node_modules/俺ライブラリ
しばらく、node_modules内にln -sで自ライブラリの参照を置いていました。でもこれ、npmがおかしな挙動をするのです。そこで、browserify用にln_to_meというディレクトリを新設しました。これで、「俺ライブラリ/ln_to_me/node_modules/俺ライブラリ」という構造を作り、basedirに’./ln_to_me’を指定します。それを、下記のように呼び出します。
browserify.require('俺ライブラリ/何か', {basedir: './ln_to_me'});元々この問題はbrowserifyを使ったシステムのユニットテストで起きていた問題無ので、それをこの対応で適当に回避し、サーバー部分は上記のln_to_meを使ったディレクトリ構造を期待しなくても動作するように相対パスで参照解決を行っています。ここらへんはまだまだnodeやbrowserifyとのつきあいかたが見えておらず、暗中模索です。
残された課題
ディレクトリの循環参照はvimがたまに変な動きをします。次はこれを何とか回避する方法を考えたいですね。
-
node-devに対する僕の誤解
僕と同じ過ちを皆さんに繰り返して欲しくないので、恥の記録を残します。
トラブル
- jasmineをコード変更時に再起動して、自動テストを回したかった
- 元々nodemonを改造した自前のシステムを使っていたが、周りに合わせてnode-devに乗り換えることにした
- 再起動してくれませんでした
やりたいこととしては、こんな感じのことをブラウザではなくコンソールでやりたいわけです。
原因追跡→誤解
今日現在ここらへんにある、下記のようなコードが問題で、
server.on('exit', function (code) { if (code == 101) {終了コードを判定していますが、0で正常終了した場合は、node-devは関知しないみたいです。node-devというものを誤解しておりました。
なぜこうなのかを考えてみたのですが、そもそも、node-devはサーバープロセスの再起動が主目的なので、監視対象はlistenしていることを前提としているため、正常終了するプロセスは想定外なのだとおもいます(逆に、正常終了したプロセスまで再起動すると別な問題が出るのかもしれません)。終了コード101が何か使えそうな気もしますが、これはnode-dev自身がファイル変更を検知した際に再起動をする時に使うものなので、今回の目的には使えないようです。
対処
この分野詳しくないので、できるだけ、node-devをいじらずに、今の自分で分かる範囲で対応することにしました。
- 単に落ちないようにして、しのぐ
- 下記のようなidle.jsを作成し、node-devが触るプロセスは落ちないようにしました
- 待ち方はポートを占有するよりもタイマーを使うほうが穏当と考え、setIntervalを使った無限ループとしました
- node-dev テスト対象→node-dev idle.js テスト対象 に呼び出し方が変わります
idle.js
var fs = require('fs'), path = require('path'), args = [].concat(process.argv), cmd = args.shift(), nextArgv = args.slice(1); setInterval(function() {}, 1234567890); process.argv = [cmd].concat(nextArgv); require(path.resolve(nextArgv[0]));次に、これだけだとjasmineが再起動用終了コード101を握りつぶしてしまうので、下記のような変更を加えます。
jasmine-node/cli.js 変更前
process.on("exit", onExit); function onExit() { process.removeListener("exit", onExit); process.exit(exitCode); }jasmine-node/cli.js 変更後
process.on("exit", onExit); function onExit(code) { process.removeListener("exit", onExit); process.exit(code || exitCode); }当面はオレオレcli.jsを作って、回避することにします。
結果
とりあえず、コード修正時にjasmineを自動再起動することには成功しました。この対応も間違えているかもしれませんが、その際はまた報告します。
12月 4, 2011に公開 with 1リアクション
-
browserify概説
browserify(ぶらうぜりふぁい、と発音しているのを誰かから聞きました)は、その便利さに比して、利用例などが少ないため、お粗末ながら、今日までにわかったことをまとめてみようと思いました。ドキュメントが足りない状況はしばらく変わらないと思いますので、ハックヒント用に内部構造を含めて、概要を伝えられればと思います。
そもそもbrowserifyとは
御存知の通りnode.jsはブラウザでも動作するjsで記述します。ですから、サーバーとクライアントで共有できるコードは共有したいものです。ですが、それには幾つかの壁がありまして、思いつく分だけを書きますとこのぐらいになります。
- モジュールシステム(CommonJS)
- node.jsの組み込みモジュール
- http要求を受け取って、ブラウザへコードを届ける仕組み
これらを提供するのがbrowserifyです。
処理の概要
browserifyの流れを大雑把にまとめると以下のようになります。
- browserifyモジュールをrequire
- 設定を渡すとbrowserifyインスタンスが取得できる
- 注意! ここで、ブラウザに配布するモジュールをbrowserifyに登録します
- connect/expressのミドルウェアとしてbroserifyインスタンスを登録
- htmlから < script src=’/browserify.js’ > で読み込むと、3で指定したモジュール群が連結されたものが一気に届きます
これで、サーバーからコードが送られ、ブラウザでjsが動作することになります。
概要は以上になりますが、もう少し踏み込んで解説を試みようと思います。
各目的の実現方法
作者の@substack氏は自分のシステムを細分化して、npmに公開するのが趣味みたいな方でして、僕の知る限り世界で最もnpmにモジュールを供給している人物です。

そんな氏ですから、このbrowserifyも大変細分化されています。
前述の「壁」をどのような
- モジュールシステム(CommonJS)
- resolve : node.jsのルールに従ってモジュール名からパスを解決します
- detective : 解決されたパスに従ってjsファイルを読み込んだら、その文字列をこのdetectiveに渡します。すると、文字列中のrequire呼び出しが列挙され、返されます
- deputy : detectiveと連動し、キャッシング機構を提供します
- browserify/wrappers : browserifyのwrappersディレクトリにあるテンプレートを使って読み込んだjsコードをくるみます。このことにより、ブラウザに送信された際にnode.js上と同様にrequireでモジュールを取得できるようにします
- その他のモジュール : だいぶ量があるので割愛します
- node.jsの組み込みモジュール
- browserify/buitins : node.jsの幾つかのモジュールはbrowserifyのbuiltinsディレクトリで実装されています。が、ほとんどファイルがあるだけで中身がありません(トラブルが起きた際は参照してください)
- http-browserify : httpモジュールは、実はbrowserifyとは別のモジュールとして提供されています。browserifyと衝突するモジュールなどはこのように別モジュールとして提供されているのが幾つかありますので、ご注意ください。また、この手のモジュールはaliasなどを使って元々のモジュール名で解決されるように設定を行なってください(require(‘browserify’)({require: {http: ‘http-browserify}})など)
- http要求を受け取って、ブラウザへコードを届ける仕組み
- connect/expressのミドルウェアとして、browserify自身が動作し、GET /browserify.js要求に対して、事前に指定されたモジュールを全て連結したものをブラウザに送ります
未整理の話題
- zombie.js と協調動作させて、ユニットテスト内で動作させることも確認していますが、こちらは、zombieやjsdomが厄介なので、別の機会にまとめさせてもらえればと思います
- browserifyのreadme.mdによるとaddEntryは引数がないことになっていますが、実際はあります。lib/wrap.jsを参考にしたり、resolveモジュールのreadme.mdがヒントになると思います
- node.jsでは、アプリケーションコードをsrcなどのディレクトリに置き、依存ライブラリはnode_modulesの下に置くというスタイルが一般的ですが、アプリケーションコードをbrowserifyでブラウザに送り届けるには、アプリケーションをnode_modulesに置くか、環境変数を指定する必要があります。これはaddEntryの第二引数で回避できるようです
-
browserifyとbrowserbuild
今日はブラウザにjsコードを送り届けるための2つのライブラリについてです。
browserbuildとlearnboost
learnboostの人たちは出来る人達です。

彼らはとても謙虚で、browserbuildにおいてもrequireをマッチングしてたどろうなどとは思わないようです。ディレクトリをたどって、関連するファイルを纏めます。うっかりバグったり、トリッキーな場面に出くわしても、そもそも隠すべきコードをディレクトリ単位で分けているので、不用意にサーバー側のコードが配信されるという可能性は低そうです。利口な人たちが選択しそうなやり方で、憧れます。
browserifyと@substack
一方、僕がとても親近感を持つ@substack氏は

node-detectiveなんてものを作って、browsefiyを作るパワープレイヤー。そこがしびれる憧れる。
僕は今、browserifyの根幹であるrequireのパスが通っていなければコードをまとめられないという大前提にはまっていて、アプリケーションコードをrequireのパスが通る場所(すなわち、手を加えなければnode_moduleの下)に置くという屈辱的な対応をしており、何か対策はないか考えているところです。node-detectiveでも利用して解決できると良いのですが。