counter_cultureでN+1カウントクエリを解消する手順

Ruby

前回、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_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の利用手順
  1. 集計対象の子モデルにcounter_cultureオプションを追加する
  2. 集計用のカラムを親モデルに追加する
  3. countクエリの代わりに追加したcountカラムを利用する
  4. 既存のレコードを反映したい場合はcounter_culture_fix_countsメソッドを実行する

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!