読者です 読者をやめる 読者になる 読者になる

hakobera's blog

技術メモ。たまに雑談

Github Issues を利用したリリースマネージャのお仕事

最近、Quipper という会社で「リリースマネージャ」という名前のお仕事をしています。開発以外の仕事は久しぶりだったので大変でしたが、最終的にそれなり上手く行った方法を振り返りとしてブログに書いておくことにします。

経緯

自分のチームとは別のチームが開発しているサービスのリリースが迫っている中、それまで開発者の1人がリリース管理っぽいことをやっていたのですが、さすがに開発と管理の二足のわらじが辛くなってきたとのことで、急遽サポート的に自分が「リリースマネージャ」という役割りで参加することになりました。

コンセプト

コンセプトは「使用するツールを増やさない」です。 管理のために新しいツールを増やすと、その使い方を教えるなど新たなタスクが発生してしまいます。タスクを減らすためにタスクが増えるなんてナンセンスです。

ということでQuipper では普段の開発に Github を利用しているので、自然と Github Issues を利用することにしました。 (正確には既に Github Issues が使われていたので、それを整理する方向で調整しました)

Quipper の開発スタイルとか開発ワークフローについては、既に同僚の方は書いているので、そちらを参考にしてください。

ルーク、 MongoLab を使え! - @kyanny's blog

非開発者もGitHub Flowに巻き込んでみんなハッピーになった話 - Masatomo Nakano Blog

Github Issues の使い方

ポイントはマイルストーンとラベルの使い方です。

マイルストーン

Issue は必ずマイルストーンに紐付けます。 マイルストーンに紐付けるという作業をすることで、Issue が放置されるのを防ぎます。 後でやるかもしれないがリリースには含めない、という Issue は「チェックはしたけど、意図的に先送りした」という印として、「nice to have」という名前のマイルストーンに紐付けておきます。

マイルストーンは絶対なので、原則リリース日はずらさず、消化しきれなかった Issue はマイルストーンを移動させます。

ラベル

ラベルは主に以下を利用します。

from-customer クライアントからの問い合わせをトリガーに作成された Issue。この時点ではバグかどうかはわからないが、取りこぼさないようにとりあえず Issue として作成する
bug 社内で気が付いた Issue、もしくは from-customer の Issue がバグだった場合につける
critical bugの中でも特に緊急性の高い Issue につける
confirm-spec 仕様確認が必要な Issue につける
final-check 修正が完了し、ビジネス側、クラアントに最終チェックして欲しい Issue につける

実際はこれに加えてプロジェクト固有のラベル(例えば新機能開発なら、機能名とか)と併用して運用しました。

リリースマネージャのお仕事

ここまで準備が整えば、リリースマネージャのお仕事は次の手順を繰り返すだけの簡単なお仕事です。

やること

朝来て、帰るまでに以下の順番で作業をします。順序は日によって多少前後してもOKです。

  1. 朝会での進捗確認と問題点やリリーススケジュールの共有する
  2. マイルストーンに紐付けられていない Issue をマイルストーンに紐付ける
  3. 直近のマイルストーンを眺めて、担当者のいない Issue に担当者をアサインする
  4. critical ラベルの付いた Issue の進行状況を開発者に確認。必要に応じてタスクの前後関係をリソース調整する
  5. from-customer ラベルの付いた Issue の整理。とりあえず手元で再現するかどうかを確かめ、再現する場合は開発者に、しない場合はクライアントに確認する
  6. confirm-spec ラベルの付いた Issue の仕様をビジネス側の人と調整し、クライアントと開発者を含めて仕様を決定する
  7. final-check ラベルの付いた Issue の確認状況をビジネス側、クライアント側の人に確認する
  8. ドサ回り(チーム内をグルグルまわって、個別 Issue の相談をしたり、状況に応じてラベルの付け直しをしたりする)
  9. マイルストーンの Issue の数とスケジュールを見て、終わりそうにない Issue はマイルストーンを移動し、クライアント調整が必要なものは調整する
  10. 夕会での進捗確認と問題点やリリーススケジュールの共有する
  11. Issue は順次、増えていく可能性があるので、1日の最後に、もう一度 「マイルストーンに紐付けられていない Issue をマイルストーンに紐付ける」をして帰ります

