speed_gun で Rails のパフォーマンスを測定する
Web アプリケーションのパフォーマンスにうるさいみなさんこんにちは。
Rails アプリのパフォーマンスプロファイリングだと、 rack-mini-profiler が有名で、それ以外だと New Relic とかを使って測定していくのが普通のようですが、物足りない部分があったので、自前でプロファイラ gem を作りました。
Rubygems: http://rubygems.org/gems/speed_gun / Github: https://github.com/rosylilly/speed_gun
詳細は Github の README なんかを見ていただくとして、基本的には rack-mini-profiler 的な情報収集が出来ると思ってもらって差し支えないです。
ちなみに計測画面はこんな感じ。
小さくて何も見えないかも……とりあえず3セクションあって、
- ブラウザの UserAgent や Performance API などの結果
- プロファイル結果
- Rack ENVs
となっています(ちなみに画像は Redmine に導入してみたところです。 Gemfile に gem 'speed_gun'
って書いたら OK でした)。
rack-mini-profiler には GC を測定する機能とか ruby-prof との連携機能とかあるんですが、一旦その辺はのせていません。もしかしたらあとで実装するかも。
speed_gun はとにかく手早く Rails のプロファイルを取れるように気を使っています。カスタムプロファイラも手軽に作れるようにしています(README の通り)。
また、 JS のプロファイル結果を speed_gun に送るのも簡単に出来るようにしてあります。1つの画面を皆で共有することで、違うブラウザ間でのパフォーマンス測定をより効率的に行えるようにするためです。
Web アプリケーションの『パフォーマンス』は様々なレイヤがあり、Rails のログからだけでは、本当のボトルネックがどこなのかを認識するのが困難だと思っています。たとえば、 Rails は高速に返しているけど、 JS ですごく重い処理があって『パフォーマンス』が悪い気がする、などです。
重いサイトを見てもユーザーさんは『重い』としか言ってくれないので、どこが重いのかを検証するために作りました。
まだまだドキュメント整備不足なので、もっと増やしていくつもりです。
実験的な機能 SpeedGun::Hook
speed_gun には SpeedGun::Hook
というクラスがあり、これを継承して #invoke
を生やしておくと、そこに保存された SpeedGun::Profiler
のインスタンスが渡るようになっています。
Javascript プロファイリングの都合上、同じ ID の SpeedGun::Profiler
インスタンスが数度呼び出される部分に考慮してコードを書いておくことで、パフォーマンス情報をどこかに永続化する、といったことも可能です。
この辺はまだ API を固めきれていないので今後変更される可能性がありますが、とりあえず僕が試しに使って使用感を確かめようかなというところです。
それでは!
自前 error にエラーコードなどを付与している時
自前で Error() string
なメソッドを持った struct を定義した際、エラーコードなどを持たせていて、それを取り出したい。
package main import "log" type MyError struct { Message string } func (e *MyError) Error() string { return "MyError" } func NewError() error { return &MyError{"Fail"} } func main() { err := NewError() log.Print(err) // これは OK。なんら問題ない log.Print(err.Message) // これは NG。 error interface には Message string がないため。 myerror := err.(*MyError) log.Print(myerror.Message) // これで取り出せる }
ことは単に interface
だということを忘れていたという話なんだけど、結構ひっかかってわからなかった。
STI と Polymorphic を同時に使うとハマる
以下の様なクラスがあるとき
class Company < AR::Base has_many :employments has_many :engineers, through: :employments, source: :employee, source_type: 'Engineer' end # STI の基底クラス class Employee < AR::Base; end # STI class Engineer < Employee; end # Polymorphic class Employment < AR::Base belongs_to :company belongs_to :employee, polymorphic: true end
この時、 Employment#employee
に Engineer
のインスタンスをいれたらそのまま #employee_type
は Engineer
になるだろうな、なんて思っていると、全然そんなことはなく Employee
が入る。
当然、 source_type
を Engineer
にしている Company#engineers
も引けない。employee_type
がミスマッチでコケるから。
気をつけて。
ファイルアップローダを作ろう
How to じゃなくて、何かの言語を学ぶ、もしくはあるパラダイムにチャレンジするとき、に僕がよく使うサンプルアプリケーションとして、ファイルアップローダというのがあり、それの仕様をまとめておこうと思い至っただけです。
ちょっと研修資料っぽい感じになっちゃったけど、まぁいいか。
アプリケーションの概要
非ログイン型のファイルアップローダです。ファイルの保持先は S3 や Disk 、 DB への Blob などいくつかの選択肢が提供されます。
HTTP のフォームからファイルがアップロードされ、リストで表示されるだけの簡単なアプリケーションです。
ファイルには有効期限があり、それを過ぎるとダウンロードできなくなる、かつリストにも表示されなくなります(保存先の実ファイルも削除されていることが望ましいです)。
また、ファイルにはプライベートモードがあります。プライベートモードのファイルはリストに表示されませんが、ファイルの個別 URL に直接アクセスすることでダウンロードすることが出来ます。
ファイルにはパスワードが設定でき、そのパスワードを用いることで、有効期限をまたずにファイルのダウンロードを無効化することが出来ます。
URL 一覧
GET /
: ファイルリスト + アップロードフォーム(text/html)POST /files
: ファイル作成(作成後は/files/:id
へリダイレクト)GET /files/:id
: ファイル個別ページ + 無効化フォーム(text/html)GET /download/:id
: ダウンロード(application/octet-stream もしくは実ファイルの content-type)DELETE /files/:id
: ファイル無効化(成功なら/
へ、失敗なら/files/:id
へリダイレクト)
想定する挙動一覧(正常系のみ)
- ファイルをアップロードする -> リストにファイルが追加されている -> 個別ページにアクセス出来る -> ファイルがダウンロードできる
- ファイルをプライベートモードでアップロードする -> リストには追加されていない -> 個別ページにアクセス出来る -> ファイルがダウンロードできる
- ファイルを有効期限1秒後の設定でアップロードする -> 1秒後に個別ページにアクセスできない(404) -> ファイルのダウンロードも出来ない(404)
- ファイルをパスワード付きでアップロードする -> 個別ページからパスワードを入力して無効化を行う -> 個別ページにアクセス出来ない -> ファイルのダウンロードも出来ない
補足
保存先の設定はアップロード毎じゃなくてアプリの設定でよいです。データの永続化先は DB でもファイルでもなんでもいいですが、要件上 DB を使っておくのが懸命です。
パスワードを生で保存するのは賢くないのでやめておくべきです。どのように保存するかは考えましょう。
正常系のみでいいのでテストを書きましょう。テストが書きにくい、難しいと感じたら、どうやったらテストしやすくなるかを考えましょう。
もし、身の回りに先輩やウィザードがいる場合、この仕様と出来上がったコードをセットでレビューを受けましょう。もしくは、どこか適当な(自前の VPS など)でアプリケーションを動かして、なんらかの攻撃に成功した人にコーヒーをおごる、といって友人、先輩などに公開しましょう(くれぐれもパブリックなアプリケーションとしては運用しないこと!)
蛇足
僕がこの仕様を使ったのは
- 目指せオブ厨な勢いで OOP に馴染もうとしたとき
- HTTP なアプリのテストの書き方がわからない言語を初めて触るとき
- DDD / DCI などにチャレンジするとき
てな具合です。物足りない時はユーザー登録機能を足したり、ファイルが画像の時だけプレビュー機能をつけたり、アップロード出来るファイルの容量制限をかけたりってな具合に仕様を足して複雑度を上げていくようにしていました。
何かの参考になれば。
ActiveSupport::Autoload を単体で使う
追記
「ruby標準のautoloadは後々非推奨的なアレになりそうらしいので」と言ってもActiveSupport::Autoload#autoloadはModule#autoloadの只のwrapperなので特に関係無い気がする
— 中村氏 (@r7kamura) October 20, 2013
特に意味ないらしい。 autoload 使えなくなったらどうしようかなぁ。
元記事
ruby 標準の autoload は後々非推奨的なアレになりそうらしいので
require 'active_support/dependencies/autoload' module Mod extend ActiveSupport::Autoload autoload :Foo # => require 'mod/foo' autoload :Bar, 'bar' # => require 'bar' autoload_under 'foo' do autoload :Baz # => require 'mod/foo/baz' end end
eager_load とかもできる
module Mod eager_autoload do autoload :Foo # => require 'mod/foo' end end Mod.eager_load! # => ここで読み込まれる
便利だ。
golang を始めた時
そういやまだ新しい経験だから覚えてるし、 golang を勉強していった過程を書いておく。
正しさとかは気にせず、動くものが出来るまでがむしゃらに。
Hello, world まで
特にひねりはない。 homebrew は便利だ。
$ brew install go $ cd ~ $ mkdir -p go/src/hello_world $ export GOPATH=${HOME}/go $ cd go/src/hello_world $ cat main.go package main import "fmt" func main() { fmt.Println("hello, world") } $ go run main.go hello, world
手軽。
入門、そして組み込みパッケージを使う
http://golang.org/pkg/ に組み込みパッケージの一覧があるので参照する。だいたいここ見ればどういう引数を与えるべきかがわかる。
まずもって golang のことを知らないので見てもさっぱりわからない。苦痛。なので http://tour.golang.org/ をやる。
なるほど〜。こんな感じなのか〜。わかんね〜。http://golang.org/doc/code.html を読む。正直いきなり REPL っぽいことさせられるよりわかりやすい。ああ、こういうディレクトリ構成が普通なのね、とかすんなりわかる。
さっきの "fmt" とかは fmt パッケージなのか、とかもこの辺でやっとわかってくる。まだこの辺では import はおまじない状態だ。 package main の意味もよくわかってない。
GOPATH 以外でコードを書く
GOPATH の中に引きこもるのが嫌すぎるのでどっかべつのとこでやりたい。
go/pkg/ ... のパスから package を探すようなので、src から外れても実は問題なくコードは書ける。問題はパッケージの import だけで、外部のライブラリを使う分には gondler で解決出来る。では自作のローカルパッケージをどうするかになる。
たとえば以下の様な構成の場合。
- project
- main.go
- mylib
- mylib.go
この時、 main.go
から mylib
パッケージを使いたい。
import "./mylib"
で解決した。なんてことはない。相対パスかけばいいだけだった。
外部ライブラリは gondler で、ローカルパッケージは相対パスで食わせればオッケーとおぼえておくと人生が楽になる。
自分の Github に上がっているような、別リポジトリの場合は gondler に任せるのがよい。 gondler の使い方は次。
Gondler 使う
$ gem install gondler
もしくは
$ cat Gemfile group :development do gem 'gondler' end $ bundle install $ bundle exec gondler
のように使う。 Gemfile でバージョンを固定しておくと各メンバーの gondler のバージョンを固定出来てよい。 gondler はまだ 0.x.x なので固定しておかないと非互換な変更が入る可能性があることを考慮しておくとよい。
あとは Gomfile を書くだけ。
$ cat Gomfile package 'github.com/golang/glog'
難しさは特にないと思うがどうか。
ここまで来ると
なんとなくいろいろ書ける気がしてくる。普通の executable なものを作る分にはこれで大抵問題なくなる。
revel とか使うともっと制限がかかって大変だけど、使う人は頑張って欲しい。僕は revel 使うのだるすぎるし結局省力化されてない気がしたのと、別に golang ででかい Web アプリ書きたくなかったという大前提に直面して使うのやめた。
いいコードを探す
読んだりしたコードは多分そこそこあるんだけど、Github の Explore で go の Trending Repos とか読むのがよい。 vegeta あたりは小さくて読んでてためになる。
チャネルの使い方とか難しいから、自分で書きつつ人のコード見て使い所検討するのがよい。僕は未だに慣れてない。
ドキュメントを探す
外部パッケージは大抵 godoc.org でドキュメント化出来るようにコメント書いてある。コメントなくても引数名と型で大抵のことはなんとなくわかる。より詳細を知りたければコードを読むしかないが、これは Ruby の世界もそんなもんだし、結局コード読むからまあいい。許した。
Dash で go の package とかはローカルに落としておけるので使うのがおすすめ。ドキュメントは何度も読み返すことになる。いつもおもうけどこのへんの暗記ゲーだけなくなってほしい。僕は暗記苦手だから多分コード書いてる時間で一番 Dash を開いてる。
Dash 使ってない人は普通にブラウザから読むといいと思うけど、Dash の方が便利だと思う。理由はこれ。
Functions とかが出るのが便利。あと HUD モードで Global shortcut 設定しておくとすぐ呼び出せる。
vim の設定とか
http://qiita.com/todogzm/items/3c281da10287f7383487 この辺り読むといい。もともと補完に頼らない派の人間なので :Fmt と :Import ぐらいしか使ってない。
:Import の補完候補には GOPATH 以下にある pkg も対象になるけど、 gondler だとこの辺がサポートできてないので、ちょっと考えないといけない。
よりよいコードを書くために
あと触れてないのなんだろ、楽なテスト手法とかかな。僕は testing しか使ってないからわからない。"testing/quick" とかもあるけど、あれあんまクイックになってないと思う。 gocode とかで補完されると便利なのかもしれない。
結構世の中には golang の勉強会があるようで、そういうところで発表された資料とかも転がってる。あとはよく見てる所。
- mattn さんのブログとか http://mattn.kaoriya.net/
- Qiita とか http://qiita.com/tags/go
他にも読んでるけど、今思いついたのがこれだけだった。各位すいません。
他にも『ここで詰まってる』とか具体的な話されると答えられることあるかもしれないけど、わかんない。ちなみに現状まで大体2ヶ月くらいだと思う。始めたの9月ごろだったはず。
grape-msgpack v0.1.0
grape-msgpack
という gem を作った(github / rubygems)。
最近 golang ばっかり触ってるように見えるのは幻覚で、僕はずっと Rubyist だし Ruby は最高だしオープンクラスは地獄だ。
Grape では Formatter がいくつか選べるようになっていて、大体 JSON マジおすすめみたいな感じなんだけど、僕は msgpack 使いたいなって時があるから簡単に msgpack 使えるようにした。
require 'grape-msgpack'
これで default_formatter :msgpack
とか出来るようになる。どんどんこんな感じでコード書かなくなっていって、最後には『必要になった瞬間にソフトウェアは完成している』みたいな SF 世界まで到達して欲しい。
Grape ユーザー、国内であんまりみたことないけど、僕が cacheable を書いてる Sequel もあんまりユーザー見たことないし、そういうもんだと思う。僕ぐらいでもプラグインで貢献出来る知名度、最高。