質問

次のようなアクティブなレコードオブジェクトのツリーがあります:

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を毎回再計算するにはコストがかかりすぎます。そのため、値をキャッシュする方法が必要です。ただし、一部が変更された場合、そのキャッシュとその親、祖父母などのキャッシュを無効にする必要があります。

大まかなドラフトとして、キャッシュされた計算を「パーツ」に保持する列を作成しました。テーブルが、これは少し腐ったにおいがします。計算された値を「実際の」横に詰めることなく、キャッシュするよりクリーンな方法があるはずです。列。

役に立ちましたか?

解決

  1. 実際にキャッシュされた値をRailsキャッシュに詰めることができます(配布が必要な場合はmemcachedを使用します)。

  2. 困難な部分はキャッシュの有効期限ですが、キャッシュの有効期限はまれですよね?その場合、各親オブジェクトを順番にループして、そのキャッシュもザッピングできます。親オブジェクトをシンプルにするために、クラスに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ではなく、モデルの計算値を更新するたびにキャッシュ値をダーティにする必要があります。

データベース内の情報を非正規化する正当な理由がある場合があることがわかりました。私が取り組んでいるアプリには似たようなものがあり、コレクションが変更されるたびにそのフィールドを再計算します。

キャッシュを使用せず、データベースに最新の数値を保存します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top