リリースマネージャがやってはいけないこと

リリースマネージャはリリース対象サービスの開発をやってはいけません。 これは完全な失敗談ですが、すぐ直せそうだから自分でやろうとした結果、以下の問題が発生しました。

  • 開発環境のセットアップに時間を取られた
  • 開発フレームワークの勉強に時間を取られた
  • どうしても目の前の問題に目が行って、リリース全体を考えられなくなった
  • 結果、リリース管理作業がうまく回らなくなった

人間は基本シングルコア、シングルタスクです。開発したい欲求はグッとこらえましょう。 特に開発者がリリースマネージャの場合に陥りやすいので注意。

まとめ

Github があって本当に良かったです。

Node.js の Heroku 環境におけるパフォーマンス

Heroku で Node.js を動かしても絶対的なパフォーマンスは得られないのだけれど、最近仕事で Node.js on Heroku をやっているので、実際にどれくらいのパフォーマンスがでるのか測ってみました。

今回調べたのは、主に3点。

  • 2X DynoCPUとメモリが2倍のDyno。ただし、コア数は4のまま)を使うと速くなるのか?

  • Cluster に効果はあるのか?

  • Dyno 数はどれくらいが良いのか?

ベンチマークは Node.js v0.10.9 を対象とし、全て siegeベンチマーク機能の結果です。ベンチマーク対象のコードは、Node.js 本家トップページに載っている例のHTTP サーバで、AWS の us-east-1 リージョンの EC2 m1.large インスタンスで実行した結果です。

$ siege -b 60S -c 100 http://server

結果

1X Dyno

まずはノーマル。これが基準ですね。

Transactions:            155084 hits
Availability:              99.99 %
Elapsed time:              59.87 secs
Data transferred:           1.77 MB
Response time:              0.04 secs
Transaction rate:        2590.35 trans/sec
Throughput:             0.03 MB/sec
Concurrency:               99.63
Successful transactions:      155084
Failed transactions:              17
Longest transaction:            5.07
Shortest transaction:           0.00

2X Dyno

2X Dyno を使うと、なぜかパフォーマンスが下がりました。 何回か測りなおしたのですが、結果は同じでした。

Transactions:            127151 hits
Availability:              99.99 %
Elapsed time:              59.16 secs
Data transferred:           1.46 MB
Response time:              0.05 secs
Transaction rate:        2149.27 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               99.32
Successful transactions:      127151
Failed transactions:               7
Longest transaction:            4.18
Shortest transaction:           0.00

1X Dyno + Cluster

Cluster でもパフォーマンスは低下します。 Failed transactions が減っているのがせめてもの救いでしょうか。

Transactions:            122164 hits
Availability:             100.00 %
Elapsed time:              59.52 secs
Data transferred:           1.40 MB
Response time:              0.05 secs
Transaction rate:        2052.49 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               99.73
Successful transactions:      122164
Failed transactions:               4
Longest transaction:            5.27
Shortest transaction:           0.00

2X Dyno + Cluster

予想はつきましたが、これが最低のパフォーマンスになりました。 2X Dyno はまだ Heroku の Router がうまく捌けていないのか、と思うくらいパフォーマンスが落ちますね。

Transactions:            116440 hits
Availability:             100.00 %
Elapsed time:              59.48 secs
Data transferred:           1.33 MB
Response time:              0.05 secs
Transaction rate:        1957.63 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               99.80
Successful transactions:      116440
Failed transactions:               3
Longest transaction:            5.09
Shortest transaction:           0.00

1X Dyno 2台

