前回、counter_cacheでN+1カウントクエリを解消する手順でRailsの標準機能であるcounter_cacheを利用してN+1カウントクエリを解消する方法について紹介しました。
今回はcounter_cacheの高機能版にあたるgem、counter_cultureを利用してN+1カウントクエリを解消する方法について紹介します。
目次
今回利用するサンプルについて
1:多で紐づいたAuthorモデルとBookモデルを例に、N+1カウントクエリの解消方法を紹介します。
counter_cultureについて
counter_cultureはRails標準のcounter_cacheの機能を改良したgemです。
counter_cacheと同様、集計対象のレコード数を集計用カラムに保存することでN+1カウントクエリを解消します。
主な機能は以下の通りです。1
- 作成時・破棄時だけでなく値の変更時もカウントのキャッシュが更新される
- 多段リレーションのカウントのキャッシュをサポート
- 動的なカラム名のサポート
- レコード数だけでなく、各レコードが持つ数値の合計のキャッシュが可能
counter_cultureの導入手順
counter_cacheの導入手順について紹介します。
Gemの追加
Gemfile
gem 'counter_culture'
$ bundle
集計対象のモデルの修正
集計対象のモデルにcounter_culture: [集計元のモデル名]
を追加します。
今回の場合、Authorモデルに紐づくBookモデルの数をキャッシュするためBookモデルを修正します。
book.rb
class Book < ApplicationRecord
belongs_to :author
counter_culture :author
end
集計用カラムの追加
集計した数を保存するカラムを作成します。
Bookモデルにcounter_culture: :author
を追加した場合、authorsテーブルにbooks_count
カラムを追加します。
カラムはcolumn_name
オプションで任意の名前に変更できます。
たとえばcounter_culture: :author, column_name: books_cache_count
とした場合、authorsテーブルにbooks_cache_count
カラムを追加します。
マイグレーションファイルはrails generate counter_culture
で作成できます。
$ rails generate counter_culture Author books_count
xxx_add_books_count_to_authors.rb
class AddBooksCountToAuthors < ActiveRecord::Migration[6.0]
def self.up
add_column :authors, :books_count, :integer, null: false, default: 0
end
def self.down
remove_column :authors, :books_count
end
end
コードの修正
countクエリの代わりに新しく追加したbooks_count
カラムを利用します。
Author.all.each do |author|
- author.books.count
+ author.books_count
end
動作確認
ログを確認するとN+1カウントクエリが発生していないことがわかります。
Author Load (0.6ms) SELECT `authors`.* FROM `authors` ORDER BY `authors`.`id` ASC LIMIT 1000
Author Load (0.6ms) SELECT `authors`.* FROM `authors`
コンソールでレコードを作成すると、books_count
カラムにbooks.count
の値が保存されていることがわかります。
author = Author.new(name: "hoge")
author.save
author.books.count
=> 0
author.books_count
=> 0
author.books.create(title: "fuga")
author.books.count
=> 1
author.books_count
=> 1
countuer_culture導入前のレコードを集計カラムに反映させる方法
レコードが追加されるたびに集計用カラムの値が変更されます。
ただしcounter_culture導入前に集計対象のレコードがすでに存在していた場合、その数は集計用カラムに反映されていません。
counter_culture_fix_counts
メソッドを実行することでcounter_culture導入前のレコードを集計カラムに反映できます。
### counter_culture導入前に紐づいていた3つのbooksは反映されていない
author = Author.find(1)
author.books.count
=> 3
author.books_count
=> 0
### 集計数の反映実行
Book.counter_culture_fix_counts
### counter_culture導入前に紐づいていた3つのbooksが反映される
author = Author.find(1)
author.books.count
=> 3
author.books_count
=> 3
まとめ
- 集計対象の子モデルにcounter_cultureオプションを追加する
- 集計用のカラムを親モデルに追加する
- countクエリの代わりに追加したcountカラムを利用する
- 既存のレコードを反映したい場合はcounter_culture_fix_countsメソッドを実行する
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!