計算された列をレールにキャッシュするにはどうすればよいですか?
-
05-07-2019 - |
質問
次のようなアクティブなレコードオブジェクトのツリーがあります:
class Part < ActiveRecord::Base
has_many :sub_parts, :class_name => "Part"
def complicated_calculation
if sub_parts.size > 0
return self.sub_parts.inject(0){ |sum, current| sum + current.complicated_calculation }
else
sleep(1)
return rand(10000)
end
end
end
complicated_calculationを毎回再計算するにはコストがかかりすぎます。そのため、値をキャッシュする方法が必要です。ただし、一部が変更された場合、そのキャッシュとその親、祖父母などのキャッシュを無効にする必要があります。
大まかなドラフトとして、キャッシュされた計算を「パーツ」に保持する列を作成しました。テーブルが、これは少し腐ったにおいがします。計算された値を「実際の」横に詰めることなく、キャッシュするよりクリーンな方法があるはずです。列。
解決
-
実際にキャッシュされた値をRailsキャッシュに詰めることができます(配布が必要な場合はmemcachedを使用します)。
-
困難な部分はキャッシュの有効期限ですが、キャッシュの有効期限はまれですよね?その場合、各親オブジェクトを順番にループして、そのキャッシュもザッピングできます。親オブジェクトをシンプルにするために、クラスにActiveRecordマジックを追加しました。データベースに触れる必要さえありません。コードで必要に応じて
Part.sweep_complicated_cache(some_part)
を呼び出すことを忘れないでください-これをコールバックなどに入れることができますが、> complicated_calculation
は変更されています。class Part < ActiveRecord::Base has_many :sub_parts, :class_name => "Part" belongs_to :parent_part, :class_name => "Part", :foreign_key => :part_id @@MAX_PART_NESTING = 25 #pick any sanity-saving value def complicated_calculation (...) if cache.contains? [id, :complicated_calculation] cache[ [id, :complicated_calculation] ] else cache[ [id, :complicated_calculation] ] = complicated_calculation_helper (...) end end def complicated_calculation_helper #your implementation goes here end def Part.sweep_complicated_cache(start_part) level = 1 # keep track to prevent infinite loop in event there is a cycle in parts current_part = self cache[ [current_part.id, :complicated_calculation] ].delete while ( (level <= 1 < @@MAX_PART_NESTING) && (current_part.parent_part)) { current_part = current_part.parent_part) cache[ [current_part.id, :complicated_calculation] ].delete end end end
他のヒント
関連付けコールバックを使用することをお勧めします。
class Part < ActiveRecord::Base
has_many :sub_parts,
:class_name => "Part",
:after_add => :count_sub_parts,
:after_remove => :count_sub_parts
private
def count_sub_parts
update_attribute(:sub_part_count, calculate_sub_part_count)
end
def calculate_sub_part_count
# perform the actual calculation here
end
end
素敵で簡単=)
カウンタキャッシュのようなフィールドを用意します。たとえば、order_items_amountをキャッシュされた計算フィールドにします。
after_saveフィルターを使用して、その値を変更できるものについてフィールドを再計算します。 (レコード自体を含む)
編集:これは基本的にあなたが今持っているものです。キャッシュされた計算フィールドを別のテーブルに保存する場合を除き、よりクリーンなソリューションはわかりません。
before_saveまたはActiveRecord Observerを使用することは、キャッシュされた値が最新であることを確認する方法です。 before_saveを使用してから、計算で使用する値が実際に変更されたかどうかを確認します。そうすれば、必要がない場合にキャッシュを更新する必要がありません。
dbに値を保存すると、複数のリクエストにわたって計算をキャッシュできます。このための別のオプションは、値をmemcacheに保存することです。 memcacheを確認し、必要に応じて更新できる特別なアクセサーとセッターをその値に作成できます。
別の考え:モデルの1つで値を変更し、保存する前に計算を更新する必要がある場合はありますか?その場合、before_saveではなく、モデルの計算値を更新するたびにキャッシュ値をダーティにする必要があります。
データベース内の情報を非正規化する正当な理由がある場合があることがわかりました。私が取り組んでいるアプリには似たようなものがあり、コレクションが変更されるたびにそのフィールドを再計算します。
キャッシュを使用せず、データベースに最新の数値を保存します。