2台にしても、パフォーマンスが落ちます。 振り分けのコストがそれなりにでかいわけですね。 Logest transaction が大きく下がっているので、引っ掛かりみたいなものを減らせていると思います。

Transactions:             80351 hits
Availability:             100.00 %
Elapsed time:              59.94 secs
Data transferred:           0.92 MB
Response time:              0.07 secs
Transaction rate:        1340.52 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               99.79
Successful transactions:       80351
Failed transactions:               4
Longest transaction:            1.70
Shortest transaction:           0.00

1X Dyno 3台

3台でやっと1台の時と同じくらいの Transaction rate が出るようになります。 Logest transaction がさらに下がっているのがわかります。

Transactions:            143521 hits
Availability:             100.00 %
Elapsed time:              59.72 secs
Data transferred:           1.64 MB
Response time:              0.04 secs
Transaction rate:        2403.23 trans/sec
Throughput:             0.03 MB/sec
Concurrency:               99.67
Successful transactions:      143521
Failed transactions:               5
Longest transaction:            0.85
Shortest transaction:           0.00

1X Dyno 4台

4台だとまた下がってしまいました。

Transactions:             97675 hits
Availability:              99.99 %
Elapsed time:              59.05 secs
Data transferred:           1.12 MB
Response time:              0.06 secs
Transaction rate:        1654.11 trans/sec
Throughput:             0.02 MB/sec
Concurrency:               98.85
Successful transactions:       97675
Failed transactions:               6
Longest transaction:            1.93
Shortest transaction:           0.00

まとめ

今回の結果だけをまとめると

  • Heroku で Node.js を使う場合、2X Dyno も Cluster も使わない方が良い。
  • Dyno 数は処理能力的には 1 で十分だが、可用性とかを考慮して 3 に設定すると良い。

となります。実際には処理の内容によって結果に差異はでると思うので、実際に測ってみると良いと思います。 自分は効果あると思って Cluster 使ってましたが、今回の結果をみて、使うのをやめました。

2X Dyno は ImageMagick などで画像処理を行うなど、CPU負荷の高い処理を書く場合は意味あるかもしれないが、それは Node.js で書くべきプログラムでもないし、倍の値段払うほどでもないかなと思います。

2X Dyno は Rails または、JVM 系の言語 (Scala / Java)のための Dyno であり、Node.js にはあまり意味がないかと。前述した公式Blog にもそう書いてありますしね。

補足

2X Dyno はまだ Public Beta (でも、明日から本サービス開始)なので、もしかしたら Router の調整とかがうまくいっていない可能性はあります。なので、1か月後くらいにまた測る予定。

追伸

Quipper という会社に転職しました。

YAND (Yet Another Node.js Documentation) のソースを公開しました

1年前にこんなエントリを書きました。

僕の考えた最強の Node.js Document Viewer - hakobera's blog

YAND 自体は結構使っていたのですが、色々あって Node.js v0.6.10 で更新を止めていました。 が、さすがに v0.8 用のものが欲しくなってきたので、v0.8.18 のドキュメントに更新しました。

YAND - Yet Another Node.js Documentation

これに合わせて、ソースの整理もしたので、GitHub で公開しました。

hakobera/yand · GitHub

以下の様な感じで、特定のバージョン(多分、v0.8 以上じゃないと動かないと思いますが)のドキュメントを自分で作成できるようにしてあります。NODE_VERSION にドキュメントを作成したバージョンを指定します。

$ make NODE_VERSION=v0.8.18 build

Herokuにもデプロイできるし、そのまま HTTP サーバにコピーしても使えるので、色々と便利に使える場面はあるのかなと思います。 Node.js でスクレイピングして、加工して、サイト作成のサンプルコードにもなるんじゃないかな。

EventEmitter.emit() によくある勘違い

突然ですが、Node.jsで次のプログラムを実行した結果を答えてください。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  console.log('2');
});
event.emit('open');
console.log(3);

