2008-10-08 41 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

我發現,有時有很好的理由在數據庫中去規範化信息。在我正在開發的應用中,我有類似的東西,並且只要集合發生變化,我就會重新計算該字段。

它不使用緩存,它將最新的數字存儲在數據庫中。