自分で運営しているサービスのタグ付け機能に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つですφ(..)
tags
をincludes
し、キャッシュすること- タグの名称取得にが
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^)/