SO_REUSEPORT ソケットオプション
Linux Kernel3.9(BSDでは割と以前から実装されていたけど)で取り込まれたSO_REUSEPORT
ソケットオプションは、異なるプロセスで同一のポートにbindできるようになるので、いろいろおもしろいことができそう。
https://lwn.net/Articles/542629/
これがmerge commit
試しにRubyでSO_REUSEPORT
を使うサーバを書いてみた。
もちろんKernelを3.9以降にアップデートしないと動かない。
Ubuntuだと、script書いてくれてる人がいるので楽にUbuntu Kernelをアップデートできる。
http://dl.dropboxusercontent.com/u/47950494/upubuntu/kernel-3.10.9
require 'socket' SO_REUSEPORT = 15 s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) s.setsockopt(Socket::SOL_SOCKET, SO_REUSEPORT, 1) s.bind(Addrinfo.tcp("127.0.0.1", 10080)) s.listen(5) while true do conn, addr = s.accept puts "Connected to #{Process.pid}" data = conn.recv(1024) conn.send("HTTP/1.1 200 OK", 0) conn.close end
で、これを10プロセスほど起動して、apache benchでつないでみる。
$ for i in `seq 10` do ruby so_reuseport_opt.rb & done $ ab -n 10000 -c 10 localhost:10080/ This is ApacheBench, Version 2.3 <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient)...Connected to 3476 Connected to 3334 Connected to 4875 Connected to 3531 Connected to 3531 Connected to 4879 Connected to 4869 Connected to 2891 Connected to 4870 Connected to 4868 Connected to 4866 ...
カーネル側でバランシングしてくれていることがわかる。 使いどころとして、
- 単純なpreforkモデルでパフォーマンスを出したいアプリケーション
- システム管理系のアプリケーションで、プロセスを落とせないけど、新しいversionを使いたくて、同時に新しいものも起動させてから、古いversionのプロセスを落とす(hotswapしたい)
などが作れそう。
guard文のあれこれ
あるメソッドで、渡されたパラメータによっては処理をまったくしないということがある。 その場合guard文を書くことでネストが深くならなくて、読みやすくなることはイディオムとしてよく知られている。
しかし、guard文を書くと逆に読みにくくなってしまう場合があるんじゃないかと最近仕事のコードを書いていて思った。とくに、必ず特定の値を返すメソッドの場合。 「必ず特定の値を返すメソッド」はどんな時に書くのかと考えると、例えば、Interceptorパターンを実装していて、パラメータは降ってきて特定の条件下では処理はするけど、必ず後続のInterceptorに処理を継続させたくてtrueを返す場合。
public boolean before(AObject o, AMap param) { if (!cond(param)) { return true; } // brabrabra return true; }
二回もreturn文が出てきて気持ち悪いのでここは、guard文を書かないで素直に書くほうが読みやすいんじゃないかと。
public boolean before(AObject o, AMap param) { if (cond(param)) { // brabrabra } return true; }
Mac OS -> Ubuntu
教授がMac OSをメインで使っていた影響で、学生時代からメインマシンにMacを使ってきたけれども、転職を期にUbuntuへ移行しました。
- 職場の開発マシンがUbuntuなので、普段からプログラミングスタイルや開発環境のノウハウを貯める
- xmonadの完成度が高くてプログラミング環境として集中できる(職場にxmonad使い多くて吹いた)
- 別にMac OSが必要な仕事をするわけでもないし、プライベートでも興味がない。結局Xcodeもcommand line toolのインストール時等、数える程しか起動しなかった
など、よくよく考えるとMacでなければならない理由はなくて、手軽にUnixライクな環境が使えればよかった。 とはいえ、純粋なUnixではなくUnixの皮をかぶったDarwinなので、たまに不都合(ビルドがすんなりできないとか)が出たりしたときはつらいなーとか思って諦めてそのまま使ったり、頑張って解決したりしてきたけど、そんな悩みもなくなった(はず)。 切り替えてから1ヶ月ほど経過してるけどいまのところ問題もなく、むしろ快適に感じている。
nodeでRedisを使ってセッション管理
フレームワークにexpressを使う場合、connectが提供するsessionモジュールを使うと楽に実装できる。
さらに、Redisにセッションデータを保存する場合、connect-redis
を使うと楽だ。
https://github.com/visionmedia/connect-redis
他にも、express-session-redis
というモジュールがあるけれども、コードを読んでセンスがあると思ったのは、connect-redis
の方。
connectがセッションストア用インタフェースを作ってくれているので、それを継承して作ったほうがスマート。
Twitterでログインするやつをconnect-redisを使って書いてみた。
nodeでWebアプリを開発するときに考えること
僕は、全体を俯瞰して作り出すので遅いのだけれども、トータルとして同じくらいのスピードになるかなと思って、新しい言語やフレームワークを使うときは、まずは全体で必要になりそうな技術であったり、問題点を潰すことにしています。
フレームワーク
nodeのフレームワークは、たくさんありすぎてどれを使っていいのやら、という状況だけれども express 使っておけば良いというのが僕の中での結論。 ルーティング、テンプレート、セッション管理といった基本的な機能を簡単に使える(connectが素晴らしいというのもあるけど)ので、複雑なことをしたければあとは自分で頑張るほうがいいと思う。
それに、全部読みきれる量であるということ。
使うライブラリのコードを読みきれるというのはすごい重要。フレームワークでサポートしていないことをしたくなったときにどこに手を入れて良いのかがすぐに判断できるから。
また、expressのテストコードはmocha
を使っているので、本体と合わせて読むと、どうやってテストコード書けばいいかも勉強できて良い。
O/Rマッパー
RDBMSを使ってそこそこの規模のWebアプリを書こうと思うと、O/Rマッパーがあるとすっきりする。が、探してみるとActiveRecordのような決定的なものがない。
ぱっと見でよさそうなのが、node-orm2
。
https://github.com/dresende/node-orm2
基本的なクエリに加えて集計関数も投げられるし、has-oneとかhas-manyといった関連も定義でき、フックも充実、さらにexpressとも相性がいい。 Rails界に住んでる人は、モデル毎にファイルを分けて定義したくなるので、手動でロードしないといけないのでちょっと面倒。
var orm = require('orm'), fs = require('fs'); var dbConfig = JSON.parse(fs.readFileSync(__dirname + '/database.json')); var db = orm.connect(dbConfig.dev); fs.readdir(__dirname + "/models", function(err, models) { if (err) { console.log(err); return; } models.forEach(function(model, i) { db.load(model, function(err) { console.log(err); }); }); });
一般的にnodeでRDBMSを操作することはやはり少ないのかなぁ。スロークエリ頻発するともはや非同期IOは意味をなさなくなるというところから、mongoやCouch、Redisを積極的に使う方針にしたほうがよいのだろうか。
migration
RDBMS使うなら、migrationの機構がないと死ねる。node-db-migrate
が使いやすい。Rails流。up/downで操作、データベースにmigrationsテーブル作って管理するところなど。
https://github.com/nearinfinity/node-db-migrate
上記のnode-orm2と相性はまぁまぁ。唯一ぐんにょり来るのが、設定ファイルのプロパティ名が違っていること。
node-orm2では、protocol
だがnode-db-migrateはdriver
になっている。database.jsonで両方記述しておくとうまくいくが、ぐんにょり来る。
あと、Rails界の人は、
updated_at
やcreated_at
は必ず自分で定義しないといけない- サロゲートキーも自分で定義(
id
も自動付与されず自分でPK、AUTO_INCREMENTを指定する)
に注意。
auto loader
hotnode
が今のところ快適に使えている。
コヒスク使う人は、hotcoffee
コマンドで。
シングルページアプリケーション程度の規模なら問題なし。
https://github.com/saschagehlich/hotnode
デバッガ
とりあえず、node-inspector
使っておけ、らしい。
https://github.com/dannycoates/node-inspector
Webkitブラウザ上でサーバサイドのコードをデバッグ実行できるので死ぬほど便利。一度使うと便利すぎてないと生きていけなくなる。
上記のhotnode
とも組み合わせて使える。
$ hotnode --debug app.js # アプリ起動 $ node-inspector # 別ターミナルで実行してChromeなどにアクセスする
ホスティング
普通のWebアプリなら迷わずHerokuだけれども、たとえばWebSocketを使いたい場合Herokuはまだサポートしていない。
https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku
Herokuは1Dynoなら無料で公開できる(ただし750時間までなので注意)ので、これほどまでに使いやすいPaaSはないので諦めるのは辛いところ。 WebSocketの代わりにロングポーリングでも良いのだけれども、iPhoneやiPadだと通信中のぐるぐるが回ったままで、気にする人は気にするので気持ち悪さを与えて結果として使わないということがあるので、なるべくWebSocket使いたい。
とりあえずは、Herokuで作り問題が出て来て(お金に余裕のある人は)、nodejitsuやAWS、VPSあたりを借りるがいいのでは。
デプロイ
nodeアプリを動かすときに、Heroku や nodejitsu といったPaaS使わない場合、手動(capコマンド実行等)/自動(git pushトリガー, jenkinsトリガー)問わずproduction環境にデプロイする必要がある。
Stack Overflowの解答を眺めていると、Capistranoを使っている人が割と多い。
プロセス管理
nodeは、内部でエラーが発生すると、落ちる。イコールサービスが止まる。なので、基本的に自動で上げるためのプロセス管理が必要になる。 まぁこれはいくらでもあるので、monit、god、supervisor等慣れたものを使うのが良いと思う。
あとは、node製のものとして、forever
がある。
nodejitsuはforever使っているとのこと。
Stack Overflowでの解答も「とりあえずforever」という意見が多い。
Rubyの標準ライブラリ及びgemのtagsファイルをロードする
Vimスクリプトを書いた。 https://github.com/yoppi/config/blob/master/vim/dot.vim/ftplugin/ruby/tag.vim
プログラミングしているときに、実装を知りたいときがよくあるので、Vimで素早くタグジャンプできるようにした。 ただ、
- 手動でtagsファイルを生成しておかなければならない https://gist.github.com/yoppi/5508053
- 途中でgemとか追加してももちろん変更されない
という問題があるので、もっと便利なようにtagsファイルを動的に更新していくような感じにしていく。
redisable
というgemを作った。
http://github.com/yoppi/redisable
RubyオブジェクトをRedisとマッピングさせる既存の有名なライブラリに、
- redis-objects(https://github.com/nateware/redis-objects)
- ohm(https://github.com/soveran/ohm)
の2つがあるけど、次の欲しかったものを実現するのが少し遠かったので作った。
- モデル毎に複数のRedisサーバにアクセスできる(sharding)
- オブジェクトをマッピングするというより、そのオブジェクトを通してRedisにアクセスしたい
という意味を込めてredisable
(redis + enable)という名前にしている。