正解は少し下の方に書いてあります。 少しだけスクロールを我慢して考えてみてください。









正解は

1
2
3

です。

1
3
2

だと思っていた人も多いのではないでしょうか。(私だけかもしれませんが)

つまり、表題の「EventEmitter.emit() によくある勘違い」とは、EventEmitter.emit()が次のイベントループで実行されるノンブロッキング処理である、という点です。しかし、EventEmitter.emit()はコールバックを同期呼出しする、つまり、ブロッキングします。

では、1, 3, 2 と出力したい場合は、どう書くかというと、次のように書きます。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  process.nextTick(function () {
    console.log('2');
  });
});
event.emit('open');
console.log(3);

もしくはこう。

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

console.log('1');
event.on('open', function () {
  console.log('2');
});
process.nextTick(function () {
  event.emit('open');
});
console.log(3);

開発者はコールバック関数の中でprocess.nextTick()を呼び出すか、emit()自体をprocess.nextTick()のコールバック内で呼び出すことで、処理を次のイベントループに回すことができます。 emit()がコールバックを同期呼び出しするのは、開発者に処理をブロッキングで処理するのか、ノンブロッキングで処理するのかの選択肢を与えるために、意図してそのようにデザインされているのです。

考えてみれば、当たり前のことなのですが、自分がついこの間まで勘違いしていたので、戒めも兼ねて書いてみました。 みなさんも思い込みには気をつけて、必ず動作を確認するようにしましょう。

「Node.jsは静的コンテンツには向いていない」のか?

この記事は東京Node学園祭2012 アドベントカレンダーの8日目の記事です。

この記事を書こうと思った理由

Node.jsに関するWeb上の記事を読んでいると、「Node.jsは静的コンテンツに弱い」とだけ書いてある記事をよく見かけます。有名なところだと、LinkedInのNode.jsのパフォーマンスに関する10個のTipsの3番目のTipsに"Don't use Node.js for static assets"とばっちり書いてあります。

確かにCDNやNginxに比べれば、Node.jsは静的コンテンツの扱いが遅いとは思います。しかし、それは LinkedIn くらいの超大規模なトラフィックがある場合には問題になるとは思いますが、小〜中規模なサイトでもNginxは必須なほど遅いのでしょうか?512MBしかメモリのないVPSにNginxとNode.jsを入れてやりくりすることがホントに効率的なやり方なのでしょうか?

実は自分自身もちょっと前まで「静的ファイルはNginxに任せる」病にかかっていたのですが、PaaSを使って開発するようになり、そもそもNginxインストールできない状況で、Node.jsで静的ファイル配信もありじゃないかと思い始めてきたので、今回、実際にベンチマークで検証してみようと思った次第です。

比較対象

  • Nginx 1.2.4
  • Node.js 0.8.12
    • Node.js標準のhttp, fsモジュール
    • node-static 0.6.4
      • 静的コンテンツ用のNPMモジュールとしては古参の部類。インメモリキャッシュしてくれる。
    • connect 2.6.0 のstaticミドルウェア
      • Express の基盤。一番良く使われていると思ったので採択
    • st 0.0.9
      • 比較的新しい静的コンテンツ用のNPMモジュール。gzip圧縮を標準でサポート。ETagなどクライアント側でも積極的にキャッシュさせるようにして効率的な静的コンテンツ配信が強く意識されています。

比較方法

$ ab -c 200 -n 100000 <url>

# gzipありの場合は以下
$ ab -c 200 -n 100000 -H "Accept-Encoding: gzip,deflate,sdch" <url>
  • リクエスト/秒で比較
  • 画像(png)とHTMLで比較
  • gzip 圧縮ありと圧縮なしを比較
  • 一部のベンチマークでは、Nginx は worker数を増やしたり、Node.jsはclusterモジュールを利用してcluster化

環境

OS CPU Memory
Server CentOS 6.2 AMD Phenom II X6 1090T 16GB
Client CentOS 5.8 AMD Phenom II X6 1090T 16GB

