0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Railsでキャッシュの削除をpub/subで行う(ActiveSupport::Notifications)

Posted at

はじめに

Railsのプロジェクトでキャッシュを使う際のパターンをちょっと考えました。
一般的なActive Recordのキャッシュというよりは、複数のモデルに紐づく返り値のキャッシュをどうするかという観点で、もしもっと良い方法とか一般的なアーキテクチャパターンあれば教えてください。

前提条件

  • 複数モデルに紐づくAPIの返り値をキャッシュしたい
  • 複数モデルに紐づくが、その中の一つでもupdateされたらキャッシュを削除したい

アンチパターン

アンチパターンという言葉はちょっと強いですが、パッと思いつく方法は以下のように紐づくモデルにafter_commitでキャッシュを削除するコードを追加する方法です。

例えば、以下のような場合です。

class Api::MyApiController < ApplicationController
  def index
    cache_key = "my_api_cache_key"
    result = Rails.cache.fetch(cache_key, expires_in: 12.hours) do
      # 複数のモデルからデータを取得する処理
      model1_data = Model1.all
      model2_data = Model2.all
      # 必要なデータのマージ処理
      { model1: model1_data, model2: model2_data }
    end

    render json: result
  end
end

class CacheClearService
  def self.clear_my_api_cache
    Rails.cache.delete("my_api_cache_key")
  end
end

# 各モデルのコールバックでサービスクラスを利用
class Model1 < ApplicationRecord
  after_commit :clear_cache, on: [:update]

  private

  def clear_cache
    CacheClearService.clear_my_api_cache
  end
end

class Model2 < ApplicationRecord
  after_commit :clear_cache, on: [:update]

  private

  def clear_cache
    CacheClearService.clear_my_api_cache
  end
end

これでももちろん良いのですが、この場合の問題点としてはキャッシュを使ってる側では、どのタイミングでキャッシュが削除されているか分からないところです。

使われているモデルを全て見に行かないと、いつキャッシュが削除されているかわかりません。

解決策

キャッシュの呼び出し側で紐づくモデルが分かれば、少なくともキャッシュ削除に関連するモデルは分かります。
そこで、ActiveSupport::Notificationsを使います。

class CacheClearService
  CACHE_KEY = 'my_api_cache_key'
  MODEL1_EVENT_KEY = 'clear_my_api_cache_model1_event'
  MODEL2_EVENT_KEY = 'clear_my_api_cache_model2_event'
  
  def self.setup_cache_cleanup_listener
    ActiveSupport::Notifications.subscribe(MODEL1_EVENT_KEY) do
      self.clear_cache
    end

    ActiveSupport::Notifications.subscribe(MODEL2_EVENT_KEY) do
      self.clear_cache
    end
  end

  def self.clear_cache
    Rails.cache.delete(CACHE_KEY)
  end
end

class Model1 < ApplicationRecord
  after_commit :clear_cache, on: [:update]

  private

  def clear_cache
    ActiveSupport::Notifications.instrument(CacheClearService.MODEL1_EVENT_KEY, self)
  end
end

class Model2 < ApplicationRecord
  after_commit :clear_cache, on: [:update]

  private

  def clear_cache
    ActiveSupport::Notifications.instrument(CacheClearService.MODEL2_EVENT_KEY, self)
  end
end

# initilizerでリスナーを初期化
Rails.application.config.to_prepare do
  CacheClearService.setup_cache_cleanup_listener
end

このようにActiveSupport::Notificationsを使ってイベントをsubscribeする形にすることで、少なくともCacheClearServiceを見れば、どのモデルが関係ありそうとか、イベントを一覧で確認することができますので便利かなと思います。

まとめ

お互いに疎結合のためこれを導入してもテストが落ちることがないというのはある意味で利点ですが、モデル側のキャッシュをクリアするイベントが削除されても、CacheClearService側からは分からないところは欠点かと思いますので、メリットデメリット把握した上で導入されるのが良いと思います。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?