19
13

More than 3 years have passed since last update.

余計なクエリ数を減らしたい。counter_cacheの検討からcounter_cultureの導入まで。

Posted at

起こっていた問題

Railsでインスタグラムを模したアプリを作っています。
データベース構造はこんな感じです。

Image from Gyazo

このアプリのトップページ(投稿一覧)でこんなメソットを実装していたところ、

  - if post.images.count >= 2
    - # 処理

クエリがこんなことになってしまいました^^;

Image from Gyazo

imagesのcountが大量に回っている...^^;

そういえば、Railsにはcounter_cacheという機能がデフォルトであって、こういうときに役立つんだよ、と聞いたことがあったので、調べてみたのが今回の始まりです。

なお、実行環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.0

counter_cacheについて

まず、上記の問題を解決する手段として、

の3種類があることがわかりました。(3つ目は教えていただきました!ありがとうございます!!)

まず、Railsのデフォルトの機能のcounter_cacheなのですが、以下のように実装します。

models/image.rb
class Image < ApplicationRecord
  belongs_to :post, counter_cache: true
end

従属している(belongs_to)がついている方のモデルに、counter_cache: trueを記載します。

models/post.rb
class Post < ApplicationRecord
  has_many :images
end

has_manyの方のモデルはそのままなのですが、こちらには、子テーブルの名前_countというカラムを作ります。今回の場合はimages_countというカラムをpostsテーブルに作ります。

$ rails g migration AddColumnToPost images_count:integer

こうすることで、images_countカラムには、postが持っているimageの数が記録される様になります。

ビューでは、こんなふうに書けば、いちいちimagesテーブルを呼び出さずにクエリ数も減るはず...

- if post.images_count >= 2
  -# 処理 

...と、ここまで書いて気がついた。すでにpostのデータがある場合はどうすればいいんだ。。。:sob:

はい、counter_cacheの弱点はまさにそれで、すでにpostsテーブルにデータがある場合、images_countの値は自動更新されません。

先人たちが色々苦労されて修正された様子も見つけたのですが、構造が謎すぎて自分には使いこなせそうにありませんでした。。。

そこで、さらに調べていく中で出会ったのが次に紹介するcounter_cultureです。

counter_cache補足

なお、counter_cacheについては、デフォルトのカラム名を変更したりなど、色々なオプションがありましたので、詳しくはこちらをご覧ください。

Railsガイド - 4.1.2.3 :counter_cache

counter_cultureについて

counter_cultureは上記のcounter_cacheの弱点も補強してくれるような高機能のgemです。

▶︎公式ドキュメントはこちら

導入方法

gemfile・migration

導入方法は以下の通りです。まず、Gemfileに以下を記載しbundle installします。

Gemfile
gem 'counter_culture'

その後、以下のコマンドでマイグレーションを生成します。

$ rails generate counter_culture Post images_count

こちらのコマンドの結果、以下のようなマイグレーションが作成されます。

XXXXXXXXXXXXXXX_add_images_count_to_posts.rb
class AddImagesCountToPosts < ActiveRecord::Migration[5.2]
  def self.up
    add_column :posts, :images_count, :integer, null: false, default: 0
  end

  def self.down
    remove_column :posts, :images_count
  end
end

同様の構造を持つマイグレーションファイルであれば、必ずコマンドで作成しないといけない、というわけではないようです^^

rails db:migrateします。

model

モデルファイルには、それぞれ以下の様に記載します。

models/image.rb
class Image < ApplicationRecord
  belongs_to :post
  counter_culture :post
end
models/post.rb
class Post < ApplicationRecord
  has_many :images
end

その他

そして、すでにpostsテーブルにたくさんデータがあるなどの場合、コンソールで以下の様に実行します。

$ rails c
pry(main)> Image.counter_culture_fix_counts

こうすることで、postsテーブルのimages_countのカラムの中身がアップデートされます。

最後に、ビューでこの様に書けば...

- if post.images_count >= 2
  -# 処理 

無事、クエリ数が減りました!!:smiley:

Image from Gyazo

補足・参考資料等

counter_cultureにはオプションが色々ある様でしたので、詳しく知りたい方は公式ドキュメントをご覧ください。

また、今回参考にさせていただいた記事は以下の通りです。

その他・感想等

今回の実装前に、N+1問題も発生していたので、その解決方法も参考までにご共有いたします。

クエリ数を減らしたいが今年の目標の一つだったのですが、SQLをゴリゴリ勉強する前に、手早く減らせる方法が見つかってよかったです^^

今後は、SQLの勉強を頑張っていきたいです。

19
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13