Domanda

Ho un albero di oggetti record attivi, qualcosa del tipo:

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

È troppo costoso ricalcolare ogni volta il calcolo complicato. Quindi, ho bisogno di un modo per memorizzare nella cache il valore. Tuttavia, se una parte viene modificata, deve invalidare la cache e la cache del padre, del nonno, ecc.

Come bozza, ho creato una colonna per contenere il calcolo memorizzato nella cache nella sezione "parti" tavolo, ma questo puzza un po 'marcio. Sembra che ci dovrebbe essere un modo più pulito per memorizzare nella cache i valori calcolati senza riempirli insieme al "reale". colonne.

È stato utile?

Soluzione

  1. È possibile inserire i valori effettivamente memorizzati nella cache nella cache di Rails (utilizzare memcached se è necessario che sia distribuito).

  2. Il momento difficile è la scadenza della cache, ma la scadenza della cache è insolita, giusto? In tal caso, possiamo semplicemente passare su ciascuno degli oggetti padre a turno e anche zappare la sua cache. Ho aggiunto un po 'di magia ActiveRecord alla tua classe per rendere la semplicità degli oggetti principali stessa e non hai nemmeno bisogno di toccare il tuo database. Ricorda di chiamare Part.sweep_complicated_cache (some_part) come appropriato nel tuo codice - puoi inserirlo nei callback, ecc., Ma non posso aggiungerlo per te perché non capisco quando complex_calculation sta cambiando.

    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
    

Altri suggerimenti

Suggerisco di utilizzare i callback di associazione.

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

Nice and easy =)

Avere un campo simile a una cache del contatore. Ad esempio: order_items_amount e avere un campo calcolato nella cache.

Usa un filtro after_save per ricalcolare il campo su qualsiasi cosa possa modificare quel valore. (Incluso il record stesso)

Modifica: questo è fondamentalmente quello che hai ora. Non conosco alcuna soluzione più pulita se non si desidera archiviare i campi calcolati memorizzati nella cache in un'altra tabella.

È possibile utilizzare un before_save o un ActiveRecord Observer per assicurarsi che il valore memorizzato nella cache sia aggiornato. Vorrei utilizzare un before_save e quindi verificare se il valore utilizzato nel calcolo è effettivamente cambiato. In questo modo non è necessario aggiornare la cache se non è necessario.
La memorizzazione del valore nel db consente di memorizzare nella cache i calcoli su più richieste. Un'altra opzione per questo è quella di memorizzare il valore in memcache. È possibile creare un accessorio e un setter speciali per quel valore che può controllare la memcache e aggiornarla se necessario.
Un altro pensiero: ci saranno casi in cui cambierai un valore in uno dei modelli e devi aggiornare il calcolo prima di effettuare il salvataggio? In tal caso dovrai sporcare il valore della cache ogni volta che aggiorni uno dei valori di calcolo nel modello, non con un before_save.

Ho scoperto che a volte ci sono buone ragioni per de-normalizzare le informazioni nel tuo database. Ho qualcosa di simile in un'app su cui sto lavorando e ricalcolo quel campo ogni volta che cambia la raccolta.

Non utilizza una cache e memorizza la cifra più aggiornata nel database.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top