如何在rails中缓存计算列?

时间:2008-10-08 01:39:12

标签: ruby-on-rails ruby calculated-columns caching

我有一个活动记录对象树,如:

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

每次重新计算复杂的计算成本太高。所以,我需要一种缓存值的方法。但是,如果更改了任何部分,则需要使其缓存及其父级和祖父级的缓存无效。

作为草稿,我创建了一个列来保存“部件”表中的缓存计算,但这有点臭。似乎应该有一种更简洁的方法来缓存计算值而不将它们填充在“真实”列旁边。

5 个答案:

答案 0 :(得分:27)

我建议使用关联回调。

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

很好,很容易=)

答案 1 :(得分:6)

  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
    

答案 2 :(得分:2)

有一个类似于计数器缓存的字段。例如:order_items_amount并且具有缓存的计算字段。

使用after_save过滤器重新计算可以修改该值的任何内容的字段。 (包括记录本身)

编辑:这基本上就是你现在所拥有的。除非您想将缓存的计算字段存储在另一个表中,否则我不知道任何更清晰的解决方案。

答案 3 :(得分:2)

使用before_save或ActiveRecord Observer是确保缓存值是最新的方法。我会使用before_save,然后检查您在计算中使用的值是否实际更改。这样,如果您不需要,则无需更新缓存 将值存储在db中将允许您在多个请求上缓存计算。另一个选择是将值存储在memcache中。您可以为该值创建一个特殊的访问者和设置器,以便检查内存缓存并在需要时进行更新 另一个想法是:是否会出现更改其中一个模型中的值并需要在进行保存之前更新计算的情况?在这种情况下,每当更新模型中的任何计算值时都需要弄脏缓存值,而不是使用before_save。

答案 4 :(得分:1)

我发现有时候有充分的理由对数据库中的信息进行反规范化。我在我正在处理的应用程序中有类似的东西,我只是在集合发生变化时重新计算该字段。

它不使用缓存,它在数据库中存储最新的数字。