2008-10-08 76 views
13

我有活动记录对象的树,像这样:如何在轨中缓存计算列?

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。所以,我需要一种缓存价值的方法。但是,如果任何部分发生更改,则需要使其缓存及其父项和祖父项等的缓存无效。作为粗略草稿,我创建了一个列以在“部分”表中保存缓存计算,但这味道有点烂。似乎应该有一个更清晰的方式来缓存计算值,而不是将它们填充到“真实”列的旁边。

回答

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

有一个类似于计数器缓存的字段。例如:order_items_amount并将其作为缓存的计算字段。

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

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

2

使用before_save或ActiveRecord Observer是确保缓存值是最新的方法。我将使用before_save,然后检查计算中使用的值是否实际发生了更改。这样,如果你不需要更新缓存,你就不必更新缓存。
将值存储在数据库中将允许您缓存多个请求的计算结果。另一个选项是将值存储在内存缓存中。您可以为该值创建一个特殊的访问器和设置器,以检查内存缓存并在需要时进行更新。
另一个想法:是否会出现在其中一个模型中更改值并需要在执行保存之前更新计算的情况?在这种情况下,无论何时更新模型中的任何计算值,都将需要对缓存值进行脏处理,而不是使用before_save。

26

我建议使用联想回调。

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

我猜这不会处理从另一个方向创建子部件的情况(* not * through has_many),如下所示:Part.create(:parent_part => the_parent_part)。我可能会在Part上添加一个after_create回调,以确保count_sub_parts在这种情况下也被触发... – 2012-11-29 19:35:05

1

我发现,有时有很好的理由在数据库中去规范化信息。在我正在开发的应用中,我有类似的东西,并且只要集合发生变化,我就会重新计算该字段。

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