Bulletを導入して、N+1問題を検出する方法

✏️️ 2019/08/16 👍️2019/08/19 🔗

bulletN+1問題を検出してくれるgemです。bulletを導入してクエリのパフォーマンスを落とさないように気をつけます。

Bulletをインストール

# Gemfile
group :development do
  gem 'bullet'
end
bundle install

Bulletの設定

config/environments/development.rbに以下の設定を追加します。

# Settings for bullet
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end
設定 意味
Bullet.enable Bulletを有効にする
Bullet.alert JavaScriptアラートをブラウザで表示
Bullet.bullet_logger Rails.root/log/bullet.logにlogを出力
Bullet.console console.logに出力
Bullet.rails_logger Rails logに出力

設定はbullet#configurationを参考

N+1問題の対処方

N+1問題を発見したら、includesで対象関連テーブルを読み込むようにします。

# 関連テーブルが一つの場合
posts = Post.includes(:tags).limit(10)

# 関連テーブルが複数の場合
posts = Post.includes(:tags, :categories).limit(10)

# 先の関連テーブルを読み込みたい場合
Category.includes(posts: { comments: :guest }).find(1)

無視したい場合

ホワイトリストに追加できます。

white listの設定ファイルを作成

# config/initializer/bullet.rb

# N+1問題を無視
Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments

# 使ってないeager_loadingもOK
Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments

# 回避できる不要なCOUNTクエリを検出を無効
Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities

特定のコントローラーアクションでスキップするには、skip_bulletメソッドを作成すると便利です。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  around_action :skip_bullet, if: -> { defined?(Bullet) }

  def skip_bullet
    previous_value = Bullet.enable?
    Bullet.enable = false
    yield
  ensure
    Bullet.enable = previous_value
  end
end

以上で、Bulletを導入して、N+1問題を検出する方法は完了です。

参考

akito
日本のスタートアップで主にRuby on Railsを使ってプロダクト開発をしています。