技術部・開発基盤グループの中村です。
この度、クックパッドが開発し、オープンソースとして公開しているRuby on Rails向けプロトタイプ開発用のプラグイン「Chanko」を再設計し、Ruby 2.0.0 への移行に引き続き Chanko 2.0.0 をリリースしました。Chanko 2.0.0 では、これまでのバージョンと互換性を保ちながら、主に実行速度やコードの可読性について改善が加えられています。
http://cookpad.github.io/chanko/
Chankoとは
Chankoは、素早く安全に新機能のプロトタイプを行うためのフレームワークです。クックパッドでは今現在でも、Chankoを使って多くの機能を独立して開発し、対象範囲を限定して公開することで新機能の検証サイクルを回しています。Chankoを利用して拡張した機能でエラーが起きた場合、拡張前の機能に自動的に切り替えられるため、プロトタイプでの検証段階から安全に機能を提供することが出来ています。RailsConf 2012で発表したChankoについてのプレゼン資料がありますので、併せてこちらもご覧ください。
https://speakerdeck.com/mrkn/chanko
改善の経緯
クックパッドでは、サーバからのレスポンスタイムについて200ms以下という目標を置いています。そこでサイトのパフォーマンス向上のための取り組みとして、開発者全員で速度改善を競い合うパフォーマンス・チューニンガソンというイベントを行いました。今回のChankoの改善は、その一環で速度と保守性の向上を狙いとして取り組んだものです。
開発の進め方
コードの速度改善や品質向上を目的として行う場合、皆さんどのような手法で取り組まれているでしょうか。今回の改善にあたっては、計測すること・検証することの二点を守りながら開発を進めました。開発にあたって利用したツールやサービスを幾つか紹介します。
ruby-prof
速度低下の原因になっている部分を調べるため、Ruby用のプロファイラを利用して調査を行いました。Rubyには標準添付ライブラリとしてprofileというプロファイラが用意されていますが、より実行速度の速い ruby-profを利用しました。これを利用してメソッド呼出回数と単位実行時間を計測することで、速度低下の原因になっていそうな箇所に大まかな予測が付けました。大抵の場合、全体の中で実行時間を多く占めている箇所というのは一部に偏っているため、これで大体の当たりをつけることができます。
Travis CI
Travis CIを利用して継続的にテストを行いながら開発を進めました。ご存知の方も多いかと思いますが、Travis CIはオープンソースコミュニティのための継続的インテグレーションサービスです。Chankoでは、ruby 1.8、1.9、そして2.0で継続的にテストを回すことで、常にテストやバージョン間の互換性を意識した開発を行うことが出来ました。また幾つかの他のサービスと連携させることで、更にTravis CIを便利に使うことが出来ます。
Coveralls
今回の改善にあたり、保守性の高いコードにするということも目的の1つとしていました。そこで、まずはテストのC0カバレッジを100%にした後、この状態からカバレッジを落とさないように開発を進めました。これを支援するためのサービスとして、Coverallsがあります。Coverallsを利用するとTravis CIと連携してテスト完了時にカバレッジを記録・通知してくれるため、常にテストカバレッジの値を意識して開発を進められます。
Code Climate
コードの品質についても、やはり何らかの指標を持って改善していきたいところです。今回はCode Climateの値を指標として参考にしました。Code Climateでは、コードの重複や複雑度、メソッドごとの行数などを元に、コードに点数をつけてくれます。Chankoの評価結果を見ると、今回の改善の前後で点数が向上していることが分かります。
Badge
ここで紹介したTravis CI、Coveralls、Code Climateのサービスでは、それぞれ常に最新のCIの結果を表示しておくための画像が提供されています。最近ではGitHub上で見かけることも多くなってきました。これらの画像をREADME等に表示しておくことで、より改善意識を高めておくことが出来ると思います。
検証方法
速度の検証では、まずApache Benchを利用して手元のサーバ環境で簡単に検証した後、Apache JMeterを利用して本番に近い形でのリクエストのテストを行いました。テストに使うリクエスト内容も実際のユーザからのものを想定し、本番環境で吐かれたサーバログを元に生成したものを利用し。速度検証の結果、クックパッドのPCトップページの平均応答時間が約94.5%に向上するという結果になりました。少ない値のように見えますが、例えば改善前に200msかかっていた応答時間が、改善後では189msになります。
もう1つ、今回の開発では改善前後で実行結果が変わってしまわないように特に気を付ける必要がありました。そこで本番環境のリクエストをkageを使って複製し、新旧Chankoを利用した同等のサーバをそれぞれ用意してリクエストを受け取り、レスポンス結果を比較するといった検証を行いました。
また、テスト実行時に生成されるHTMLを新旧Chankoで保存し、両者の差分を比較するということも検証の材料として利用しました。具体的には、RSpecの実行時に下記のようなコードを利用してcontroller specsとfeature specsで生成されるレスポンスの中身をファイルに書き出しておき、diffを利用して差分を調べました。
# Write all response bodies into specified directory # # Examples # # $ RESPONSE_BODY_WRITER_DIR=/path/to/dir rspec spec/controllers/entries_controller_spec.rb # # (e.g. the following files are created as a result.) # /path/to/dir/spec/controllers/entries_controller_spec-2.html # /path/to/dir/spec/controllers/entries_controller_spec-5.html # /path/to/dir/spec/controllers/entries_controller_spec-8.html # if ENV["RESPONSE_BODY_WRITER_DIR"] RSpec.configure do |config| config.include( Module.new do def write_response_body target = response || page if target.body.present? pathname = Pathname.new("#{ENV['RESPONSE_BODY_WRITER_DIR']}/#{example.location.gsub(?:, ?-)}.html") pathname.parent.mkpath pathname.open("w") {|file| file.puts target.body } end rescue warn "Something went wrong in the process of #write_response_body after hook" end end ) config.after(:each, :type => :controller) { write_response_body } config.after(:each, :type => :feature) { write_response_body } end end
まとめ
このように、計測と検証を徹底して行いながら改善していくことで、Chankoは安全かつ着実に開発を進めることが出来ました。結果として、サイト全体のレスポンスタイム向上に繋がり、コードの品質向上にも貢献できました。
クックパッドは、オープンソースコミュニティに対して積極的に貢献していきたいと考えています。社内で開発されたライブラリやツールを切り出してオープンソース化し、またオープンソースプロジェクトにコミットすると言った取り組みを、今後もより活発に続けていきたいと思います。