ReactiveCaching
このドキュメントは
reactive_caching.rb
を参照しています。
ReactiveCaching
関数は、バックグラウンドでデータを取得してRailsキャッシュに保存し、リクエストされている間だけ最新の状態に保つために使用されます。データがreactive_cache_lifetime
の間リクエストされなかった場合、データの更新は停止され、削除されます。
使用例
class Foo < ApplicationRecord
include ReactiveCaching
after_save :clear_reactive_cache!
def calculate_reactive_cache(param1, param2)
# Expensive operation here. The return value of this method is cached
end
def result
# Any arguments can be passed to `with_reactive_cache`. `calculate_reactive_cache`
# will be called with the same arguments.
with_reactive_cache(param1, param2) do |data|
# ...
end
end
end
この例では、#result
が最初に呼ばれたとき、nil
を返します。しかし、#calculate_reactive_cache
を呼び出すためにバックグラウンドワーカーをキューに入れ、 最初のキャッシュの有効期間を 10 分に設定しています。
どのように動作するか
最初に#with_reactive_cache
が呼び出されると、バックグラウンド・ジョブがエンキューされ、with_reactive_cache
はnil
を返します。バックグラウンド・ジョブは#calculate_reactive_cache
を呼び出し、その戻り値を保存します。また、reactive_cache_refresh_interval
の後にバックグラウンド・ジョブを再エンキューします。したがって、保存された値は常に最新の状態に保たれます。計算が同時に実行されることはありません。
#with_reactive_cache
値がキャッシュされている間に #with_reactive_cache
呼び出すと、キャッシュされた値が返されます。また、キャッシュの有効期間をreactive_cache_lifetime
の値だけ延長します。
有効期間が過ぎると、バックグラウンド・ジョブはキューに入れられなくなり、#with_reactive_cache
を再度呼び出すと、nil
が返され、処理が最初からやり直されます。
ReactiveCachingにハードリミットを設定
パフォーマンスを維持するために、ReactiveCaching
を含むクラスにハードキャッシュの制限を設定する必要があります。設定方法の例を参照してください。
詳細については、内部イシューRedis (または ReactiveCache) のソフトリミットとハードリミットをお読みください。
いつ
- 外部APIへのリクエスト(例えばk8s APIへのリクエスト)が必要な場合。外部リクエストの間、アプリケーションサーバのWorkerをブロックし続けることは望ましくありません。
- モデルが大量のデータベース呼び出しや時間のかかる計算を行う必要がある場合。
使用方法
モデルやインテグレーションで
ReactiveCaching 懸念は、モデルだけでなくインテグレーション (app/models/integrations
) でも使用できます。
-
モデルやインテグレーションにこのコンサーンを含めてください。
懸念事項をモデルに含めるには
include ReactiveCaching
懸念をインテグレーションに含めるには:
include Integrations::ReactivelyCached
- モデルまたはインテグレーションに
calculate_reactive_cache
メソッドを実装します。 - キャッシュされた値が必要なモデルまたはインテグレーション内部で
with_reactive_cache
を呼び出します。 - それに応じて
reactive_cache_work_type
を設定します 。
コントローラ
ReactiveCaching
を使用するモデルやサービスメソッドをコールするコントローラのエンドポイントは、 バックグラウンドワーカーが完了するまで待つべきではありません。
-
ReactiveCaching
を使用するモデルやサービスメソッドをコールする API は、キャッシュの計算中 (#with_reactive_cache
がnil
を返すとき) に202 accepted
を返すべきです。 - また、
Gitlab::PollingInterval.set_header
でポーリング間隔ヘッダを設定する必要があります。 - APIのコンシューマはAPIをポーリングすることが期待されます。
- ポーリングによるサーバ負荷を軽減するために、ETagキャッシュの実装を検討することもできます。
モデルやサービスに実装するメソッド
ReactiveCaching
を含むモデル/サービスで実装すべきメソッドです。
#calculate_reactive_cache
(必須)
- このメソッドは実装する必要があります。戻り値はキャッシュされます。
- このメソッドは
ReactiveCaching
によって呼び出されます。 -
with_reactive_cache
に渡された引数はすべてcalculate_reactive_cache
にも渡されます。
#reactive_cache_updated
(オプション)
- このメソッドは必要に応じて実装することができます。
- このメソッドは、キャッシュが更新されるたびに
ReactiveCaching
から呼び出されます。キャッシュが更新され、新しいキャッシュ値が古いキャッシュ値と同じ場合、このメソッドは呼び出されません。このメソッドは、新しい値がキャッシュに格納されている場合にのみ呼び出されます。 - これは、キャッシュが更新されるたびにアクションを実行するために使用できます。
モデルもしくはサービスによって呼び出されるメソッド
これらはReactiveCaching
によって提供されるメソッドで、モデル/サービスの内部で呼び出されるべきものです。
#with_reactive_cache
(必須)
-
with_reactive_cache
は、calculate_reactive_cache
の結果が必要な場合に呼び出されなければなりません。 - ブロックは、任意の数の引数を取ることも
with_reactive_cache
できますwith_reactive_cache
。with_reactive_cache
に渡された引数はすべてcalculate_reactive_cache
に渡されます。with_reactive_cache
に渡された引数は、キャッシュ・キー名に付加されます。 - 結果がすでにキャッシュされているときに
with_reactive_cache
が呼び出された場合、ブロックが呼び出されてキャッシュされた値が返され、ブロックの返り値はwith_reactive_cache
によって返されます。また、キャッシュのタイムアウトもreactive_cache_lifetime
の値にリセットされます。 - 結果がまだキャッシュされていない場合、
with_reactive_cache
はnil
を返します。また、calculate_reactive_cache
を呼び出し、結果をキャッシュするバックグラウンド・ジョブもキューに入れます。 - バックグラウンド・ジョブが完了し、結果がキャッシュされると、
with_reactive_cache
への次の呼び出しがキャッシュされた値をピックアップします。 -
以下の例では、
data
がキャッシュされた値で、with_reactive_cache
に与えられたブロックに渡されます。class Foo < ApplicationRecord include ReactiveCaching def calculate_reactive_cache(param1, param2) # Expensive operation here. The return value of this method is cached end def result with_reactive_cache(param1, param2) do |data| # ... end end end
#clear_reactive_cache!
(オプション)
- このメソッドはキャッシュを期限切れ/クリアする必要があるときに呼び出されます。例えば、モデルが修正された後にキャッシュがクリアされるように、モデルの
after_save
コールバックで呼び出すことができます。 - パラメータはキャッシュキーの一部なので、このメソッドは
with_reactive_cache
に渡されるのと同じパラメータで呼び出されなければなりません。
#without_reactive_cache
(オプション)
- これは、デバッグの目的で使用できる便利なメソッドです。
- このメソッドはバックグラウンドワーカーではなく、現在のプロセスで
calculate_reactive_cache
を呼び出します。
設定可能なオプション
class_attribute
、調整可能なオプションがいくつかあります。
self.reactive_cache_key
- この属性の値は、
data
およびalive
キャッシュ・キー名のプレフィックスです。with_reactive_cache
に渡されたパラメータが残りのキャッシュ・キー名となります。 -
デフォルトでは、このキーはモデル名とレコードの ID を使用します。
self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
-
data
キャッシュキーは"ExampleModel:1:arg1:arg2"
で、alive
キャッシュキーは"ExampleModel:1:arg1:arg2:alive"
です。ExampleModel
はモデルの名前、1
はレコードの ID、arg1
とarg2
はwith_reactive_cache
に渡されるパラメータです。 -
この問題をインテグレーション (
app/models/integrations/
) に含める場合は、インテグレーションに以下を追加してデフォルトをオーバーライドする必要があります:self.reactive_cache_key = ->(integration) { [integration.class.model_name.singular, integration.project_id] }
reactive_cache_key
が上記とまったく同じであれば、代わりに既存のIntegrations::ReactivelyCached
を使用することができます。
self.reactive_cache_lease_timeout
-
ReactiveCaching
はGitlab::ExclusiveLease
を使って、キャッシュ計算が複数のワーカーによって同時に実行されないようにします。 - この属性は
Gitlab::ExclusiveLease
のタイムアウトです。 - デフォルトは 2 分ですが、別のタイムアウトが必要な場合はオーバーライドできます。
self.reactive_cache_lease_timeout = 2.minutes
self.reactive_cache_refresh_interval
- これはキャッシュが更新される間隔です。
- デフォルトは1分です。
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime
- これはリクエストがなければキャッシュがクリアされるまでの時間です。
- デフォルトは 10 分です。このキャッシュ値に対するリクエストが 10 分間ない場合、キャッシュは失効します。
- 有効期限が切れる前にキャッシュ値が要求された場合、キャッシュのタイムアウトは
reactive_cache_lifetime
にリセットされます。
self.reactive_cache_lifetime = 10.minutes
self.reactive_cache_hard_limit
- これは
ReactiveCaching
がキャッシュを許可する最大データサイズです。 - デフォルトは1メガバイトです。この値を超えるデータはキャッシュされず、Sentry上で
ReactiveCaching::ExceededReactiveCacheLimit
。
self.reactive_cache_hard_limit = 5.megabytes
self.reactive_cache_work_type
- これは
calculate_reactive_cache
メソッドで実行される作業の種類です。この属性に基づいて、キャッシュジョブを処理する適切なワーカーを選ぶことができます。外部からのリクエスト (Kubernetes や Sentry など) を処理する場合は、必ず:external_dependency
に設定してください。そうでない場合は:no_dependency
に設定してください。
self.reactive_cache_worker_finder
- これは、
calculate_reactive_cache
が呼び出されるオブジェクトを見つける、または生成するためにバックグラウンドワーカーが使用するメソッドです。 -
デフォルトでは、オブジェクトを見つけるためにモデルの主キーを使用します:
self.reactive_cache_worker_finder = ->(id, *_args) do find_by(primary_key => id) end
-
デフォルトの動作は、カスタム
reactive_cache_worker_finder
を定義することでオーバーライドできます。class Foo < ApplicationRecord include ReactiveCaching self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } def self.from_cache(var1, var2) # This method will be called by the background worker with "bar1" and # "bar2" as arguments. new(var1, var2) end def initialize(var1, var2) # ... end def calculate_reactive_cache(var1, var2) # Expensive operation here. The return value of this method is cached end def result with_reactive_cache("bar1", "bar2") do |data| # ... end end end
- この例では、主キーIDは
with_reactive_cache
に渡されたパラメータとともにreactive_cache_worker_finder
に渡されます。 - カスタム
reactive_cache_worker_finder
はwith_reactive_cache
に渡されたパラメータで.from_cache
を呼び出します。
- この例では、主キーIDは