Server <-> Client 間は1Gbpsのローカルネットワークで繋がっています。注意点として、この環境だとネットワーク帯域の限界から、15,000リクエスト/秒(=約13億リクエスト/日)あたりで性能が頭打ちになります。

ベンチマーク結果

使用したソースファイルや設定ファイルは Gistにおいてあります。

画像 (png)

Node公式サイトのロゴ (5,081 byte) を取得

Nginx http node-static node-static (cluster x4) connect st st (cluster x4)
11,419 5,243 5,568 11,546 3,007 4,547 11,876

worker数=1で比較するとNgixがダブルスコアで圧勝ですが、 node-static, st に関しては、cluster を使えば Nginxとほぼ同等の速度が出ています。

NodeがNgixよりも遅いのは、CPUの使用率が100%に張り付く、つまりCPUボトルネックで速度がでていない。 ただし、CPU利用率は Nginx より高かったことは注記しておきます。

HTML

Node公式サイトをindex.html (8,550 byte)を取得

gzip 圧縮なし

Nginx http node-static node-static (cluster x4) connect connect (cluster x4) st st (cluster x4)
12,385 4885 4,975 12,297 2,921 8,982 4,268 12,401

画像と傾向は同じ。

Total transferred:      877906604 bytes
HTML transferred:       855101360 bytes

gzip 圧縮あり

http, node-static, connect は非対応なので省略。(connect.compress()はcluster x4でも遅かったので省略)

Nginx Nginx (worker x4) st st (cluster x4)
5,543 15,975 4,425 12,232
Total transferred:      345644803 bytes
HTML transferred:       320434219 bytes

gzipありだと NginxがCPUを100%使いきり、速度が大きく低下しました。worker を増やすことでパフォーマンスは改善できますが、VPSなどのCPUのコア数が少ないサーバでgzipを有効化すると逆にパフォーマンスが低下する可能性があることを示唆しています。stは既にgzipなしの時点でCPUボトルネックが発生していたので、gzipなし、ありで性能差が殆ど無い、という面白い結果になりました。gzip圧縮ありだと転送量がgzip圧縮なしの半分以下となりでネットワークに優しく、全体的なスループットの向上に貢献しそうです。

まとめ

  • Node.jsの静的コンテンツの性能は、同一のworker数のNginxの約半分だということがわかりました。たしかに、Nginxに比べれば、Node.jsは静的コンテンツの配信には向いていないようです。とはいっても、4,000リクエスト/秒(=約3.4億リクエスト/日)は捌けるので、小〜中規模のサイトなら十分に捌ききれるレベルです。
  • NPMモジュールとしては、st がお勧めです。gzip圧縮ありでも大きなパフォーマンス劣化はなく、(CPU効率は良くないですが)cluster を組めば、絶対性能としてNginx の8割程度の速度を出すことはできます。
  • connect の static ミドルウェアはダントツで遅い(つまり、connectを拡張したフレームワークであるexpressも遅い)ので、express を使っているサイトは要注意です。Node.jsが静的コンテンツに向かない、と言われる1つの要因は、この最も有名なNode.jsのフレームワーク express (connect) の staticモジュールが遅いということも原因の1つなんじゃないかと、今回測定して感じました。

「Node.jsは静的コンテンツにむいていない」という言葉だけが独り歩きしていますが、実際に測ってみると同じNode.jsのモジュールでも特徴が出ていて面白かったです。ある程度以上の規模になったらNginxやCDNを使うようにした方が良いというのも事実ですが、噂に惑わされずに、ファクトベースで自分の用途にあっているかどうかを見極めていくのが良いと思います。

Tuppari コミュニティ始めました & IE に対応しました

Tuppari コミュニティについて

これまで個人で Tuppari を書いていましたが、@seratchRuby ライブラリ書いてくれてので、これを機にコミュニティ体制に移行しました。

