2014年9月18日~20日の3日間、タワーホール船堀にてRubyKaigi 2014 が開催されました。基調講演をそれぞれレポートしてきました。
3日目最後の基調講演は@a_matsuda の紹介を受けて登壇した、Aman Gupta(@tmm1 )です。タイトルは「Ruby 2.1 in Production」 。Aman Guptaは現在GitHub, Inc.(以下、GitHub)に勤め、そこで使用している高速化のテクニックとツールを紹介しました。Ruby本体のコミッタでもあるAmanによる講演は、圧巻でした。
当日のスライド(PDF版)は次のリンクから参照できます。
はじめに
現在AmanはGitHubで技術インフラストラクチャのヴァイスプレジデントをしています。github.com の巨大なRailsアプリケーションについてモデルの数やコントローラの数、レスポンスタイムなどを紹介した後、自己紹介に移りました。
Rubyとの出会い
AmanとRubyとの出会いは2002年までさかのぼります。Amanがまだ高校生のときです。そのころ、AmanはWeb開発を学んでいました。PHPに熱中していて、シェルスクリプトから3Dゲームといったものまで、すべてをPHPで書くほどだったそうです。
ある晩、ネットを見ていると偶然Programming Ruby というページを見つけました。Rubyのオブジェクトモデルの簡潔さに興味をそそられ、とても面白そうなものに見えたそうです。しかし、まだその時点ではRubyはWeb開発にあまり使えなかったので、すぐに興味をなくしPHPに戻りました。
その後、ふたたびAmanがRubyを使い始めるのは、数年後の大学時代になってからのことです。そのころRailsについての話を聞くようになり、他の人と同じようにDHHのRailsの本を一冊購入して何回か読んだそうです。
Amanはリアルタイムにブラウザにpushすることにとても興味を持っていたので、Railsにその機能を追加しようとしました。しかしすべてのメタプログラミングを追うことはできず、あきらめました。とりわけ、そのころRailsは1.0ぐらいで、コードベースはものすごいものだったようです。そこで一度Railsを触るのはやめてしまいました。
代わりに、SequelやRamazeといった、よりシンプルなRailsの代わりとなるようなものの開発に関わっていきました。EventMachineにも出会い、次第にメンテナにもなりました。
2008年からのサンフランシスコ時代
2008年にサンフランシスコに移ったのは大きな変化であったのと同時に、サンフランシスコにいるのはとても刺激的だったそうです。そのころ、ICHR: i can has rubyと呼ばれるRubyのミートアップが催されており、github.comという名のサイトを作っていた2人と出会ったのもこの場所でした。
2009年
2009年前後、GitHubの創業者たちは隔週の飲み会を開催し始めました。この機会を利用して、町のあちこちを見たり、シリコンバレーにいる他のRuby開発者たちにも会ったそうです。
2010年
2009年と2010年にはRubyコミュニティにより関わるようになり、 EventMachineでの功労に対してRuby Hero Awardsを受賞したりしました。たくさんのカンファレンスにも参加するようになり、RubyのスレッドやGC、メモリのプロファイリング、デバッグについて話したそうです。
同時期にGitHubも軌道に乗り始め、サンフランシスコの優秀な開発者が徐々に会社に参加していきました。
2011年
そして2011年に、GitHubに参加しました。Amanの友人もかなり参加していたそうです。
GitHubに参加する前には、実際のところGitHubやTwitterといったサンフランシスコのスタートアップに対してパフォーマンス改善のコンサルタントをしていました。しかし、一旦github.comのコードベースにアクセスできるようになると、もう他のことはしたくなくなったそうです。それは人生の転機のようでもありました。毎日使っているgithub.comに実際に改良を加えられることが嬉しかったそうです。
空き時間にファイル検索機能に取り組み始め、GitHubのフルタイム従業員として働き始めた、まさにその最初の日に, その機能をリリースしたのでした。
GitHubでのRubyについて
GitHubは独特なサービスで、標準的なRailsのサイトよりもたくさんの構成要素を持っています。2011年当時、GitHubではstandard debian packageを介してruby 1.8.7-p72を使っていました。
rbtrace
巨大なコードベースに新しく触れるにあたり、プロダクション内で走っている様々なプロセス内で何が起こっているのかと思うことがよくあり、その中で生まれたのがrbtrace でした。もしrbtraceを使ったことがなければ、是非使ってほしいそうです。
Rubyのアップデート
1.8.7-p72からのアップデート
2011年には1.8.7-p72はとても時代遅れになっていたので、アップグレードをしてみようとしました。これはとても大変なプロセスだったそうです。
GitHubはCIのカバレッジがとてもよかったのですが、ビルドがとても遅く、並列処理でもpush毎に8分かかっていました。そのため、Amanは最初のステップとしてCIのパフォーマンスを調査することにしました。
●Amanのパフォーマンスチューニングの方法
ここで一旦、Amanがパフォーマンスチューニングをするにあたり、どのようなアプローチをとるのが好きなのかを解説しました。
パフォーマンス改善の最初のステップは、計測です。どこに時間がかかっているのかを知って初めて、改善することができます。改善後、再度計測し、その変更が違いを生み出すのかを見ることができます。
また、単純にリクエストにどれくらいの時間がかかったかではなく、AmanはCPU timeとidle timeに分けて考えるそうです。CPU timeはカーネルかユーザー領域からにかかわらず、命令を実行するのにプロセッサがかかった時間です。他の時間はidle timeで、CPUが何もしていないことを意味します。これはプロセスがmysqlやredisといったような、ネットワークソケットを待っているか、ログなどのファイル書き込みを行なっているときに起きます。System callを計測するのにはstrace
というUnixのツールを使っています。
ree-1.8.7-2012.02へのアップデート
パフォーマンス改善後, 速いCIと短いイテレーションのサイクルを手に入れたので、REE(Ruby Enterprise Edition)がregressionを持ち込まなかったことを確かめることができました。
こうして無事1.8.7-p72からree-1.8.7-2012.02へのアップデートが完了しました。
ruby-1.8.7-p72
ruby-1.8.7-2012.02
1.9へのアップデート
前述の通り、プロダクション環境のrubyをアップロードする戦略はうまくいったので、1.9にあげることを考え始めました。実は1.9がリリースされてからしばらく時間が経っていたのですが、その間にVMなどがとてもいい感じになっていて、コミュニティによって多くのgemの互換性の問題も解決されていました。
このアップデートは特に大変で、最初のステップとしてデュアルブートをサポートすることにしました。これにより、簡単にCIとプロダクション環境で1.9をテストできます。
幸い、deprecatedなgemの対応や、シンタックスの変更といった一般的な問題に関しては、たくさんのブログ記事を参考にすることができました。
しかし、marshalの互換性については考えなければなりませんでした。github.comは数テラバイトの大きなmemchached clusterを使用しているため、1.9のアップデートのためにすべてのキャッシュを消すようなまねはできません。移行の間は1.8と1.9のデュアルブートを計画していたので、余計にそんなまねはできませんでした。
互換性の問題が起きる場所はDateとDateTimeクラスでした。根本の原因はRationalクラスの変更によるものでしたが、モンキーパッチを書いて1.8と1.9の両方のスタイルでマーシャルされたオブジェクトを扱える、カスタムしたmarshal_loadを比較的簡単に実装することができるとわかりました。これにより、プロダクションで同時に1.8と1.9を実行でき、一つのmemchache poolを共有できるようになりました。
このような変更を加え、なかなかいい感じになってきたように見えたのですが、まだ一つ大きな問題が残っていました。エンコーディングです。
かなり多くのエンコーディングエラーがありました。ユーザーは名前やメールアドレス、コミットメッセージ、ファイル名、ファイルの内容など、すべての種類のデータを保存します。これらのデータのエンコーディングは、ユーザー環境やOSによって様々なものになります。
プロダクション環境で直面した例外のそれぞれに対して、失敗するテストを追加しましたが、明確な解決法はまだわかりませんでした。
結局、巨大なhackをして解決することになりました。Ruby 1.8の挙動をよりエミュレートするために、エンコーディングエラーを飲み込むオプションモードを作成しました。ASCII-8BITの文字列をutf-8の文字列と合体させるとき、例外を発生させる代わりにエラーを飲み込み、結果の文字列をASCII-8BITとしてマークをつけることにしたのです。
プロダクション環境で1.9のテストを実行するとき、異なるバージョンのRubyに簡単に変更できる仕組みも用意しました。それにより、残る問題を追跡するとき、1.8と1.9の切り替えを容易にすることができて、とても助かりました。
1.9へのアップデートにより、レスポンスタイムの大幅な縮小を目の当たりにしました。こうしてついにGitHubはYARVにのり、Amanはさらにパフォーマンスに注力できるようになりました。
ruby-1.8.7-p72
ruby-1.8.7-2012.02
ruby-1.9.3-p231-tcs-github
ruby-2.1.2へのアップデート
AmanはRubyのアップデートに取り組むにあたり、いくつかのツールを書いてきました。script/bench-app
はシンプルなベンチーマークツールで、Railsアプリケーションをループ内で実行します。rblineprof
は1.8やREEに取り組んでるときに作成したプロファイラです。
プロファイラを書くのがAmanの趣味らしいです。このプロファイラはRailsのテンプレートやコントローラのパフォーマンスを改善するのにとてもよく役立ちます。Amanはこのツールを使い、ダッシュボードに混入したデバッグ用のコードを見つけ, 15ms余計にかかっていたものを取り除きました。
Rubyのパフォーマンスに深く関わっていくなかで、パフォーマンスの問題に取り組んでいる他の人たちともっと協力したくなったそうです。
そこで、Ruby 2.0がリリースされた2013年の早い時期に、+Ruby Performanceと命名したチャットルームを作り、@tenderlove , @charliesome , funnyfalcon, @samsaffron , @jamesgolick といった、よく知られた多くの開発者を招待しました。このチャットルームで、皆で取り組み始めたのがRuby 2.1の数々のパッチです。
ある日、そのチャットルームで、「 なぜRubyのコミット権を持ってないんだ?」と@jamesgolickに聞かれました。持たない理由は特に見当たらなかったので、Matz にコミット権をもらえるよう聞いてみることにしました。Amanは実際にコミット権をもらえるとは思っていなかったらしく、Matzが同意したことに驚いたようです。こうして、AmanはRubyのコミッタになりました。
コミット権を得たAmanは、まさに鬼に金棒といった感じで、また自分の趣味を追求することにしました。プロファイラを書くことです。
rblineprof
はうまく動きましたが、 AmanはYARVで動くperftools.rb
のような低コストのサンプリングプロファイラを求めていました。@samsaffronが@_ko1 に連絡を取り、一緒に“ Ruby profiler task force” というSkypeの部屋を作って、そこでRuby 2.1のリリースに向けた新しいAPIの設計をしました。
Amanはこうしてtrunkにコミットされた数々の新しいAPI使い、プロダクション環境を試しました。その結果、Ruby 2.1のパフォーマンスは特にGC面ですばらしいものだとわかりました。
そこで、GitHubのプロダクション環境を完全に2.1.2に移行することにしたそうです。
ruby-1.8.7-p72
ruby-1.8.7-2012.02
ruby-1.9.3-p231-tcs-github
ruby-2.1.1-github
1.9と2.1はほとんど互換性が保たれていたので、アップデートした際の苦労はここでは特に語られませんでした。
Ruby 2.1の新しいAPIを使ったツールの紹介
ここからは、2.1の新しいAPIを利用したツールを紹介しました。
stackprof
最初のツールはstackprof です。300行ほどのシンプルなプロファイリングツールでした。グラフを生成することもできます。
stackprof:flamegraphs
stackprof:flamegraphsは、stackprofにd3のUIをつけたものです。是非使ってみてください。感想があれば、Twitterなどで教えてほしいそうです。
ObjectSpace.dump
新しいプロファイル用のAPIの他に、メモリ分析ツールObjectSpace.dumpも導入しました。この新しいheap dump APIはJSONで結果を出力します。これを使って、AmanはRubyのバグを発見しました。
jemalloc profiler
上記で見つけたバグのような結果を確かめるため、このメモリプロファイラjemalloc profilerを使用しました。
そして、GitHubに直接組み込んだツールも紹介しました。
peek stats bar
GitHubの従業員になると、すべてのページのトップに現在のリクエストのパフォーマンスデータが表示されます。この機能はpeek gemを導入することで、他のアプリケーションでも使えます。
stats bar: detail views
stats bar: detail viewsを使うことで、さらに詳細をみることができます。例えば、描画されたテンプレートのリストを表示して、どれにどれぐらいの時間がかかったのかをみることができます。
template visualization
template visualizationは週末に作った実験的な機能です。ページを構成するテンプレートを見分けることができます。見分けるだけでなく、それぞれのテンプレートの描画にどれくらいの時間がかかっているかも確認できます。
この説明中には会場ではため息が漏れていました。残念ながら、これはRailsにかなりモンキーパッチを書いて作っているため、gemとしてまだ独立していないそうです。
最後に
まとめとして、次のことを挙げました。
Ruby 2.1はプロダクションに使えます。
新しいプロファイリングとオブジェクトスペースのAPIはデバッグをより簡単に、より楽しくしてくれます。
Rubyがどのように実行されているかを知ることが、パフォーマンス改善には不可欠です。
今回挙げたツールを使い、また新しいのを作ってください! VMがブラックボックスである必要はありません。
最後は会場全体が盛大な拍手で包まれ、Amanの講演は終わりました。
Aman Guptaのブログはこちら:http://tmm1.net/