Rails

【Rails】N+1問題をアラート表示してくれるgem「bullet」が超絶便利だった!

Rails

 

今回は、N+1問題をアラート表示してくれるgem「bullet」について解説したいと思います。N+1問題はアプリケーションのパフォーマンス低下に繋がってしまうため、動きさえすれば良いというアプリケーション以外は、ぜひ導入を検討していただけたらと思います。

更新(プルリクエスト)も頻繁にされているgemなので、比較的安全だと思うよ。

N+1問題とは

まずはN+1問題の概要について簡単におさらいしておきましょう。

N+1問題とは簡単に言うと、データベースへのアクセス回数が余計に多くなってしまう現象です。これは、モデル間のアソシエーションを利用する場合に発生します。

以下の場合だと、クエリ(検索文)が4回発行されていることになります。これだとアプリケーションのパフォーマンスが低下してしまいますね。

クエリの発行回数

 

しかしincludesメソッドを使用することで、パフォーマンスを改善することができます。includesメソッドを使うと、以下のようにレコードを一気に取得することができるので非常に便利です。

includesメソッドを使用した時のクエリの発行回数

 

N+1問題について詳しく知りたい方はこちらの記事を参考にしてください。

Rails
【Rails】パフォーマンス低下に繋がるN+1問題とは?解決策と併せて解説!アプリケーションのパフォーマンス低下に繋がる問題「N+1問題」について解説しています。練習用の小さなアプリケーションではあまり気にする必要はありませんが、本格的にアプリケーションを開発する際は、パフォーマンスは非常に重要になってきますので、ぜひincludesメソッドを使いこなせるように練習しておきましょう。...

gem「bullet」とは

では本題のgem「bullet」について見ていきましょう。

bulletとは、N+1問題が起きている箇所を特定・お知らせしてくれる便利なgemです。

テーブル数が増えてくると、N+1問題が起きているのにも関わらず見逃してしまうケースが多発します。それを避けるため、個人的には導入しておくべきgemの1つだと思っています。

The Bullet gem is designed to help you increase your application’s performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you’re using eager loading that isn’t necessary and when you should use counter cache.

参照:https://github.com/flyerhzm/bullet

bulletを実際に使ってみよう

では簡単なアプリケーションを作成しながらbulletの使い方を学んでいきましょう。

アプリケーションの作成にはscaffoldを使用するので、scaffoldが曖昧な方はこちらの記事に目を通しておいてください。

Rails
【Rails】秒速でアプリケーション開発できる、scaffoldの使い方を簡単にまとめてみた手っ取り早くアプリケーションを開発することができるscaffold(スキャフォールド)の使い方について簡単に解説しています。新しい機能実装をテストしてみたい、すぐにでもアプリケーションを立ち上げたいといった場合に便利な機能なので、ぜひ覚えておきましょう。...

事前準備

まずは以下のコマンドでアプリケーションを立ち上げます。立ち上げ後、データベースも作成しておきましょう。

$ rails new test_bullet -d mysql
$ cd test_bullet
$ rails db:create

今回開発するアプリケーションの環境は以下のようになります。

  • アプリケーション名:test_bullet
  • Rails:6.0.3.2
  • Ruby:2.6.5
  • DB:MySQL

 

次にscaffoldの機能を使い、postモデルとcommentモデルに関する土台を作成します。マイグレートも忘れずに行ましょう。

$ rails g scaffold post name:string
$ rails g scaffold comment name:string post_id:integer
$ bundle exec rails db:migrate

Image from Gyazo
Image from Gyazo

 

先ほど作成したpostモデルとcommentモデルのアソシエーションも組んでおきましょう。postモデルとcommentモデルは1対多の関係になるので、以下のように記述することができます。

↓ post.rbとcomment.rb

class Post < ApplicationRecord
  has_many :comments
end
class Comment < ApplicationRecord
  belongs_to :post
end

 

続いてコンソールを立ち上げ、データベースに値を入れていきましょう。

$ rails c
irb > post1 = Post.create(:name => 'first')
irb > post2 = Post.create(:name => 'second')
irb > post1.comments.create(:name => 'first')
irb > post1.comments.create(:name => 'second')
irb > post2.comments.create(:name => 'third')
irb > post2.comments.create(:name => 'fourth')
irb > exit

Image from Gyazo
Image from Gyazo

 

次に「app/views/posts/index.html.erb」を以下のように編集し、N+1問題が起きるよう記述します。

<p id="notice"><%= notice %></p>

<h1>Posts</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.name %></td>
        <td><%= post.comments.map(&:name) %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>
<%= post.comments.map(&:name) %>がN+1問題が起きてしまう箇所だね。

 

ここまででどのような挙動になるか一旦確かめてみます。「rails s」でサーバーを立ち上げ、「http://localhost:3000/posts」にアクセスしてみましょう。

Image from Gyazo

 

先ほどコンソールでデータベースに入れた情報(nameとcomment)が表示されていますね。これで事前準備が完了です。

bulletの導入

ではここから実際にbulletを導入していきます。

Gemfileに「gem ‘bullet’」を記載し、「bundle install」を実行しましょう。

gem "bullet"
$ bundle install

 

インストールが完了したら、以下のコマンドでbulletが使用できるようにします。

$ bundle exec rails g bullet:install

 

これで自動的にbulletがN+1問題が起きている箇所を特定し、ブラウザでアラートを表示してくれるようになりました。「rails s」でサーバーを立ち上げ、「http://localhost:3000/posts」にアクセスしてみましょう。

Image from Gyazo
原因箇所と解決方法をJSのアラートで教えてくれているね。

 

では「posts_controller.rb」をN+1問題が起きないように編集します。「posts_controller.rb」のindexアクションを以下のように書き換えてください。

def index
  @posts = Post.includes(:comments)

  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @posts }
  end
end

 

これでもう一度ブラウザをリロードしてみましょう。

Image from Gyazo

 

アラート表示が消えましたね!

このようにgem「bullet」を用いると、アプリケーションのパフォーマンスに大きく関わるN+1問題をアラート表示してくれるので、導入しておいて損はないと思います。

bulletを導入したけど、アラートがうまく表示されない!修正したのにアラートが消えない!といった場合は、ブラウザのキャッシュが悪さをしている場合があります。その場合は、ブラウザのキャッシュ機能をOFFにしておきましょう。

bulletを使いこなす

bulletには様々なオプションがあり、それらを使うには明示的に記述する必要があります。このオプションは「config/environments/development.rb」に記載されています。

config.after_initialize do
  Bullet.enable = true
  Bullet.sentry = 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.skip_html_injection = false
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
end
使いたいオプションはtrue、使わないオプションはfalseに設定すればいいね。

 

また修正する必要がない部分でアラート表示が出てしまう場合にも、明示的に記述することでアラートを無視することができます。こちらも同様に「config/environments/development.rb」に記載します。

↓ 記述例

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

まとめ

  • N+1問題とは、データベースへのアクセス回数が余計に多くなってしまう現象のこと
  • bulletとは、N+1問題が起きている箇所を特定・お知らせしてくれるgemのこと
  • bulletは、使いたいオプションを明示的に示すことでカスタマイズが可能

参考文献

Github:https://github.com/flyerhzm/bullet

 

 

今回はN+1問題をアラート表示してくれるgem「bullet」について解説しました。個人開発や練習用のアプリケーションでは導入の必要はありませんが、大規模開発になるとパフォーマンスは非常に重要になってくるので、ぜひ「bullet」というgemがあるということを頭に入れておきましょう。