Tuppari の Ruby クライアントを書きました - case class HatenaDiary(id: Symbol = ’seratch2)

具体的には、Github の Organization にリポジトリを移動し、Node.js、Java、そして今回追加された Ruby ライブラリをまとめました。

Tuppari (Github Organization)

Tuppari のメンテナになりたいという方、もしくは、Tuppari の各種言語のライブラリ書いたという方は権限を追加しますので、ご連絡ください。

IE 9 対応について

Tuppari は WebSocket を使うので、原則は IE9 以下では動作しないのですが、Flash の WebSocket 実装である web-socket-js を利用して、IE9 でも動作するようにしました。

IE に対応させたい場合は、以下のように条件付き Include を使って、必要なライブラリを読み込むようにしてください。

  <!-- Include the Tuppari client JavaScript libraries on your page from CDN -->
  <script src="http://cdn.tuppari.com/0.2.0/tuppari.min.js"></script>
  <!-- If you want to support IE6, 7, 8, 9 include script for flash sockt -->
  <!--[if lte IE 9]>
  <script src="http://cdn.tuppari.com/flashsocket/swfobject.js"></script>
  <script src="http://cdn.tuppari.com/flashsocket/web_socket.js"></script>
  <script src="http://cdn.tuppari.com/flashsocket/json2.js"></script>
  <!endif-->

デモも更新しておいたので、IEの方はお試しください。

8/17追記: IE6, 7, 8 だと動かない場合があることが分かったので取り急ぎ IE9 対応に変更しました。現在、対応中です。

Tuppari の Java クライアントを作った

Tuppari - WebSocket on Your Cloud - - Scalaとlift のはずだった ・・・

前回、Node.js で構築した Tuppari (コンセプトは Pusher クローン)を紹介しましたが、実は API 仕様が公開されていて、Node.js 以外の言語/環境でもクライアントを作ることができます。

Tuppari API Reference

で、これにもとづいて Java クライアントを作りました。他の言語でも書けると思うので、興味がある方は是非挑戦してみてください。そして、できたら教えて下さい。

tuppari-java

Tuppari 公開記念ハッカソン

興味があればどうぞー。Node.js はちょっとわからないけど、Java なら任せろー、な方も大歓迎です。ご希望があれば、Tuppari の使い方、各自の環境へのインストール方法などサポートします。

Node塾番外編 Real-time Web ハッカソン - [PARTAKE]

使い方

maven から利用することを想定しています。pom.xml に以下の設定を記入してください。

<repositories>
    <repository>
        <id>tuppari-java</id>
        <name>tuppari-java repo</name>
        <url>http://hakobera.github.com/tuppari-java/releases</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>tuppari</groupId>
        <artifactId>tuppari-java</artifactId>
        <version>0.1.0</version>
    </dependency>
</dependencies>

サンプルコード

以下の様な感じで、ほぼ Node.js のコード同じ感じで書けます。

package com.tuppari;

import com.tuppari.TuppariChannel;
import com.tuppari.TuppariClient;

import java.net.URI;
import java.util.Map;

public class TuppariExample {

    /**
     * Usage: java com.tuppari.example.TuppariExample [applicationId] [accessKeyId] [accessSecretKey]
     */
    public static void main(String[] args) {
        if (args.length != 3) {
            throw new IllegalArgumentException("Usage: java com.tuppari.example.TuppariExample [applicationId] [accessKeyId] [accessSecretKey]");
        }

        String applicationId = args[0];
        String accessKeyId = args[1];
        String accessSecretKey = args[2];

        TuppariClient client = new TuppariClient(applicationId, accessKeyId, accessSecretKey);
        // Run on you own cloud, specify the target URL.
        // TuppariClient client = new TuppariClient(applicationId, accessKeyId, accessSecretKey, URI.create("http://localhost:5100"));
        TuppariChannel channel = client.join("your_channel");
        Map<String, String> result = channel.send("your_event", "hello");

        System.out.print(result);
    }

}