nodeをv0.8系からv4系にアップデートした話

リクルートテクノロジー社で自社サービスをv0.8からv4系にアップデートしたという話を目にしたのだが、偶然うちも同じようにv0.8からv4にアップデートしている。当時のメモが出てきたのでここに記しておく。なお、すでに5系が出ていたが新規開発ではないし人的リソースも足りないので、LTSで妥協した。

アップデート後にメモリ消費が半分、ロードアベレージが1/3ぐらいに減少した。しかしレスポンスタイムは全く改善せず。まあ、WebアプリというやつはDBアクセスが律速段階なので仕方ないだろう。

バージョンアップで出現した問題は以下のとおり。修正はほぼ1日。もともとのコードがモダンに書かれており、テストコードがあったことも幸いした。

  • 管理ツールLDAPログインできない
  • ステージング環境でAPIリクエストが失敗する
  • 発生源不明のuncaughtException
  • ビルドサーバでnam installがエラー
  • process.nextTickをsetImmediateに置き換えたら動かなくなった
  • 外部リソースへのリクエスト(Webdavへの保存)が発生しない

管理ツールにログインできない

  • node-LDAPというモジュールがv4に対応していなかったため
  • ldapjsというモジュールに変更して解決
    • 後から知ったがnode-LDAPldap-clientという名前に変わっており、そちらはv4でも動いたらしい

テストサーバでAPIリクエストが失敗する

  • テストのAPIサーバがオレオレ証明書を使っていたため接続を拒否していた(v0.12でセキュリティの強化がされたらしい)
  • テスト環境のみrejectUnauthorized: falseオプションを付与してオレオレを許可するようにした
    • オレオレは微妙だが外部へのアクセスが制限されているサーバなので、仮にDNSが汚染されても問題ないであろう…
var req = http.request({
    protocol: 'https:',
    host: 'api.endpoint.example.com',
    port: 443,
    path: '/',
    method: 'GET',
    rejectUnauthorized: false
}, (res) => {
    console.log('status:', res.statusCode);
}

発生源不明のuncaughtException

  • HTTP APIの呼び出しでrequest#setTimeoutにundefinedが渡っておりエラー、v0.8系ではエラーにならず無視されていた。
  • バグなので修正、想定の5000msを渡すように変更

ビルドサーバでnam installがエラー

  • v8がC++11依存になっており、CentOS 6のGCCが古いためv8.hを読み込むネイティブモジュールがビルドできなかった
  • GCCをアップデートして対応

process.nextTickをsetImmediateに置き換えたら動かなくなった

  • バッファ処理が完了する前に、次のIOイベント(on data)が呼ばれてバッファ内容が想定外の状態になって正しく動作しなかった。nextTickはIOイベント前に呼ばれるので問題なかった。
  • バグなのでコードを直して対応

APIリクエストが発生しない

  • 以下のコードが動かなくなった
client.request(apiEndpointData, (res) => {
   // 何もしないのでコメント
   // res.on('data', (chunk) => {});

   res.on('end', () => {
      callback();   // このコールバックで次のAPIをコール 
   });
});
  • responseのendイベントは v0.8はTCP FINを受けた時点で発生する、v4はdataイベントで取得できるデータが無くなったら発生、つまりdataイベントを呼ばないと発生しなくなった
  • endイベントを待たずにcallbackを呼ぶように修正

自分でbuildしたrubyをrbenvで管理する

昔々のrails(2.3系)を動かしたくなったので古いrubyが必要になったのだが、その古いrubyruby-buildでビルドできなかったので自分でビルドしてrbenvで管理させることにした。

$(rbenv root)/versions の下にインストールするとrbenvの管理対象になるので、自分でビルドする場合は configure時に --prefix を指定すれば良い

export CPPFLAGS=-I/opt/X11/include
./configure --prefix=$(rbenv root)/versions/1.8.7
make
make install 

本来rbenvは、rubyのバージョン切り替えツールであってビルドツールではない(だからruby-buildはプラグイン扱い)。この使い方は通常の利用法で特殊なハックではない。

gem

gemもインストールする。rails 2.3はgem 2系では動かないのでgithubからgem 1.8.30をダウンロードする。setupする前にrbenvでバージョン指定を忘れずに

rbenv local 1.8.7-p375
ruby setup.rb

openssl

opensslが有効になっていなかったので有効にした。rubyのソースフォルダのext/opensslへ行ってビルド、インストール。

cd ext/openssl
rbenv local 1.8.7-p375
ruby extconf.rb --with-openssl-dir=/usr/local/opt/openssl
make 
make install

※ --with-openssl-dirに指定されているパスはhomebrewでopensslをインストールした場合のパス