RubyKaigi 2014 レポート

Aman Gupta, GitHubでのRubyの使われ方と高速化のテクニックを紹介 ~ RubyKaigi 2014 基調講演 3日目

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/

おすすめ記事

記事・ニュース一覧