LoginSignup
15

More than 3 years have passed since last update.

Rails.cache.fetch を正しく運用する!Active Record オブジェクトをキャッシュしたい時

Last updated at Posted at 2020-05-11

問題

Rails エンジニアなら一度はハマる現象。

Rails.cache で Active Record オブジェクトをキャッシュする際、キャッシュしたは良いものの、毎回クエリが発行されてしまう場合があります。

例えば以下のコード。

Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000)
end

原因と解決策

Rails.cacheの落とし穴 の記事に分かりやすく原因と解決策が書かれているので引用します。

ActiveRecord は評価を遅延させます。@users = User.allとしてもすぐにSQLが発行されることはなく@users.eachなど発行するクエリが決まった段階でクエリ発行されるわけです。

結論に書いたケースだとUser::ActiveRecord_Relationのインスタンスがキャッシュに入り、インスタンスが評価された段階でSQLが発行されるため、結果としてキャッシュを使っても毎回SQLが発行されてしまうわけです

以下の用にto_aを使ってクエリを即時発行すればOK。

Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).to_a
end

デメリット

しかし .to_a.all を使ってキャッシュさせる場合、以下のようなデメリットがあります。

  • DB のレコードやカラムに変更があった場合、キャッシュされたオブジェクトには適用されないため、不整合が起きる可能性がある
  • 巨大なオブジェクトをキャッシュするのはコストがかかるので、メモリを埋め尽くす可能性がある
  • 利用元で ActiveRecord::Relation 特有のメソッド(whereなど)が利用できなくなる

好ましい形

user_ids = Rails.cache.fetch(key) do
  User.where('status = 1').limit(1000).pluck(:id)
end
@user = User.where(id: user_ids)

上記のデメリットを回避したい場合、id などの変更されない値をキャッシュすることを考えます。

この場合、 User.where(id: user_ids) は常に実行されますが、主キーを使用したクエリなので処理は高速です。これにより、先に説明したデメリットを回避しつつキャッシュを有効活用できます。

参考リンク

Confusion caching Active Record queries with Rails.cache.fetch

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
15