Railsメモ(19) : BulletでN+1問題を検出する
Bulletの設定
N+1問題を検出するためにBulletというgemを試してみる。
Gemfile
に下記行を追加してbundle install
する。
group :development, :test do gem 'bullet' end
Bulletはconfig/environments/development.rb
に設定を追加しないと動作しないのでファイルを編集する。
用意されている基本的な設定は以下の通り。
config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.bullet_logger = true Bullet.console = true Bullet.growl = true Bullet.xmpp = { :account => 'bullets_account@jabber.org', :password => 'bullets_password_for_jabber', :receiver => 'your_account@jabber.org', :show_online_status => true } Bullet.rails_logger = true Bullet.honeybadger = true Bullet.bugsnag = true Bullet.airbrake = true Bullet.rollbar = true Bullet.add_footer = true Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ] Bullet.slack = { webhook_url: 'http://some.slack.url', foo: 'bar' } end
全てを追加する必要はないので、今回は必要そうな部分だけ追加してみる。
config.after_initialize do Bullet.enable = true # Bulletを有効化 Bullet.alert = true # JavaScriptのポップアップアラートを有効化 Bullet.bullet_logger = true # Rails.root/log/bullet.logに出力 Bullet.console = true # ブラウザのconsole.logに出力 Bullet.rails_logger = true # Railsのログに結果を出力 Bullet.add_footer = true # ページの左下に結果を表示 end
以降はその他の設定。
Bulletには検出する問題をタイプごとに無効化する設定も存在する。
# Each of these settings defaults to true # Detect N+1 queries Bullet.n_plus_one_query_enable = false # Detect eager-loaded associations which are not used Bullet.unused_eager_loading_enable = false # Detect unnecessary COUNT queries which could be avoided # with a counter_cache Bullet.counter_cache_enable = false
問題ないことがわかっている場合など通知してほしくない場合はホワイトリストの設定ができる。
Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities
コントローラー単位で通知をスキップする場合は以下のように記述する。
class ApplicationController < ActionController::Base around_action :skip_bullet def skip_bullet Bullet.enable = false yield ensure Bullet.enable = true end end
Bulletの使い方
gemをインストールして設定ファイルに追記すればアプリのページ表示時に問題があれば通知されるようになるので、試しにN+1問題があるページにアクセスしてみる。
1番目の図がBullet.alert = true
にしたときに表示されるダイアログで、2番目の図がBullet.add_footer = true
にしたときに左下に表示されるフッターである。
メッセージを見るとわかるようにN+1問題が検出され、この問題を解決するためには:includes => [:artists]
を追加すればよいとある。
ということで指示通りにapp/controllers/songs_controller.rb
にincludes
を追加してみる。
class SongsController < ApplicationController def index @q = Song.ransack(params[:q]) #@songs = @q.result.page(params[:page]) # 編集前 @songs = @q.result.includes(:artists).page(params[:page]) # 編集後 end end
編集後、再アクセスしてみるとBulletによる警告が表示されなくなり、実行されているSQLも102から4つに減ったことが確認できる。
参考:問題があったモデル、コントローラー、ビュー
$ rails c Loading development environment (Rails 4.2.3) [1] pry(main)> show-models Artist id: integer name: string created_at: datetime updated_at: datetime has_many :song_artists has_many :songs (through :song_artists) Song id: integer title: string display_artist: string ranking: integer year: integer created_at: datetime updated_at: datetime has_many :artists (through :song_artists) has_many :song_artists SongArtist id: integer song_id: integer artist_id: integer created_at: datetime updated_at: datetime belongs_to :artist belongs_to :song
class SongsController < ApplicationController def index @q = Song.ransack(params[:q]) @songs = @q.result.page(params[:page]) end end
<table class="table table-striped table-hover"> <thead> <tr> <th><%= sort_link(@q, :title) %></th> <th><%= sort_link(@q, :display_artist) %></th> <th><%= sort_link(@q, :ranking) %></th> <th><%= sort_link(@q, :year) %></th> </tr> </thead> <tbody> <% @songs.each do |song| %> <tr> <td><%= song.title %></td> <td> <% song.artists.each do |artist| %> <% song.display_artist.gsub!(/#{artist.name}/) {link_to artist.name, artist_path(artist)} %> <% end %> <%= song.display_artist.html_safe %> </td> <td><%= song.ranking %></td> <td><%= song.year %></td> </tr> <% end %> </tbody> </table> <%= paginate @songs %>
- 作者: すがわらまさのり,前島真一,近藤宇智朗,橋立友宏
- 出版社/メーカー: 技術評論社
- 発売日: 2014/06/06
- メディア: 大型本
- この商品を含むブログ (8件) を見る