RailsのN+1回カウントするクエリを改善するactiverecord-precount


Ruby on Railsをモデルでidごとのcountを取るような場合に発生しがちなN+1 countクエリを高速化するRubyGems『activerecord-precount』を紹介します。

🐞 概要

次のようなN+1カウントのクエリが発生する場合:

Tweet.all.each do |tweet|
p tweet.favorites.count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 1
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 2
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 3

precountメソッドを使うことで、2つのSQLでid単位のcountを取れます。

Tweet.all.precount(:favorites).each do |tweet|
p tweet.favorites_count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(`favorites`.`tweet_id`), `favorites`.`tweet_id` FROM `favorites` WHERE `favorites`.`tweet_id` IN (1, 2, 3, 4, 5) GROUP BY `favorites`.`tweet_id`

Joinも合わせて行いたい場合はeager_countメソッドを使うことで、1つのSQLでcountをとれます。

Tweet.all.eager_count(:favorites).each do |tweet|
p tweet.favorites_count
end
# SELECT `tweets`.`id` AS t0_r0, `tweets`.`tweet_id` AS t0_r1, `tweets`.`user_id` AS t0_r2, `tweets`.`created_at` AS t0_r3, `tweets`.`updated_at` AS t0_r4, COUNT(`favorites`.`id`) AS t1_r0 FROM `tweets` LEFT OUTER JOIN `favorites` ON `favorites`.`tweet_id` = `tweets`.`id` GROUP BY tweets.id

🤔 インストール手順

Gemfileに次の内容を追加してbundle installを実行します。

# N+1 count query killer for ActiveRecord
gem 'activerecord-precount'

🎉 参考リンク

📚 おすすめの書籍

🖥 サーバについて

このブログでは「Cloud Garage」さんのDev Assist Program(開発者向けインスタンス無償提供制度)でお借りしたサーバで技術検証しています。 Dev Assist Programは、開発者や開発コミュニティ、スタートアップ企業の方が1GBメモリのインスタンス3台を1年間無料で借りれる心強い制度です!(有償でも1,480円/月と格安)