経緯

N+1問題が発生しないようにプログラムを書いているはずだけど、どこかで起きてしまっていそうで不安。。。

ということがあって、何か判定してくれるGemないかなーと思ってたら、案の定あったので、ご紹介。

N+1問題とは

そもそもN+1問題って何?って人のために。

例えば、Articleに紐づくUserというものがあったとします。Article一覧を出しつつ、著者の名前も表示したいというとき、単純にかくと、以下のようになります。

class ArticlesController < ApplicationController

  def index
    @articles = Article.all
  end

end
<table>
  <thead>
    <tr>
      <th>タイトル</th>
      <th>コメント数</th>
    </tr>
  </thead>
  <tbody>
    <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.user.name %></td>
    </tr>
    <% end %>
  </tbody>
</table>

ただ、ここでログを見ていると、

SELECT "articles"* FROM "articles";

のあとに

SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1

みたいなものが大量に吐かれます。

最初、記事一件を取得するために1クエリが走って、そのあと、N件あれば、Nクエリ走ってしまうことから「N+1問題」と呼ばれています。

これを放置しておくと、100万件とかになったとき、パフォーマンスに影響してしまいます。

Bullet

https://github.com/flyerhzm/bullet

いつものようにGemfileの中にgem 'bullet', group: :developmentと書いて、bundle install --path vendor/bundleを実行してやりましょう。

config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end

あとは、普通にWebサーバーを起動するだけ。http://example.com/articles/にアクセスすると、「N+1問題が発生してるよ!」とJSのアラートが表示されます。

まとめ

パフォーマンスに運用上、影響しないところでも、パッとできることで、修正できるなら、直しておいてあげたほうがプロダクトとしては健全ですよね。