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

リア充爆発日記

You don't even know what ria-ju really is.

Railsのrspecが遅いので対応した結果wwwwwwwwwwwwww

rspecで書いているテストが遅い。CPUもめちゃ回る。めちゃ回ってあのスピードか。

rspec + guard + spork(spring試したい)で自動テストを回しているので、あまりテストのパフォーマンスが開発に影響することはなかったんだけど、けっこう大きいmodelの修正とかになると、1modelのテスト結果がでるまでも数秒かかってしまっていて、いよいよ耐えられなくなってきた。

ので、調査対応したときのメモ。

遅いテストを調べる

rspec2系なら--profileというオプションをつければ遅いテストワースト10がテスト終了後に表示される。毎回表示されてもいいと思うので.rspecファイルに書いておく。
.rspec

--colour
--drb
--profile ←追加

ちなみに--profile 15とか数字を引数につけるとワースト10がワーストNになる。

遅いテストの内訳を調べる

Top 10 slowest examples (22.01 seconds, 13.3% of total time):
  Hoge scope:recent return 10 rows 
    4.99 seconds ./spec/models/hoge_spec.rb:152
  Api::HogesController GET recent data should respond with 10 rows
    4.17 seconds ./spec/controllers/api/hoges_controller_spec.rb:56
  Hoge fetch_hoge_image_url should fetch the first image out of hoge points 
    3.18 seconds ./spec/models/hoge_fuga_spec.rb:109
~snip~
  Api::PiyoController PUT by invalid user should return status 404
    1.28 seconds ./spec/controllers/api/piyos_controller_spec.rb:36

一番遅いテストのコードは以下の様な感じ。

   describe "scope:recent" do
     context "return 10 rows" do
       before do
         11.times do
          FactoryGirl.create(:hoge, status: HogeStatus::PUBLIC)
         end
       end
       specify { Hoge.recent(0).count.should be(10) }
     end
~snip~


まあどう考えてもFactoryGirl.createを11回ループしているところが重いわけだ。

バルク(バッチ)インサートに修正する

こういう場合の常套手段であるbulk insertに修正してみる。毎回createすることによって発生するDBとのコネクションなどのコストを下げることによりパフォーマンスアップを図る。

Railsでbulk insertをする場合activerecord-importというgemを利用するのがよさそうだ。

Gemfile

~snip~
gem "activerecord-import", "~> 0.3.1"
~snip~


これに合わせてテストコードも修正した。

  describe "scope:recent" do
    context "return 10 rows" do
      before do
        hoges = []
        11.times do
          hoges << FactoryGirl.build(:hoge, status: HogeStatus::PUBLIC)
        end
        Hoge.import hoges
      end
      specify { Hoge.recent(0).count.should be(10) }
    end
~snip~

FactoryGirl.createのかわりにbuildにして一回のDBコネクションかつ1回のSQLで11件のデータを入れるように変えた。

期待して結果を待つ。

Top 2 slowest examples (5.52 seconds, 100.0% of total time):
  Hoge scope:recent return 10 rows 
    5.13 seconds ./spec/models/hoge_spec.rb:162
  Hoge scope:recent return only public hoges 
    0.39086 seconds ./spec/models/hoge_spec.rb:194

ぜんぜん改善しねー。カッコワリー。


FactoryGirlのテストデータを見直す

hogeモデルのテストデータ定義を見なおしてみた。

FactoryGirl.define do

  factory :hoge do
    sequence(:title) { |n| "hoge title#{n}" }
    sequence(:description) { |n| "hoge description#{n}" }
    status HogeStatus::PUBLIC
    user
  end

hogeモデルはuserモデルとhas_many関係にあるのでuserが定義してある。これか。buildするとhogeはDBにいれらんないけどuserは毎回作られてDBに入れられてるわけだ。


もう1回テストを書きなおす

ぜんぶ同じuserに紐づくhogeデータでもテストしたい内容に変わりはないので1userしか作られないように修正する。

  describe "scope:recent" do
    let(:user) { FactoryGirl.create(:user) }
    context "return 10 rows" do
      before do
        hoges = []
        11.times do
          hoges << user.hoges.new(
            title: "title",
            description: "desc",
            status: HogeStatus::PUBLIC)
        end
        Hoge.import hoges
      end
      specify { Hoge.recent(0).count.should be(10) }
    end
~snip~


結果を見てみる

Top 2 slowest examples (1.08 seconds, 100.0% of total time):
  Hoge scope:recent return only public hoges 
    0.60324 seconds ./spec/models/hogee_spec.rb:193
  Hoge scope:recent return 10 rows 
    0.47999 seconds ./spec/models/hogee_spec.rb:161


超絶改善wwwwwwwwwwwww


追記)FactoryGirl.buildを使ったほうがいいと気づいた

  describe "scope:recent" do
    let(:user) { FactoryGirl.create(:user) }
    context "return 10 rows" do
      before do
        hoges = []
        11.times do
          hoges << FactoryGirl.build(:hoge, user: user, status: HogeStatus::PUBLIC)
        end
        Hoge.import hoges
      end
      specify { Hoge.recent(0).count.should be(10) }
    end
~snip~