Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:acts-as-taggable-onのN+1問題を解決する

自分で運営しているサービスのタグ付け機能にacts-as-taggable-onを使っていたのですが、N+1が発生しまくってしまったので、その解決方法をφ(..)メモメモ

acts-as-taggable-onのバージョンは下記の通りです。

$ gem list
acts-as-taggable-on (5.0.0)

解決法

修正前のコード

まず修正前のコードですが、普通にPageの全件を取得してtag_listでタグの名称のリストを取得して、それを表示するようなコードですね(・∀・)

controller

  def index
     @pages = Page.all
  end

view

<% @pages.each do |page| %>
  <% if page.tag_list.present? %>
    タグ:<%= page.tag_list.join(', ') %>
  <% end %>
<% end %>

しかし、以下のようなコードだと下記のようにタグの検索SQLがレコード単位に発生してパフォーマンスが良くないです。。。(N+1問題が発生してます、、、。)

log

  ActsAsTaggableOn::Tagging Load (0.5ms)  SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2  [["taggable_id", 23], ["taggable_type", "Page"]]
  ActsAsTaggableOn::Tag Load (0.7ms)  SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" ="taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL)  [["taggable_id", 23], ["taggable_type", "Page"]]
  ActsAsTaggableOn::Tagging Load (0.6ms)  SELECT "taggings".* FROM "taggings" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2  [["taggable_id", 16], ["taggable_type", "Page"]]
  ActsAsTaggableOn::Tag Load (1.0ms)  SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" ="taggings"."tag_id" WHERE "taggings"."taggable_id" = $1 AND "taggings"."taggable_type" = $2 AND (taggings.context = 'tags' AND taggings.tagger_id IS NULL)  [["taggable_id", 16], ["taggable_type", "Page"]]

修正後のコード

修正のポイントは、下記の2つですφ(..)

  • tagsincludesし、キャッシュすること
  • タグの名称取得にがtag_listを使用しないこと

controller側で、Pageを取得する際に.includes(:tags)を追記し、キャッシュするようにしています。

controller

  def index
     @pages = Page.all.includes(:tags)
  end

また、viewで表示するときにはtagsを使用し、名前だけ取得するときにはpluckを使うようにします(・∀・)

view

<% @pages.each do |page| %>
  <% if page.tags.present? %>
    タグ:<%= page.tags.pluck(:name).join(', ') %>
  <% end %>
<% end %>

これにより、毎回タグ関連のTBLを取得するSQLが発生を防ぐことが出来ました!\(^o^)/

参考

qiita.com